Skip to content

fix(apps): allow version metadata reads when relationship linkage is absent#6

Closed
muenzpraeger wants to merge 1 commit into
zelentsov-dev:developfrom
muenzpraeger:fix/version-localization-relationship-guards
Closed

fix(apps): allow version metadata reads when relationship linkage is absent#6
muenzpraeger wants to merge 1 commit into
zelentsov-dev:developfrom
muenzpraeger:fix/version-localization-relationship-guards

Conversation

@muenzpraeger

Copy link
Copy Markdown

Summary

Every tool that reads version-level App Store metadata fails for all apps and all versions, even when passed a version_id the server's own list tools just returned. apps_get_metadata and apps_list_localizations are effectively dead, so the keyword field, description, promotional text, and "What's New" (which live only on appStoreVersionLocalizations) cannot be read back at all.

Root cause

One-line: the two relationship guards require a JSON:API resource linkage that App Store Connect only serializes when the request sends include=, so on the default response shape the parsed id is absent and the guard treats "absent" as "mismatch."

versionBelongsToApp() and localizationBelongsToVersion() in AppsWorker+Handlers.swift gate the read paths on:

  • version.relationships.app.data.id == appId
  • localization.relationships.appStoreVersion.data.id == versionId

App Store Connect only includes a relationship's data linkage when the request explicitly asks for it via include=app / include=appStoreVersion. These read paths do not send include=, so Apple returns a links-only relationship:

"app": {
  "links": {
    "self": ".../relationships/app",
    "related": ".../app"
  }
}

There is no data member, so the parsed id is absent. The guards' guard let ... else { return false } then treated the absent linkage as a mismatch and rejected every response, producing:

  • apps_get_metadata -> Apple returned a localization outside version '<id>' context
  • apps_list_localizations -> Version '<id>' does not belong to app '<id>'

App-level reads (app_info_list via appInfoLocalizations) and apps_list_versions / apps_get_details were unaffected because they do not hit these guards, which is why the failure was isolated to the version-localization code path.

Reproduction

Against a live account:

apps_get_metadata(app_id="<app>")
  -> Error: Apple returned a localization outside version '<version>' context
apps_get_metadata(app_id="<app>", version_id="<valid-version>", locale="en-US")
  -> same error
apps_list_localizations(app_id="<app>", version_id="<valid-version>")
  -> Error: Version '<version>' does not belong to app '<app>'

The <valid-version> id in each case was returned by apps_list_versions / app_versions_list moments earlier.

Fix

Treat an absent linkage as "cannot verify, allow" and reject only on a positive mismatch (linkage present but pointing at a different id). The sub-resources are fetched under /appStoreVersions/{id}/appStoreVersionLocalizations and the version is fetched by id, so the URL already scopes them; the guard stays meaningful for the include= case without producing false negatives on the default response shape. The change is return false -> return true in the else branch of each guard, plus explaining comments.

Tests

Adds AppsMetadataRelationshipGuardTests, which drives both apps_get_metadata and apps_list_localizations through a recorded App Store Connect response whose relationship linkage is omitted (the default Apple shape) and asserts the tools succeed and return the metadata (e.g. the keywords field).

These tests fail before the change (guard fires, isError == true) and pass after. All fixtures use dummy UUIDs; no real ids are committed.

The existing AppsMetadataGetSelectionTests continue to pass: their fixtures include the linkage, so the positive-match path is still covered.

swift test -> all tests pass (1168 tests / 108 suites, including the 2 new ones).

Notes

  • No API contract change; no new include= params were added (the smaller, safer fix, since the URL already scopes the resource).
  • No credentials, keys, or real resource ids are included in the diff or fixtures.

…absent

apps_get_metadata and apps_list_localizations always failed with
"does not belong to app" / "Apple returned a localization outside version
context", even for a version id the server's own list tools just returned.

Root cause: versionBelongsToApp() and localizationBelongsToVersion() require
the JSON:API resource linkage (relationships.app.data /
relationships.appStoreVersion.data). App Store Connect only serializes that
linkage when the request asks for it with include=. These read paths do not
send include=, so Apple returns a links-only relationship with no data member,
the parsed id is absent, and the guard treated absent as a mismatch and
rejected every response.

Fix: treat an absent linkage as "cannot verify, allow" and reject only on a
positive mismatch (linkage present but pointing at a different id). The
sub-resources are fetched under /appStoreVersions/{id}/... and the version is
fetched by id, so the URL already scopes them; the guard remains useful for the
include= case without producing false negatives on the default response shape.

Adds AppsMetadataRelationshipGuardTests exercising both tools against a
recorded response with the relationship linkage omitted (the default Apple
shape). The tests fail before this change and pass after. Fixtures use dummy
UUIDs only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zelentsov-dev

Copy link
Copy Markdown
Owner

Thank you for the detailed live reproduction and root-cause analysis — it directly led to the v4.1.3 fix.

We shipped a containment-preserving variant: the affected reads now explicitly request Apple relationship linkage with include=app or include=appStoreVersion, while missing or mismatched linkage still fails closed. The same issue class was also corrected across version metadata/localizations, review details and attachments, review submissions, lifecycle submission, and legacy age-rating lookup.

Verification completed on the tagged release: 1,847 tests, zero-warning release build, strict App Store Connect OpenAPI 4.4.1 contract, Mint installation from the tag, and a real MCP stdio smoke. You are credited in the changelog and release notes.

Released: https://github.com/zelentsov-dev/asc-mcp/releases/tag/v4.1.3

Closing this PR as resolved by v4.1.3. Thanks again for finding and documenting it so clearly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants