Skip to content

refactor(metadata,spec)!: retire the adr-0030-notification-event migration — no operator door, no platform invoker - #17194

Draft
claude[bot] wants to merge 7 commits into
mainfrom
claude/issue-16194-retire-notification-event-migration
Draft

refactor(metadata,spec)!: retire the adr-0030-notification-event migration — no operator door, no platform invoker#17194
claude[bot] wants to merge 7 commits into
mainfrom
claude/issue-16194-retire-notification-event-migration

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #16194

Clause-②: yes

Executes the director-seat ruling of 2026-09-08 (decision batch #88, comment 5582372148) — the third answer. ⛔ Both alternatives stay refused and this PR adds neither: no os migrate notification-event sub-command, no boot-time invoker.

The ruling, verbatim (Chinese decisions quoted in the original elsewhere; this one is English):

Ruled. The third answer. A migration nothing can run is not kept alive by three landed artefacts around it: migrateSysNotificationToEvent, its barrel export, its tests, the sys_migration receipt row for this id (#15710's matrix keeps the other two ids), the spec docblock and the id's membership in CREATION_ATTESTED_MIGRATION_IDS are removed in one PR. Pre-ADR-0030 sys_notification rows are not carried by the platform on this line.


⚠️ This PR is expected to be parked outside the merge queue

Clause-②: yes fires on both limbs — an exported symbol and an attested id are removed, and packages/spec/src/system/migration.zod.ts is a *.zod.ts under packages/spec/src/** (a content-independent limb). A clause-② card builds at the default judgment tier and is reviewed at CONTRACT_REVIEW_TIER; the enqueue gate refuses the queue until an in-seat PASS is on record. That tier is measured unavailable to the dispatching session. ⇒ this PR is meant to sit here, green, until that changes. ⛔ Not a failure, ⛔ do not flip it ready, ⛔ do not arm auto-merge, ⛔ do not work around the label.


The six artefacts, each proved removed rather than renamed

Measured before/after (git grep at merge base 44c917a47 vs branch head), excluding CHANGELOGs and release notes:

# Artefact Before After
1 migrateSysNotificationToEvent (the runner) 42 occurrences in 9 files; migrate-sys-notification-to-event.ts present 13 occurrences in 8 files, all of them tombstones, prose or the two new retirement pins; the module is gone from disk
2 its barrel export in packages/metadata/src/migrations/index.ts 1 export block naming it plus its 3 types 0 — replaced by a ⚰️ TOMBSTONE block in the house style of the addSysMetadataOverlayIndex tombstone above it
3 its tests migrate-sys-notification-to-event.test.ts (754 lines) and notification-migration-audit-preservation.integration.test.ts (282 lines) present; 5 references in real-driver-exec-surface.test.ts both files deleted; 0 references in real-driver-exec-surface.test.ts (its two runner cases and the now-unused engine double went with them)
4 the sys_migration receipt row for this id notification-event-migration-ledger.pin.test.ts (244 lines) pinning the ruled matrix; 7 DATA_MIGRATION_FLAG_OBJECT sites in the runner pin file deleted; runner deleted. #15710's matrix keeps the other two ids, untouched
5 the spec docblock stating the matrix 5 ledger-column bullets on NOTIFICATION_EVENT_MIGRATION_ID 0 bullets — the docblock is now a retirement tombstone naming both refused alternatives and the reversal path
6 the id's membership in CREATION_ATTESTED_MIGRATION_IDS a 3-tuple ending in NOTIFICATION_EVENT_MIGRATION_ID a 2-tuple: FILE_REFERENCES_MIGRATION_ID, VALUE_SHAPES_MIGRATION_ID

Rename control, both legs. git grep -nE 'export [^;]*(SysNotificationMigration|migrateSysNotificationToEvent|NotificationToEvent)' returns zero at the branch head. The same expression at the merge base returns the four declarations (SysNotificationMigrationReceipt, …Result, …Options, migrateSysNotificationToEvent) — so the probe fires and the zero is a reading, not a dead grep.

NOTIFICATION_EVENT_MIGRATION_ID itself is kept, deliberately: the ruling enumerates the docblock and the membership as separate removals, which it would not need to do if the constant were going. A deployment attested at birth still holds a sys_migration row keyed 'adr-0030-notification-event', and the constant is that row's name. Nothing writes or reads one now. See Open question below.


Acceptance conditions

① The population, re-measured with a firing control

Re-run on the merge base, not on the card's older head:

  • Production callers: 0. Excluding CHANGELOGs, docs/ and *.test.ts, every occurrence was the definition, its own usage docblock, the barrel line, one comment in driver-exec.ts and the id's docblock in @objectstack/spec. The only two call-shaped occurrences were line 27 (inside a docblock, * await migrateSysNotificationToEvent(...)) and line 168 (the definition).
  • Instrument live: the symbol was in 17 files — up from the card's 13, because packages/runtime/src/notification-migration-audit-preservation.integration.test.ts (The sys_notification migration back-dates created_at through the create-side ?? that #15964 removes, so migrated inbox rows and receipts get the migration instant #16312) landed since. A run finding zero everywhere would be a dead grep; this one found 17 files and 42 non-CHANGELOG occurrence lines while finding zero production callers.

② The three sibling ids are the control

Two new pins, and every negative in them is paired with the survivors asserted by name — a case that only says "the third is gone" cannot tell a correct removal from a set that lost everything:

  • packages/spec/src/system/notification-event-migration-retirement.test.ts — the array is read as a literal, in order, twice (by symbol and by string), plus toHaveLength(2); both ADR-0104 ids asserted present by name; the retired id asserted absent by value.
  • packages/platform-objects/src/system/migration-flag.test.ts — a new case runs the real attestFreshDatastore and asserts no row and no attestation for the retired id, and that both survivors still get their row in the uniform shape (applied_at: null, blocking: 0, details.attested, verified_at set) and read back verified.

③ Removal, not rename

The table above, plus the two-legged rename control.

④ Ablation — directions predicted in writing first

Predictions were written into the ablation script before it ran (ablate.sh header). Both matched exactly:

A — put the id back into CREATION_ATTESTED_MIGRATION_IDS.
Predicted: RED on exactly two cases in the spec pin, and the two docblock cases stay GREEN (a pin that reddened on both would not be telling the artefacts apart).
Observed: vitest exit = 1, 2 failed | 5 passed× CREATION_ATTESTED_MIGRATION_IDS is exactly the TWO surviving ids, in order, × the retired id is not a member, by value. Docblock cases green.

B — put the runner module and its barrel export back.
Predicted: RED on exactly three cases in the metadata pin, and the barrel carries a tombstone stays GREEN (the tombstone is left in place, so that case discriminates the export from the prose beside it).
Observed: vitest exit = 1, 3 failed | 1 passed× the module is gone from disk, × nothing in this package imports or re-exports the module, under any spelling, × the barrel exports no name derived from it. Tombstone case green.

Mechanics. The fix was committed before either leg. Each mutation proved on disk by occurrence counts and a blob hash off the HEAD blob — A: membership lines 0 -> 1, blob e997213… to 75c399c…; B: barrel export blocks 0 -> 1, blob 5e42483… to 23da44c…, and the restored runner byte-identical to the merge-base blob dc21ee6…. Each restore proved by git checkout HEAD -- path (⛔ never a bare git checkout --, which takes the index), a blob equal to HEAD's, and an empty git diff HEAD; the script ends by observing git status --porcelain is empty. An empty hash is treated as a FAILURE, never as "nothing to compare". The whole thing runs under trap … EXIT INT TERM with absolute paths only.

Not a dist ablation, stated rather than assumed: both subjects are read through relative, in-package specifiers (./migration.zod, ./index.js) that vitest resolves to src/, never through a package exports entry to dist/, so there is nothing for ablation-dist-preflight.mjs to assert and no rebuild leg. The platform-objects suite does reach spec through exports and is deliberately not the suite ablated, for exactly that reason.

⑤ Single-writer holders, from the OPEN PR LIST

Measured from GET /pulls?state=open and each PR's own /files (its head against its own merge base) — ⛔ never from remote branches. My authored face is the 16 paths of git diff --name-only $(git merge-base origin/main HEAD) HEAD, not the three-dot set against a stale base, which would have counted main's own merged commits as mine.


Coordination with #16100 — verified rather than assumed

The ruling predicted #16100 was "in flight". It is not: #16100 closed completed on 2026-09-06, two days before the ruling. What it actually shipped is the receipt writer inside the runner plus the #15710 ledger pin — both of which this PR removes, which is what the ruling names as artefact 4. The receipt-writing machinery it added was specific to this id, not generic: it lived in migrate-sys-notification-to-event.ts itself. Nothing generic is lost, and #15710's matrix keeps its other two ids untouched.

Reversal path

If a named deployment turns out to hold pre-ADR-0030 notification rows it needs, the migration returns as an operator-runnable os migrate sub-command shaped exactly like files-to-references / value-shapes — dry-run default, --apply gate, documented consequence, all three of which this migration never had — under its own card. ⛔ Not as a quiet reintroduction. This is recorded in three places that outlive the PR: the changeset, the barrel tombstone, and the id's docblock.

⚠️ The ruling's own confidence gap, restated rather than papered over

Nobody has measured whether any live deployment — cloud included — carries pre-ADR-0030 sys_notification rows. The basis for not weighting that is the maintainer's standing instruction 「2069 不考虑现有数据」, quoted in the original per AGENTS.md. This PR does not present the retirement as if that measurement had been made. Concretely: a deployment that still holds legacy per-user inbox rows keeps them where they are, and the new bell will not show them.


Verification

Every exit code captured by redirect-then-$?, ⛔ never through a pipe.

Gate family — derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final tree (⛔ never a hand-written list), then reconciled:

Run reconciliation — 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED.

85 of 88 pass. 3 are NOT MEASURED, declared as such — each exits 3, its own PREREQUISITE-NOT-MET code, distinct from a finding's 1, because it reads built output for the whole workspace and this box has only the four affected packages built. ⛔ Not counted as passes:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ (37 packages named).
  • pnpm check:i18n — needs the CLI plus the build closure of every package whose extract config it runs.
  • pnpm check:type-check-debt--re-measure cannot run: 1 workspace dependenc(ies) … have no built type entry point on disk. Its own text: this is NOT a pass and NOT a finding.

CI builds the whole workspace and will measure all three.

Tests and typecheck, on the final head:

Command Result
pnpm --filter @objectstack/spec test 470 passed (470) files, 13191 passed (13191)
pnpm --filter @objectstack/metadata test 52 passed (52) files, 776 passed (776)
pnpm --filter @objectstack/platform-objects test 38 passed (38) files, 550 passed (550)
pnpm --filter @objectstack/runtime test 248 passed (248) files, 3511 passed (3511)
typecheck (spec, metadata, platform-objects, runtime) all green; each package's test layer compiles under its own tsconfig.test.json
pnpm --filter @objectstack/spec check:generated ✓ All 15 generated artifacts are up to date — including check:api-surface, check:authorable-surface, check:docs, check:export-origins

scripts/engine-double-contract.pinned.json was regenerated with node scripts/check-engine-double-contract.mjs --write, exactly as the gate's own refusal prescribes for a deleted test. The diff is 6 rows removed, 0 added — 3 from the deleted runner test, 3 from real-driver-exec-surface.test.ts whose engine double is genuinely gone (that file no longer needs an engine at all). Gate green after.

pnpm check:nul-bytes green, plus a hand sweep of the diff with grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'.



Docs-drift round — discharged

Nothing in content/docs is falsified by this PR, and nothing there is edited. This PR touches zero content/docs paths. Every listed page is an input row: it mentions a symbol or literal the diff touches, not a page the diff changes.

Closing the bot's own uncommitted-checkout hole, by tree object rather than by sha

The bot's merge tree c6f329fdfc is not fetchable here, so the comparison is anchored on its base commit fb7d75fca1, whose content/docs tree is f5fe6bec53…identical to origin/main's. My head's content/docs tree is 150e82d6e9…. The two differ in exactly one file, content/docs/permissions/system-context.mdx, and it is main's: the commit that changed it is fb7d75fca1 itself (the bot's own base), which git merge-base --is-ancestor answers is not on this branch (exit 1), with a control leg — fd5cff209f, known to be in this HEAD's history — answering exit 0 in the same shallow checkout, so the negative is a reading rather than a truncation artefact. git diff --name-only $(git merge-base origin/main HEAD) HEAD -- content/docs returns 0 paths.

Re-derived with node scripts/docs-audit/affected-docs.mjs --json fb7d75fca1c45dc9bfa5875df8eec6b385a2efcb: same 13 docs, and "dirty": false on this checkout — this re-derivation has no uncommitted-changes hole.

The release-owned rows — ⛔ read only, ⛔ not edited

content/docs/releases/v17/17-4.mdx is NOT falsified. Two rows name the surface (plus a third the round did not list):

  • :376 — "a7da4deadr-0030-notification-event joins CREATION_ATTESTED_MIGRATION_IDS …"
  • :226 — "8647c87 — A completed run of the ADR-0030 notification cut-over now records itself in the sys_migration deployment ledger …"
  • :382 — "c2a336c@objectstack/spec/system now names the ADR-0030 notification cut-over …"

Why they stand: the page is the record of a version that is already out. @objectstack/spec@17.4.0 is published and is npm's latest tag; the repo is at 17.4.0 and packages/spec/CHANGELOG.md carries its ## 17.4.0 section. This PR's changeset is minor, so its removal lands in 17.5.0 — a version with no page yet. Each row is a true statement about what 17.4.0 shipped, and 17.4.0 did ship exactly that. :382 is doubly safe: NOTIFICATION_EVENT_MIGRATION_ID is kept, so @objectstack/spec/system still names the cut-over today. Retro-editing a shipped release page is precisely what the content/docs/releases/ guardrail exists to prevent; this PR's input to release notes is its changeset, which already states the removal.

The other three release rows are wrong rows:

  • 17-0.mdx:384sys_notification.action_url cited as evidence for a flow-node key rename. It describes the object's column, which exists; anchored via the sys_notification string literal that the deleted runner's EVENT_OBJECT const held.
  • 17-0.mdx:756 and 17-2.mdx:240/:375meta.getLegalNextStates, reached through the SDK route bridge. Zero relation to the migration.
  • 17-1.mdx:172 — localized templates reaching sys_inbox_message: the live pipeline, which this PR does not touch.

The nine hand-written rows — judged, and most are wrong rows

Page Anchor Verdict
permissions/authorization.mdx, permissions/explain.mdx, permissions/rls.mdx not_applicable ⚠️ Wrong rows, all three. Each uses not_applicable as an authorization layer verdict in the closed vocabulary grants / denies / narrows / widens / neutral / not_applicable. The anchor is the migration result's four-valued vocabulary (migrated / already_done / not_applicable / error). A bare-token collision, matched on the string and not on its vocabulary.
kernel/contracts/metadata-service.mdx getObject ⚠️ Wrong row, and the mechanism is nameable. The anchor is LEDGER_METHODS = ['getObject', 'find', 'insert', 'update'] and a method of interface MigrationLedger — both private, non-exported declarations that lived inside the deleted runner as a duck-typing probe. The page documents IMetadataService.getObject(name), a different declaration in a different package, untouched.
protocol/objectql/state-machine.mdx route bridged from getObject ⚠️ Wrong row, same private-name collision, one bridge further out. The page documents GET /api/v1/meta/object/:name/state/:field.
protocol/kernel/realtime-protocol.mdx recipient_id from LEGACY_COLUMNS Not falsified. Read properly: the hit is ### Case 3: Live Notifications, an illustrative WebSocket example subscribing to a generic notification object. The page never mentions sys_notification or any migration (grep: zero hits). LEGACY_COLUMNS was a private const naming the pre-ADR-0030 columns the runner cleared; deleting it changes no object shape and no column.
automation/hook-bodies.mdx sys_notification Not falsified — a code example inserting into sys_notification, an object that still exists.
concepts/north-star.mdx sys_notification_receipt Not falsified — a Drift entry: "The notification framework pipeline is ahead of the objectui bell cut-over; read-state should move through sys_notification_receipt." About the live model; still true.
api/client-sdk.mdx sys_inbox_message / sys_notification_receipt Not falsified — an ADR-0030 note that the framework materializes through sys_inbox_message and tracks read-state in sys_notification_receipt, helpers to be repointed at the bell cut-over. All still true.

The bot's declared coverage hole, searched by hand

packages/metadata/src/migrations/driver-exec.ts and packages/platform-objects/src/system/migration-flag.ts yielded no anchor. Searched every exported name of each across content/docs and docs:

  • driver-exec.tsdriverExecRefusal, resolveDriverExec, driverCanRunSql, DriverExec: zero pages each. Its documented surface is empty, and this PR's change to it is one doc-comment paragraph.
  • migration-flag.tsattestFreshDatastore, readDataMigrationFlag, isDataMigrationVerified, recordDataMigrationRun, CREATION_ATTESTATION_DETAIL, MigrationFlagEngine, plus the prose terms creation-attested and created-empty: two hits, both in docs/adr/0104-… — a file#symbol pointer to migration-flag.ts#attestFreshDatastore (the function still exists, same name, same file) and the phrase "every datastore creation-attested since 17.0" in a section about media-column encoding. Neither states the attested set's cardinality nor names adr-0030-notification-event (grep for both: zero). Not falsified.

Positive control for the hand search: sys_migration returns 8 pages, so the search reaches the docs tree. Of those, deployment/seed-tenancy-repair.mdx:126 is the seed-tenancy repair's own row (a different id, untouched) and references/system/migration.mdx is AUTO-GEN, current — check:docs is green inside check:generated.

⭐ The input-vs-emitter blind spot, searched as a prose class

The bot cannot see a page that says "the platform migrates your old notification rows" without naming a symbol. Swept content/docs and docs for that claim class in both orders (migration verb near a notification-row noun, and the reverse), with a positive control that fires on the two documents known to carry the claim (docs/handoff/adr-0030-… and docs/adr/0030-…). Three hits outside those:

  • references/automation/schemaless-node-config.mdx:159 — about script.config.template and sys_notification_template. Unrelated, and an AUTO-GEN references page.
  • concepts/north-star.mdx:129 — the live read-state model, judged above.
  • docs/design/notification-platform-convergence.mda page the anchor-based run never listed, because it names no symbol of mine. Its §5 "Open decisions (ADR-0030 recommends; confirm before P0)" recommends "sys_notification rename/re-model in place + migration", and §6 Risks says P0 "migrates a live, UI-depended object". Judged historical, not falsified, not edited: it is a dated build spec whose own status block (2026-06-01) declares P0 shipped and routes the reader to ADR-0030 and the handoff doc for current status — and the handoff doc is the one this PR corrects. Editing a superseded plan record to reflect a later retirement is the accrete-a-row-per-PR pattern. Reported rather than actioned.

⚠️ The round caught a real gap in this PR — fixed

docs/handoff/adr-0030-notification-convergence.md was incompletely retired on the first pass and is now complete. The first edit tombstoned only the cut-over sequence's step 2; three claims above it still described the migration as live:

  • the ### Data migration (not auto-run) section naming the deleted file path and its export — now ### ⚰️ Data migration — RETIRED, there is none, carrying the reasoning, the unmeasured-deployment caveat and the reversal path;
  • the breaking-change callout instructing a reader to "Do the objectui cut-over and the data migration together" — now says there is no data migration to pair it with, and that the bell starts from what the new pipeline emits;
  • the Tests line counting "the migration" among the suites that are green — the migration's own suites went with the runner.

The runbook tombstone is now a pointer to that one full account, so the file states it once. grep -ni 'migrat' over the file returns only past-tense and tombstone lines. pnpm check:doc-authoring, node scripts/docs-audit/check-affected-docs.mjs, node scripts/docs-audit/check-drift-comment.mjs and pnpm check:nul-bytes all exit 0 after the change.

scripts/engine-double-contract.pinned.json −30 — the polarity, stated

⚠️ This ledger is grow-only COVERAGE, not a shrink-only ratchet — its own $comment says so: "This is the OPPOSITE polarity to engine-double-contract.baseline.json: that ledger records DEBT and may only shrink, this one records COVERAGE and may only grow. A row that disappears is a pinned double that left the population." So a decrease is the flagged direction here, and it is the direction this diff moves — 6 rows, 30 lines, 0 added.

It is legitimate and it is the path the gate itself prescribes. The gate refused first, naming each row and saying: "A deleted test is a LEGITIMATE decrease: run node scripts/check-engine-double-contract.mjs --write and commit the ledger. There is no number to choose and no judgement to make — the diff records which pin left." That is exactly what was done. Three rows leave with the deleted migrate-sys-notification-to-event.test.ts; three leave because real-driver-exec-surface.test.ts no longer declares an engine double at all — its recordingEngine() existed solely for the one helper that needed an IDataEngine, and that helper is gone, which is the "genuinely and intentionally gone" case the gate's text reserves for --write. ⛔ Not a baseline relaxation: no floor moved and no threshold was edited. The gate re-runs green at pinnedRows 774 against a floor of 600, and its provenance line reports the population moving in both directions across the tree, which it states is "information, not a verdict — only the floors decide."

验收备注

Open question for the review tier

Should NOTIFICATION_EVENT_MIGRATION_ID itself be deleted? This PR keeps it, on the ruling's own wording: it enumerates "the spec docblock" and "the id's membership" as two separate removals, which would both be implied by — and so redundant with — deleting the constant, and the acceptance conditions likewise count six artefacts with the constant not among them. Keeping it also keeps a name for the sys_migration rows fresh stores were already attested with. The counter-argument is Prime Directive #10's corollary: a registered migration id whose migration cannot run is a declaration the runtime does not deliver. Reversing this is a two-line change (drop the constant, drop the it('the id itself is KEPT …') case) if the tier reads the ruling the other way.


Generated by Claude Code

…ation

`migrateSysNotificationToEvent` had no way to be run: zero production callers
anywhere in `packages` / `apps` / `examples`, and no `os migrate` sub-command,
while the two sibling members of `CREATION_ATTESTED_MIGRATION_IDS` had both.

Removed together: the runner and its three result/option/receipt types, its
barrel export, its tests, the ruled `sys_migration` receipt-claim matrix and
that matrix's pin, the spec docblock stating it, and the id's membership in
`CREATION_ATTESTED_MIGRATION_IDS`. `NOTIFICATION_EVENT_MIGRATION_ID` is kept
as the name of rows already written under it; nothing writes or reads one now.

Both alternatives stay refused: an `os migrate notification-event` sub-command
is a permanent operator surface for a migration with no measured demand, and a
boot-time invoker is an unattended data rewrite. Pre-ADR-0030 `sys_notification`
rows are not carried by the platform on this line.

Two new retirement pins replace the withdrawn ledger pin, each asserting the two
ADR-0104 survivors by name beside the negative, so a correct removal is
distinguishable from a set that lost everything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata, @objectstack/platform-objects, @objectstack/spec, touching 40 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/metadata/src/migrations/driver-exec.ts, packages/platform-objects/src/system/migration-flag.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via sys_inbox_message (literal, a string literal in INBOX_OBJECT), sys_notification_receipt (literal, a string literal in RECEIPT_OBJECT))
  • content/docs/automation/hook-bodies.mdx (via sys_notification (literal, a string literal in EVENT_OBJECT))
  • content/docs/concepts/north-star.mdx (via sys_notification_receipt (literal, a string literal in RECEIPT_OBJECT))
  • content/docs/kernel/contracts/metadata-service.mdx (via getObject (symbol, a method of interface MigrationLedger), getObject (literal, a string literal in LEDGER_METHODS))
  • content/docs/permissions/authorization.mdx (via not_applicable (symbol, a field of const object LEDGER_CLAIM), not_applicable (literal, a string literal in SysNotificationMigrationResult; a string literal in runNotificationEventMigration))
  • content/docs/permissions/explain.mdx (via not_applicable (symbol, a field of const object LEDGER_CLAIM), not_applicable (literal, a string literal in SysNotificationMigrationResult; a string literal in runNotificationEventMigration))
  • content/docs/permissions/rls.mdx (via not_applicable (symbol, a field of const object LEDGER_CLAIM), not_applicable (literal, a string literal in SysNotificationMigrationResult; a string literal in runNotificationEventMigration))
  • content/docs/protocol/kernel/realtime-protocol.mdx (via recipient_id (literal, a string literal in LEGACY_COLUMNS; a string literal in runNotificationEventMigration))
  • content/docs/protocol/objectql/state-machine.mdx (via /object/:name/state/:field (route, bridged from symbol getObject — its route source's handler names it))

4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via sys_notification (literal, a string literal in EVENT_OBJECT), getLegalNextStates (sdk, the bare tail of client method meta.getLegalNextStates, bound to GET /api/v1/meta/object/:name/state/:field; the bare tail of client method meta.getLegalNextStates, bound to GET /meta/object/:name/state/:field), meta.getLegalNextStates (sdk, the route ledger binds it to GET /api/v1/meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field; the route ledger binds it to GET /meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field))
  • content/docs/releases/v17/17-1.mdx (via sys_inbox_message (literal, a string literal in INBOX_OBJECT))
  • content/docs/releases/v17/17-2.mdx (via getLegalNextStates (sdk, the bare tail of client method meta.getLegalNextStates, bound to GET /api/v1/meta/object/:name/state/:field; the bare tail of client method meta.getLegalNextStates, bound to GET /meta/object/:name/state/:field), meta.getLegalNextStates (sdk, the route ledger binds it to GET /api/v1/meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field; the route ledger binds it to GET /meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field))
  • content/docs/releases/v17/17-4.mdx (via CREATION_ATTESTED_MIGRATION_IDS (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/metadata/src/migrations/driver-exec.ts, packages/platform-objects/src/system/migration-flag.ts) — pages documenting those are invisible to this run
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c3756ff096f8bd4e437e36152779dafe0e7cf41apackageMentionDocs.

Which tree this was computed on

This run read content/docs from cd301e4d7e2f2a818acc2cf333790a1776c2cdff — the merge of head 12bc827ac47487b035c04248e884a476e98f0004 into base c3756ff096f8bd4e437e36152779dafe0e7cf41a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cd301e4d7e2f2a818acc2cf333790a1776c2cdff && git checkout cd301e4d7e2f2a818acc2cf333790a1776c2cdff
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c3756ff096f8bd4e437e36152779dafe0e7cf41a 12bc827ac47487b035c04248e884a476e98f0004 && git checkout -B drift-repro c3756ff096f8bd4e437e36152779dafe0e7cf41a && git merge --no-ff 12bc827ac47487b035c04248e884a476e98f0004

node scripts/docs-audit/affected-docs.mjs --json c3756ff096f8bd4e437e36152779dafe0e7cf41a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c3756ff096f8bd4e437e36152779dafe0e7cf41a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…cription too

The first pass tombstoned only the cut-over sequence's step 2. Three claims
above it still described the migration as live: the `### Data migration
(not auto-run)` section that names the deleted file and its export, the
breaking-change callout telling a reader to "do the objectui cut-over and the
data migration together", and the Tests line counting the migration's own
suites among the green ones.

Found by the docs-drift round on the PR. The runbook tombstone is now a
pointer to the one full account, so the file states it once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant