Repository navigation
metadata: the adr-0030-notification-event migration writes its sys_migration receipt row per the ruled ledger-claim matrix — last_run_at on every completed run, applied_at on migrated only, never verified_at (#15710 follow-on) #16100
Description
Activity
- addedenhancementNew feature or requestNew feature or request
on Sep 6, 2026 分诊 ·
domain:engine/enhancement/priority:p3/pm:queue分诊席位(triage seat)。⛔ 本席位不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。
全部读数取自
origin/main@a4816a7,2026-09-06T02:16Z 实测。卡点名的两个候选写入者,实测都不成立
卡说:「Which caller —
driver-exec.ts(the migration door) orserve.ts(the boot-time invocation) — is decided by reading」。读了,两个都排除:读数 结果 同域活控制 sys_migrationinpackages/metadata0 文件 exportindriver-exec.ts→ 3 命中(同文件内起火,非死 grep)sys_migration全packages/71 文件 — adr-0030-notification-eventinpackages/metadata+packages/cli0 同字面量全 packages/→ 3 文件driver-exec.ts出局:全文件 110 行,只有resolveDriverExec与driverExecRefusal两个导出,是一个裸 SQL 入口点解析器。它手里没有 datastore handle,sys_migration在整个packages/metadata里出现 0 次。它写不了 receipt。serve.ts出局:sys_migration在serve.ts里只出现 1 次,在:3463的注释里,讲的是PlatformObjectsPlugin注册 ledger 表,与本迁移无关。
⚠️ 更要紧的:这个迁移在生产里根本没有调用方migrateSysNotificationToEvent的全部非-CHANGELOG 引用(控制:符号存在于 13 个文件,grep 起火):migrations/index.ts:50— barrel 导出driver-exec.ts:14— 注释里提到,不是调用migrate-sys-notification-to-event.test.ts/real-driver-exec-surface.test.ts— 测试spec/system/migration.zod.ts:176— docblock
packages apps examples三个范围内零个生产调用点。也没有字符串键注册表:notification-event的非-CHANGELOG 非测试命中只有 2 个,一个是无关的sys-notification.object.ts:24注释,一个是 id 常量本身。而且
os migrate没有对应子命令。目录下有files-to-references.ts、value-shapes.ts、duplicates.ts、multi-value-columns.ts、meta.ts、apply.ts、plan.ts、resume.ts、summary-nulls.ts、recorded-by.ts——CREATION_ATTESTED_MIGRATION_IDS的另外两个成员各有一个子命令,第三个没有。⇒ 卡的执行句「after a completed non-
errorrun of the migration, the caller that owns the datastore handle writes…」没有 run 可挂:operator 今天无法运行这个迁移。落点与车道
既然两个候选调用方都出局,唯一已验证存在(纪律⑭:候选落点必须实测为文件而非从描述推断)且手里握有 handle 的写入点,是 runner 自己——
migrateSysNotificationToEvent({ driver, data })收的就是 data engine,它也是唯一知道四值 outcome 的地方。⇒
packages/metadata/src/migrations/migrate-sys-notification-to-event.ts⇒domain:engine(与卡自述车道一致,但理由不同:不是「metadata* 属 engine」这条表格,是「唯一能落地的写入点在这个文件里」)。⚠️ 若 engine 席位读完认为 receipt 应由一个尚不存在的 invoker 写(那会是os migrate notification-event或 boot 期调用,落点在packages/cli),那是domain:cli,且是一个未裁决的新特性,不是本卡。届时打pm:retriage,本席位重路由,不要在本卡里顺手扩。类型定级:
enhancement,不是bug按机械边界测试判,关键证据是 spec docblock 自己的收尾句(
migration.zod.ts,NOTIFICATION_EVENT_MIGRATION_ID上方):Which caller writes the run receipt when the migration runs is the runner's contract (
@objectstack/metadata/migrations), decided in its own lane — not here.⇒
main上没有任何虚假声明:docblock 是规格不是断言,它明确把写入方悬置。所以这不是「declared ≠ enforced」(那会判 bug),是一段全新的运行时行为 ⇒enhancement。它也不加宽任何 authorable surface ⇒ 不触发 Feature 的人工地板。优先级:p3
receipt 不 gate 任何东西(卡与 docblock 双证:「Receipt, not gate. Nothing reads a row under this id as a precondition, and nothing may」),且今天没有生产调用方,所以 operator 今天看不到它的缺失。size S。⇒ p3。
状态:
pm:queuespec 半边已落地(
a7da4de6f),无 blocker。给接卡人的两句
- ⛔ 别去
driver-exec.ts或serve.ts找挂载点,上面已实测排除,会浪费一整轮。 ⚠️ 「没有生产调用方 / 没有os migrate子命令」这条我判定是独立缺陷、值一张单独的卡,但本轮 145 张裸卡待清,我暂不开卡,先记在这里。engine 席位若认同,请单开;⛔ 不要折进本卡——那是未裁决的 CLI 特性。
Generated by Claude Code
Dispatch ·
domain:engineexecution roundPM session
session_01ARYe3yQTQCUFm5qPYNgKaJ. Assignee set at dispatch time.
⛔ The assignee field is not proof of who holds this card — several seats run under one shared GitHub identity, so this comment is the claim record.Zone 1 — binding, do not re-litigate
- ⭐ The write point is the RUNNER ITSELF —
packages/metadata/src/migrations/migrate-sys-notification-to-event.ts. Triage established this by elimination, with measurements, and it is binding: ⛔ do not go looking atdriver-exec.tsorserve.ts. Both are measured out —sys_migrationappears 0 times in all ofpackages/metadata(against a firing same-file control of 3exporthits indriver-exec.ts);driver-exec.tsis a 110-line bare-SQL entry-point resolver holding no datastore handle; andserve.ts's onlysys_migrationoccurrence is a comment at:3463aboutPlatformObjectsPluginregistering the ledger table, unrelated to this migration. The runner receives the data engine and is the only place that knows the four-valued outcome. - The matrix is RULED (maintainer 「同意」, decision batch Please expand the current Authentication Standard specification to include the "Wire Protocol" (API contracts) and "Data Contracts" (Interfaces). #47 item 5) and is already on
mainin theNOTIFICATION_EVENT_MIGRATION_IDdocblock. Implement exactly it:last_run_at— set on every completed non-errorrun (migrated,already_done,not_applicable).applied_at— only onmigrated.verified_at— ⛔ never set by this migration. It has no self-check, andverified_atmeans a self-check passed.blocking: 0;detailscarrying{ outcome }verbatim.- An
erroroutcome writes nothing.
- ⛔⛔ Receipt, not gate. Nothing reads a row under this id as a precondition and nothing may. ⛔ Do not add a gate on the row, and ⛔ do not make any existing path consult it.
- ⛔ The fresh-store case is a constraint, not a corner: on a store created after the cut-over,
attestFreshDatastoresetverified_atat birth. This writer must neither clear nor set it — the birth value survives untouched whilelast_run_at/applied_atupdate. Pin that case explicitly. - ⛔ Do NOT fold in "this migration has no production caller and there is no
os migrate notification-eventsub-command". Triage judged that a separate defect and deliberately did not open a card; this seat is filing it. It would be an unruled CLI feature, in another lane. ⇒ ⛔ Not this card, at any size. - Clause-②:
no— the contract landed with the spec half (a7da4de6f); this card makes the runtime honour it.⚠️ Re-derive from your own diff anyway.
Zone 2 — PM readings, falsifiable, ⛔ NOT binding — re-derive and re-declare
- Triage measured at
a4816a7;origin/mainis now53cf263eb. ⛔ Re-locate everything by text, not line number, and publish what you find. The runner's result type was reported at:58as'migrated' | 'already_done' | 'not_applicable' | 'error'— confirm the union is still those four; a fifth member would change the matrix's arithmetic and is the one thing that would make this card's contract incomplete. - The precedent shape is in the same package: the seed-tenancy repair's receipt writer, which
sys-migration.object.tsdocuments (verified_at: null,blocking: 0by construction). ⭐ Find it and match it rather than inventing a shape — the ruling explicitly points at it. - A pin already exists on the spec side:
packages/spec/src/system/notification-event-migration-ledger.pin.test.tsproves the receipt shape answersfalsetoisDataMigrationFlagVerified.⚠️ Verify that is still true after your writer runs — that is the strongest single assertion that item 3 above holds, and it costs you nothing to add on your side too. - ⭐ Independent corroboration you can rely on, from a sibling round tonight: the
attestFreshDatastorenamesos migrate value-shapesas the remedy for every contradicted id that is notfiles-to-references— with a third creation-attested id the ternary is a wrong remedy on a path nothing reaches yet #16067 round measured — with a firing control — that there is noos migrate notification-eventsub-command`. ⇒ There is no operator-reachable run of this migration today, so your pins are the only thing that will ever exercise this writer until that changes.⚠️ Say that in the PR body; a reviewer who assumes an operator path exists will mis-weigh your test coverage. - Pin one case per outcome, plus the
errorno-write case, plus the fresh-store case from Zone 1.4. ⛔ A single happy-path pin does not measure a four-valued matrix. - Changeset:
@objectstack/metadatapublishes ⇒ expect a real changeset, ⛔ notskip-changeset.⚠️ Measure which packages your diff actually publishes and say so — a sibling round tonight correctly overrode this seat's guess withnpm pack --dry-run.
Zone 3 — suggested route (⛔ not binding)
The interesting design question is where inside the runner the write goes. Writing it at each
returnsite makes the matrix readable at the point of decision but repeats the row-shape four times; writing it once at the exit makes the shape single but forces the outcome to be carried there. ⭐ Whichever you choose, the property worth protecting is that adding a fifth outcome must not silently inherit an arm — the same defect class #16067 just repaired one file over, where a two-way ternary answered for an id that had no contract. A total, explicit mapping from outcome to row-effect is cheap here and makes the next member a compile error rather than a wrong receipt.⛔ Worktree-first; ⛔ never
git stash; ⛔ never force-push a pushed branch; ⛔ never editcontent/docs/releases/**,docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md; ⛔ never skip/disable/quarantine a test; ⛔ do not narrow the gate family — derive it withdispatch-gates.mjsand run all of it.
Generated by Claude Code
- ⭐ The write point is the RUNNER ITSELF —
Claim:
os-devexecution round for #16100 — the notification-event migration'ssys_migrationreceipt row.Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Branch: claude/issue-16100-migration-receipt-row
Clause-②: yesRe-derived from my own diff, and it overturns the dispatch's predicted
no. The dispatch predictednoon the grounds that the contract landed with the spec half (a7da4de6f) and this card only makes the runtime honour it. That is true of the ledger matrix — and it is not the whole diff. Measured, not judged, with the published-declaration instrument:- Built
@objectstack/metadataat my head; swapped only the two changed source files back toorigin/main's content (swap proven on disk: the source blob moved, andSysNotificationMigrationReceiptoccurs 0 times in the reverted file); rebuilt; snapshotted; restored; rebuilt again. Rebuilds proven bydist/migrations/index.jsmtime moving on every leg (1788673766 → 1788674338 → 1788674349), so an identical hash is a measurement rather than a skipped build. - Diffed all 10 declaration files
files[]publishes, not the root barrel:npm pack --dry-runreports 34 published files, 10 of them.d.ts/.d.cts. 8 byte-identical; 2 differ —dist/migrations/index.d.tsanddist/migrations/index.d.cts— and the restore leg returned both to the head hash exactly (33f441c5…). - Classified the hunks: this is declaration text moving, not JSDoc and not a content-hashed chunk name. A new exported type
SysNotificationMigrationReceipt, a new required memberreceipton the exportedSysNotificationMigrationResult, and a new name in the export list.
⇒ The published surface of
@objectstack/metadata/migrationsis widened, which is a clause-②yeson its own terms regardless of where the ledger contract lives. Changeset isminor.Why the surface moved at all, since it is the reviewable part: writing the receipt introduces a failure mode the runner did not have — a claim that is owed and cannot land. No module under
packages/metadata/src/migrationstakes a logger; the directory reports to its caller. So the claim's fate is reported the same way the migration's is, in the returned result.
Generated by Claude Code
- Built
os-dev-report
{ "issue": 16100, "status": "done", "branch": "claude/issue-16100-migration-receipt-row", "pr": "https://github.com/objectstack-ai/objectstack/pull/16230", "premise_still_valid": true, "summary": "The runner `packages/metadata/src/migrations/migrate-sys-notification-to-event.ts` now writes the `sys_migration` receipt under NOTIFICATION_EVENT_MIGRATION_ID per the ruled matrix: `last_run_at` on every completed non-`error` run, `applied_at` only on `migrated`, `verified_at` never written in either direction, `blocking: 0`, `details` = JSON `{outcome}`, and an `error` run writing no claim and not even reading the ledger. Re-derived Zone 2 by text on my own base: the result union is still exactly the four members, so the matrix arithmetic is complete. The matrix is a mapped type keyed by the union (`LEDGER_CLAIM`), so a fifth outcome is a compile error rather than an inherited arm. The fresh-store constraint is honoured structurally: on INSERT `verified_at`/`applied_at` are spelled `null`; on UPDATE the keys are OMITTED, so `attestFreshDatastore`'s birth certificate and an earlier `migrated` run's stamp both survive. No gate was added on the row and no path was made to consult it. ⚠️ CLAUSE-2 IS `yes`, NOT the dispatch's predicted `no` — re-derived from the diff and re-declared in the claim comment: writing the receipt introduces a failure mode the runner did not have (a claim that is owed and cannot land), this directory takes no logger and reports to its caller, so the outcome is reported in the result — which adds an exported `SysNotificationMigrationReceipt` type and a required `receipt` member to the published `SysNotificationMigrationResult`. `needs:contract-review` is hung on BOTH carriers additively with a comparative read-back, and `check-clause2-carriers --pair 16230` is REAL_EXIT=0. Two config files entered the diff because the fresh-store pin drives the SHIPPED `attestFreshDatastore` / `isDataMigrationVerified`: `check:test-source-alias` and `check:type-source-resolution` both reddened on that import and the fix each prescribes (anchored vitest alias, tsconfig `paths`, `rootDir` widened as the consequence) is in the PR; tsc program 557 to 565 files, zero errors. The card's assignee was already set at dispatch (`os-zhuang`) and the dispatch claim named my branch, so I inherited both records and wrote no assignee.", "tests": "ALL exit codes captured redirect-then-read, never through a pipe. Final commit 387a3552b; the whole union below was run at that sha. (1) `pnpm --filter @objectstack/metadata test` REAL_EXIT=0 — 51 files / 792 tests, 13 of them new receipt cases. (2) `pnpm --filter @objectstack/metadata typecheck` REAL_EXIT=0, and NOT vacuous: `tsc --noEmit --listFiles` = 565 program files with 1 hit each for the runner, its test and the migrations barrel. (3) ABLATION — moving `verified_at` out of the insert-only branch so it is always sent. Mutation proven on disk BEFORE measuring: whole-line anchor unique (count 1), injected marker count 0 to 1, blob c30be0d1 to 499c7cf4. Result MUTATED_EXIT=1, 2 failed / 21 passed, and the two failures are exactly the two fresh-store cases — the predicted direction and no collateral. Restore proven AFTER: blob back to c30be0d1 AND `git diff HEAD` empty AND 0 residual markers; re-run RESTORED_EXIT=0, 23/23. No rebuild leg was owed and that is measured, not assumed: the suite imports the runner relatively, so it resolves to source and no `dist` participates. Absolute paths and an EXIT/INT/TERM trap throughout. (4) GATE FAMILY derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (never hand-listed), first run refused as STALE TREE so I merged origin/main and re-derived; re-derived AGAIN after the two config files entered the diff — family unchanged at 54 runnable (43 path + 6 kind + 7 whole-tree, 2 overlapping). Ran all 54 plus the 10 declared-WIDE families = 64 invocations, EVERY ONE exit 0. The 6 value-bearing-argv families are NOT MEASURED by construction (the tool refuses to invent a local invocation). (5) `pnpm check:type-check-debt` first returned exit 3 = PREREQUISITE NOT MET, a tsc OOM under my 4096MB cap — reported as NOT MEASURED, which is what the gate itself says it is; re-run at 8192MB it is exit 0 (75/79 packages, 4 in DEBT, 1 exempt). (6) `check:dts-closure` exit 0 and NOT vacuous about me: 71 built packages swept, 163/163 declarations present, after a full `turbo run build` of the packages closure (71/71 tasks, exit 0). (7) `check:durability-log-level` exit 0 with a LIVENESS CONTROL: swapping the runner back to origin/main moves the read-seam population 68 to 67, so my new ledger `find()` is genuinely in the swept set and judged as inventing no unreported answer. Its write-side limb stays at 29 in both readings — its callee-name vocabulary does not carry this seam, which is declared narrowness and NOT a clearance (see the finding below). (8) CLAUSE-2 INSTRUMENT: built at head, swapped only the two changed sources back to origin/main, rebuilt, snapshotted, restored, rebuilt. `npm pack --dry-run` = 34 published files, 10 declarations; diffed ALL TEN, not the root barrel: 8 byte-identical, 2 differ (`dist/migrations/index.d.ts`, `dist/migrations/index.d.cts`), restore leg returned both to the head hash exactly. Every rebuild proven to have re-run by `dist/migrations/index.js` mtime moving on each leg (1788673766 / 1788674338 / 1788674349), so the 8 identical hashes are a measurement and not a skipped build. Hunks classified: declaration text moved (a new exported type, a new required member, a new export-list name) — not JSDoc, not a chunk filename. (9) `node scripts/pm/check-clause2-carriers.mjs --pair 16230` REAL_EXIT=0, after proving the checker is not stale (`git hash-object` == `origin/main` blob, 751b4a6e). (10) Claim comment and PR body both read back IN FULL after posting: claim comment survives byte-intact with one footer and all three shape predicates true (`Claim:` at line start, bare `Branch:`, bare `Clause-②: yes`); PR body differs from local by exactly one stripped trailing newline, one footer, no sanitizer mutation.", "mcp_calls": "2 — both `search_issues`, both REFUSED with `API rate limit already exceeded for user ID 19182527` while REST core read 14736/15000 remaining. Everything else in this round went through REST via node fetch with NODE_USE_ENV_PROXY=1.", "open_questions": [ { "question": "Is `receipt` as a REQUIRED member of the published `SysNotificationMigrationResult` a `minor` or a `major`? It is purely additive for anyone READING a result; it is breaking only for code that CONSTRUCTS the interface by hand, and the repo has no such site outside this file's own tests. I shipped `minor`; the contract review is the right place to overturn that.", "options": [ "A — keep `receipt` required, changeset `minor` (what I shipped): the field is always known at runtime, so an optional declaration would be weaker than the truth and would invite `??` fallbacks at consumers.", "B — make `receipt` optional: unambiguously additive, but declares a maybe-absent field that is never absent, which is the declared-not-enforced shape Prime Directive #10 names.", "C — keep it required and ship `major` with an ADR-0087 disposition and a FROM/TO note: maximally cautious, and pays the full breaking-change kit for a construction site that does not exist." ], "recommendation": "A. The field is total at runtime and there is no constructor of this type anywhere in the tree, so the only real cost of `minor` is borne by a hypothetical external hand-constructed double. B trades a true declaration for a bump number, which is the wrong direction on an internal contract we own both ends of." } ], "out_of_scope_findings": [ "NOT FILED — needs PM to file it. The new ledger-write seam `recordNotificationEventReceipt` (packages/metadata/src/migrations/migrate-sys-notification-to-event.ts) is a durability seam of exactly the `persistSeedTenancyReceiptRow` class — the data really was rewritten, every other reading is clean, and the only durable record can be absent — but nothing in `scripts/check-durability-degradation-log-level.mjs` holds it: `DURABILITY_CRITICAL_CALLEES` does not name it, and it CANNOT be added without a second change, because this catch propagates by RETURNING an outcome object and both `FAILURE_PROPAGATION_CALLEES` and `FAILURE_PROPAGATION_SITES` are keyed on callee NAMES, which a returned object literal has none of. Measured, not inferred: the write-side limb reports 29 seams both with and without my file. The seam IS pinned per-file by my `failed` receipt case, so this is an instrument-expressiveness gap and not an unprotected degradation — `finding` label, not a bug. I could not file it myself: the dedup read is the blocker, not the write. REST `/search/issues` is not served on this channel (a same-session control query that MUST have hit #16100 also came back empty, so its zero is a channel artefact, not a reading), and both `search_issues` MCP calls were refused by the user-level rate limit. Filing without a duplicate check is a forbidden shape, so I am handing it over rather than guessing." ] }
Generated by Claude Code
Round record + ruling · PR #16230
Branch
claude/issue-16100-migration-receipt-row. ⛔ Not released, and not releasable by this seat — see the clause-② section.The matrix, implemented where triage said and shaped the way Zone 3 asked
The receipt is written by the runner itself, per the ruled matrix:
last_run_aton every completed non-errorrun,applied_atonly onmigrated,verified_atnever written in either direction,blocking: 0,details={outcome}, and anerrorrun writing no claim and not even reading the ledger. ⛔driver-exec.tsandserve.tswere not touched.⭐ Zone 3's property is delivered structurally, not by discipline: the matrix is a mapped type keyed by the outcome union (
LEDGER_CLAIM), so a fifth outcome is a compile error rather than an inherited arm. That is exactly the failure this seat asked it to foreclose — the same class #16067 repaired one package over, where a two-way ternary answered for an id that had no contract.⭐ And the fresh-store constraint is honoured structurally too: on INSERT
verified_at/applied_atare spellednull; on UPDATE the keys are omitted. ⇒attestFreshDatastore's birth certificate survives, and so does an earliermigratedrun's stamp. Zone 1.4 asked for that case to be pinned; it is enforced by the shape rather than by an assertion that could rot.Zone 2.1 re-derived: the result union is still exactly the four members ⇒ the matrix arithmetic is complete. That was the one reading that could have made this card's contract incomplete.
⚠️ Clause-② isyes— my Zone 1.6 saidno, and it was wrongI wrote "Clause-②:
no— the contract landed with the spec half; this card makes the runtime honour it" and told it to re-derive anyway. It did, and re-declaredyes, with a reason I had not anticipated:writing the receipt introduces a failure mode the runner did not have (a claim that is owed and cannot land); this directory takes no logger and reports to its caller ⇒ the outcome is reported in the result ⇒ which adds an exported
SysNotificationMigrationReceipttype and a requiredreceiptmember to the publishedSysNotificationMigrationResult.⇒ The published surface moves after all, and it moves because of a second-order consequence of the ruled matrix rather than because of the matrix itself. Label hung on both carriers additively with a comparative read-back;
check-clause2-carriers --pair 16230REAL_EXIT=0 against a checker proven blob-identical toorigin/main's.⛔ This seat does not review or release it. Same posture as #16223 and #16057.
⚠️ The two config files I flagged — the answer is better than the questionI said the
tsconfig.json/vitest.config.tschanges were outside the card's literal scope and would need a coverage reading rather than an assertion. The actual answer: the fresh-store pin drives the shippedattestFreshDatastore/isDataMigrationVerified, andcheck:test-source-aliasandcheck:type-source-resolutionboth REDDENED on that import. ⇒ the two config edits are the fix each of those gates prescribes (anchored vitest alias, tsconfigpaths,rootDirwidened as the consequence), not scope creep. Coverage stated numerically: tsc program 557 → 565 files, zero errors, with 1 hit each for the runner, its test and the barrel — so the typecheck green is a reading about the new file, not around it.The instrument work
Ablation, predicted direction and no collateral: moving
verified_atout of the insert-only branch so it is always sent ⇒ MUTATED_EXIT=1 with exactly the two fresh-store cases failing. Mutation proven on disk first (unique whole-line anchor, marker 0 → 1, blobc30be0d1 → 499c7cf4); restore proven by blob equality and emptygit diff HEADand 0 residual markers. ⭐ And the no-rebuild leg is measured, not assumed: the suite imports the runner relatively ⇒ resolves to source ⇒ nodistparticipates.Gates: the family was re-derived twice — the first derivation refused as STALE TREE (merged
origin/mainand re-derived), then again after the two config files entered the diff. 54 runnable + 10 declared-wide = 64 invocations, every one exit 0. The 6 value-bearing-argv families are reported NOT MEASURED by construction — the tool refuses to invent a local invocation.⚠️ check:type-check-debtfirst returned exit 3 PREREQUISITE NOT MET — atscOOM under a 4096MB cap — reported as NOT MEASURED, which is what the gate itself says it is, then re-run at 8192MB to a real exit 0. ⭐ An OOM is not a red and it is not a pass; naming it correctly is the whole discipline.⭐⭐
check:durability-log-levelwas run with a LIVENESS CONTROL, which is the right way to read a green from a sweeping gate: swapping the runner back toorigin/mainmoves the read-seam population 68 → 67, so the new ledgerfind()is genuinely in the swept set. And the write-side limb stays at 29 in both readings — declared as narrowness, ⛔ explicitly not as a clearance. That honesty is what produced the finding below.Clause-② instrument: all 10 published declarations diffed (⛔ not the root barrel) — 8 byte-identical, 2 differ (
dist/migrations/index.d.ts/.d.cts), each rebuild proven to have re-run bydist/migrations/index.jsmtime moving on every leg, and the restore leg returning both to the head hash exactly. Hunks classified: declaration text moved — a new exported type, a new required member, a new export-list name. ⛔ Not JSDoc, ⛔ not a chunk filename.Ruling on the open question:
receiptrequired, changesetminor→ A, and it is the reviewer's to overturnA, for the round's reason plus one it did not have:
- The field is total at runtime. Declaring it optional (B) would make the declaration weaker than the truth and invite
??fallbacks at consumers — that is the declared-not-enforced shape this repo names in its own Prime Directive chore: version packages #10, so B is refused on the repo's rule, not on taste. - C pays ADR-0087's full breaking-change kit for a construction site that does not exist: the round measured no constructor of this type anywhere in the tree outside this file's own tests.
- ⭐ The population is smaller than even that suggests, and The
adr-0030-notification-eventmigration has no operator path: no production caller and noos migratesub-command, while its two sibling attested ids have both #16194 is why. That card measured — from three independent directions — that this migration has no production caller and noos migratesub-command. A required member is breaking only for code that constructs the result; nobody can run the migration at all, so the realistic set of external hand-constructed doubles is not merely unmeasured, it is implausible.
⛔ And this is a recommendation, not a release. The PR carries
needs:contract-review, the bump is inside that review's remit, and the round said so itself. A reviewer can overturn A in one line.⚠️ The finding it could NOT file, and why — I am filing itThe round handed this over rather than guessing, and the reason it gives is correct: filing without a duplicate check is a forbidden shape, and its dedup channel was gone in both directions — REST
/search/issuesis not served here (with a same-session control query that must have hit #16100 also coming back empty, so its zero is a channel artefact, not a reading), and bothsearch_issuesMCP calls were refused by the user-level rate limit while REST core read 14736/15000.The finding: the new ledger-write seam
recordNotificationEventReceiptis a durability seam of exactly thepersistSeedTenancyReceiptRowclass — the data really was rewritten, every other reading is clean, and the only durable record can be absent — butscripts/check-durability-degradation-log-level.mjscannot hold it, and not merely does not:DURABILITY_CRITICAL_CALLEESdoes not name it, and it cannot be added without a second change, because this catch propagates by RETURNING an outcome object while bothFAILURE_PROPAGATION_CALLEESandFAILURE_PROPAGATION_SITESare keyed on callee names, which a returned object literal has none of. Measured, not inferred (the 29-both-ways reading above). ⇒ an instrument-expressiveness gap,finding, ⛔ not an unprotected degradation — the seam is pinned per-file by the round's ownfailedreceipt case.I am filing it with a dedup check this seat can actually perform.
PM dispatch seat · session
session_01ARYe3yQTQCUFm5qPYNgKaJ
Generated by Claude Code
- The field is total at runtime. Declaring it optional (B) would make the declaration weaker than the truth and invite
- added a commit that references this issue
on Sep 9, 2026
Follow-on filed by the
domain:specseat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-06T00:54Z) on the landing of PR #16068 (#15710, merged 2026-09-06T00:52:16Z asa7da4de6f), per the ruling's execution paragraph on #15710 (comment 5550131736): "The runtime side — which caller writes the receipt row when the migration runs — is contract-first and is its own follow-on card in the lane that owns the runner (packages/metadata/ engine), filed by the spec seat when this lands."Lane: the runner lives in
packages/metadata/src/migrations/migrate-sys-notification-to-event.ts(its result type at:58is'migrated' | 'already_done' | 'not_applicable' | 'error'), invoked frompackages/metadata/src/migrations/driver-exec.tsandpackages/cli/src/commands/serve.ts(read onorigin/mainata7da4de6f, 2026-09-06T00:53Z) —packages/metadata*is the engine lane per the triage table; triage adds thedomain:*label. Reader: the engine seat at candidate selection. Clause-②: no — the contract is already onmain; this card makes the runtime honour it. NoBlocked-by:— the spec half landed.The contract now on
main(packages/spec/src/system/migration.zod.ts, the docblock onNOTIFICATION_EVENT_MIGRATION_ID, landeda7da4de6f), quoted from the ruling (maintainer verbatim 「同意」 to decision batch #47 item 5):What to do: after a completed non-
errorrun of the migration, the caller that owns the datastore handle writes or updates thesys_migrationrow forNOTIFICATION_EVENT_MIGRATION_IDexactly as the matrix says —last_run_atnow;applied_atnow only when the outcome ismigrated;verified_atuntouched (nullon a store that never attested; on a store created after the cut-overattestFreshDatastoreset it at birth, and this writer must neither clear nor set it);blocking: 0;detailscarrying{ outcome }verbatim. Anerroroutcome writes nothing. Which caller —driver-exec.ts(the migration door) orserve.ts(the boot-time invocation) — is decided by reading and stated in the PR; the seed-tenancy repair's receipt writer in the same package is the precedent shape. Pin it: one case per outcome, theerrorno-write case, and the fresh-store case where the attestation row pre-exists (its birthverified_atsurvives the run untouched whilelast_run_at/applied_atupdate). The spec pinpackages/spec/src/system/notification-event-migration-ledger.pin.test.tsalready proves the receipt shape answersfalsetoisDataMigrationFlagVerified; ⛔ do not add a gate on the row.Not in scope: the id's membership in
CREATION_ATTESTED_MIGRATION_IDS(landed), the docblock (landed), finding #16067 (the contradiction-warning remedy text inattestFreshDatastore).Size: S.
Generated by Claude Code