---
name: asc-audit
category: asc
description: Read-only drift check between live App Store Connect state, the repo, and whatever launch/submission docs claim. Use before quoting any build/version/TestFlight number back to the user, before trusting a canonical "where we stand" doc block, and as the required first step of submission-preflight before any submit. Answers "is what we believe still true?" — never a compliance checker and never a fixer.
invoke: "/asc-audit [app] — Check for drift between live App Store Connect state and what the repo/docs claim."
---

# ASC Drift Audit

**This skill answers one question: _is what we believe still true?_**

It is not a rule checker and not a fixer. It diffs three sources of truth —
**live ASC**, **the repo**, and **what the project's launch/submission docs
claim** — and reports where they disagree. Every fix is a human decision,
except an unambiguous doc correction (a stale fact restated to match live
state) with no submission-strategy judgment involved — apply those, flag
everything else.

```
asc_audit({
  app_id: "<App Store Connect App ID, from asc_list_apps>",
  repo_build: "<optional: expected build number from the repo>",
  repo_version: "<optional: expected marketing version from the repo>",
  runbook_path: "<optional: path to a canonical launch/state doc for freshness check>"
})
```

## Why this exists

Two independent incidents, in two different apps, six weeks apart, found the
same shape of silent drift:

| Drift | Silent for |
|---|---|
| A version's draft was attached to an old build while several newer builds shipped past it | ~6 days |
| Release-notes text identical across many builds (copied forward, not rewritten) | 8+ builds |
| A TestFlight feedback email pointing at a stale/wrong domain | unknown |
| A canonical "where we stand" doc presenting old state as current | 6 days |
| A version actually `REJECTED` in ASC while the canonical doc still said `WAITING_FOR_REVIEW` | 3 days, 8 builds |
| Live ASC review notes naming a build number 8 versions stale, and missing a disclosure paragraph the local doc had | unknown, at least 8 builds |

**None of these were knowledge gaps.** The information needed to catch each
one was already sitting in a doc or a teammate's memory. The drift happened
anyway because every other ASC skill is a *forward action* — a ship skill
uploads, a beta-distribution skill attaches, `submission-preflight` runs once
at submission time. Nothing ever goes back and re-checks what's actually
true on Apple's servers right now. That is the gap this fills.

**Corollary: run it before you quote a number.** Any build, version, or
TestFlight state repeated from a doc rather than read live from ASC is a
guess wearing a fact's clothing.

## Read-only, always

Every call this skill makes is a `GET`. Never extend it to write. When it
finds drift, report the finding and let the user choose — attaching a build,
submitting to Beta App Review, and editing review-facing text are all their
call, and several of them are outward-facing.

## The four drift classes

**1 · Build drift** — the draft version holds an older build than the newest
`VALID` one, or disagrees with the repo's own build-number source of truth
(`project.yml`, `project.pbxproj`, whatever this project uses). This is the
dangerous one: it silently redefines what would go to App Review, and it
invalidates any device/QA gate run against the previous candidate. A gate
result belongs to a *build*, never to a version.

**2 · Assigned ≠ distributed** — a build attached to the external TestFlight
group but never actually sent to Beta App Review. Distinguish three states so
a normal queue wait isn't reported as a mistake:

| `externalBuildState` | Meaning |
|---|---|
| `READY_FOR_BETA_SUBMISSION` | never sent to Beta App Review — **the real bug** |
| `WAITING_FOR_REVIEW` / `IN_REVIEW` | in Apple's queue, nothing to do |
| `BETA_APPROVED` / `IN_BETA_TESTING` | testers can install it |

**3 · Copy-forward release notes** — "What to Test" / release notes
byte-identical across several recent builds means the text is being carried
forward, not written for what actually changed in this build. Whether that's
acceptable depends on the project's own convention (some pre-launch projects
deliberately keep a "first look at the whole app" note rather than a
changelog) — report it either way and let the project's own docs settle
whether it's a bug.

**4 · Consistency drift** — feedback vs. review contact email on different
domains, locale asymmetry in screenshots/keywords/what's-new, a missing App
Preview, a subscription not `READY_TO_SUBMIT`, live review-notes text naming
a build number several versions behind the newest `VALID` build, a
disclosure/compliance paragraph present in a local doc but absent from the
live ASC "Notes for Review" field, or a canonical state block older than a
few days without being re-verified.

## Live-call sequence

If a composite one-call `asc_audit` MCP tool is registered and working, use
it directly with the parameters above. If it isn't available, or 404s, run
the equivalent sequence by hand — this is the exact sequence that has caught
every drift instance found so far:

1. `asc_list_apps` — confirm the app ID if unsure.
2. `asc_list_versions(app_id)` — current version string + `appStoreState`.
   Compare against what the canonical doc claims.
3. `asc_get_review_detail(version_id)` — live "Notes for Review" text; check
   it for a build number and diff against the newest `VALID` TestFlight build,
   and check whether every disclosure paragraph the local submission doc has
   actually made it into this live field.
4. `asc_check_submission(app_id, version_id)` — `ready` + per-locale metadata
   completeness flags.
5. `asc_list_tf_builds(app_id, limit: 10)` — newest `VALID` build number, diff
   against the repo's build-number source of truth.
6. `asc_get_metadata(version_id)` — per-locale description/keywords/whatsNew,
   for the locale-asymmetry check (drift class 4).

## Findings that are informational, not fixable

- **`whatsNew` set on one locale only, on a first (1.0) version.** ASC
  **locks** this field on a first version and returns
  `409 STATE_ERROR — 'whatsNew' cannot be edited at this time` for both a
  write *and* a clear. An inherited or partially-set value cannot be
  corrected via the API on a first version. It is also not displayed to
  users on a first release. Report it once; never treat it as a real
  blocker or keep re-attempting the write.

## What this does NOT do

| Need | Use instead |
|---|---|
| Will App Review reject us (content/guideline risk)? | `submission-preflight` |
| Upload a build | the project's own ship/build skill |
| Attach to the external TestFlight group / beta review | the project's own beta-distribution skill |
| Keywords, metadata, ASO strategy | `asc-aso` |
| Respond to reviews | `review-management` |
| Diagnose an actual rejection | `app-rejection-recovery` |

Those own the actions. This skill only tells you the ground truth they
should be acting on.

## After a run

If findings change the project's real state, update the project's own
canonical "where we stand" doc (whatever it calls it — a launch runbook, an
ASC registration doc, a state block) with what was actually verified, and
let every other doc that references it keep pointing there rather than
restating the numbers themselves. Record *what was verified and when*, not
just the numbers, so the next audit can tell a fresh fact from a stale one.

If a project doesn't have a canonical state doc yet and is actively shipping
through App Store Connect, that absence is itself worth reporting — every
project this pattern has been checked against loses live-vs-doc sync
eventually without one.
