Trap Catalog Maintenance

Trap Catalog

data/traps.json is the load-bearing artifact for Mobile Surfaces’ agentic-development surface. It enumerates every silent-failure trap, contract invariant, and platform constraint that downstream consumers (the AI assistant guides, future audit CLI, future MCP server, the long-form troubleshooting recipes) read from. Edit the catalog; everything else regenerates.

Pipeline

data/traps.json ──► packages/surface-contracts/src/traps.ts (Zod validation)
                ├─► AGENTS.md   (generated by scripts/build-agents-md.mjs)
                └─► CLAUDE.md   (identical content; Claude Code reads this directly)

Additional consumers shipped in v7:

  • npx mobile-surfaces audit [path]: walks the catalog against an existing Expo project, emits a pass/warn/fail report with MS-id chips and docsUrl links. Backed by the same script set that runs in pnpm surface:check for this repo. Available with the create-mobile-surfaces install.

All consumers read from this catalog. None are hand-maintained.

Catalog schema

Defined in packages/surface-contracts/src/traps.ts and exported as trapCatalog. The top-level shape:

{
  "schemaVersion": "1",
  "entries": [ /* TrapEntry[] */ ]
}

Each TrapEntry:

FieldTypeRequiredNotes
idMS\d{3}yesStable across releases. Append new ids; never reuse.
titlestringyesShort imperative title.
severityerror | warning | infoyesSee Severity.
detectionstatic | config | runtime | advisoryyesSee Detection.
tagsTrapTag[]yes (≥1)Free-form within a closed enum; see traps.ts.
summarystringyesOne-sentence statement of the rule.
symptomstringyesWhat the user sees when the rule is violated. Live Activity failures are mostly silent, so write the silence: “no error, just X never appears.”
fixstringyesConcrete fix in one or two steps.
iosMinstringnoMinimum iOS version where the rule applies, e.g. "17.2".
enforcement.scriptstringnoRepo-relative path to the script that enforces this rule today. The validator confirms the file exists.
appleDocsURL[]noPointers into Apple documentation when the failure mode is platform-defined.
docsstring[]noPointers into local docs/ (path + optional #anchor).
sincesemveryesCatalog schema version when the rule was introduced.
deprecatedbooleannoSet when the rule has been retired but the id is reserved.

Severity

SeverityWhen to use
errorViolation breaks a contract, produces a silent failure on real devices, or makes a build unshipable. AI assistants must not bypass error rules.
warningViolation degrades reliability or operating cost (e.g. priority budgets, end-of-life token cleanup) but does not break the user experience on the first send.
infoAwareness-only. Apple bugs, force-quit caveats, cross-platform notes. Detection is advisory.

Detection

DetectionMeaning
staticRepo-local script can verify the rule by reading source files. Most catalog rules are here.
configVerifiable from declarative config (app.json, package.json, expo-target.config.js).
runtimeOnly surfaces at send/receive time. APNs reason codes, token environment crossing, payload-size ceilings.
advisoryNo programmatic check. Captured in the catalog so the rule appears in AGENTS.md and the user has runbook coverage.

Workflow: adding a rule

  1. Pick the next id. Highest existing id + 1, padded to three digits. Reserved ids stay reserved even when retired.
  2. Add the entry to data/traps.json. Keep entries in numeric order. Multi-line prose is fine; JSON requires \n for line breaks but most fields read better as a single sentence.
  3. Run pnpm traps:check. Validates the catalog against the Zod schema and confirms every enforcement.script exists on disk.
  4. Run pnpm agents:build. Regenerates AGENTS.md and CLAUDE.md.
  5. Run pnpm surface:check. Confirms the umbrella picks up your new rule and nothing is stale.
  6. Commit data/traps.json, AGENTS.md, and CLAUDE.md together. A drift-free PR is a one-shot review.

If the rule is static or config and you want CI to enforce it, add the script under scripts/ and reference it via enforcement.script. The validator script in scripts/validate-trap-catalog.mjs confirms the path exists; the script itself runs from surface:check. The next milestone (mobile-surfaces check) will route enforced rules through these scripts directly so the catalog and the runner share one definition.

Workflow: editing or deprecating a rule

  • Edit: mutate the entry in place. Bump since only if the change widens the set of inputs that violate the rule (i.e. an existing project may now report a new error). Tightening detection without changing the rule’s meaning does not require a since bump.
  • Deprecate: keep the entry, set deprecated: true, optionally add a deprecatedReason-style note inside summary. Do not delete and do not reuse the id.

Why a catalog instead of hand-written AGENTS.md

AGENTS.md files rot. Project conventions evolve, iOS minor versions shift the deployment floor, Apple closes radars, and @bacons/apple-targets ships features that obsolete entire rules. A hand-maintained AGENTS.md is a rotting prose document by month six.

A structured catalog gives every fact one home. When iOS 27 lands and the deployment floor changes, we update MS012 in data/traps.json, regenerate, commit. The change is a one-line diff plus the regenerated artifacts. Reviewing it is trivial, and every downstream consumer (the AI guide files, the future audit CLI, the long-form docs) picks up the change without manual edits.

See also