fix(spec): spec-changes.json's aggregate export diff declares the release pair it really spans - #19115
Conversation
…lly spans
`spec-changes.json`'s `aggregate.added`/`removed` are filled by a release-time
api-surface diff of this artifact against the previously PUBLISHED one, so they
span ONE RELEASE — under a record keyed `from: 10, to: 17`, with every entry
carrying only `since: 17` and `perMajor[16 -> 17].added` sitting at `0`. Nothing
in the file distinguished a minor's slice from the major-boundary delta.
Measured on the published `@objectstack/spec@17.4.0` Release asset: 225 added /
51 removed, set-identical to a recomputed 17.3.0 -> 17.4.0 diff of the two
tarballs' own `api-surface/` snapshots.
A record whose export arrays are non-empty now carries
`surfaceScope: { fromVersion, toVersion }`. The generator reads the previous
version off the previous artifact's own `package.json` and OMITS the arrays,
loudly, when it cannot; a non-empty unlabelled array is refused outright. The
publish gate recomputes the aggregate's claim from the same two tarballs and
refuses an absent, wrong or untrue scope in both directions.
Deliberately additive: `SpecChangesSchema` still ACCEPTS an unscoped diff,
because every manifest published so far carries one. The committed
registry-only projection and every `perMajor` record carry no new key at all —
the committed artifact moves on its `$comment` line and nowhere else.
Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…reed The gate now checks the aggregate record's export claim as well as the per-release section's, so a failure headline saying "the per-release section disagrees" sent the reader to the wrong half. Each problem line already names its own path (`release.added`, `aggregate.surfaceScope`, ...); the headline now says so. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
Clause-②: yes (widening) — one new optional key on a published artifact and one new optional schema field; the accept set is not narrowed and no existing key changes spelling or meaning. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 949af26a9e5820c8d330461da8a8eeef2dc398ff && git checkout 949af26a9e5820c8d330461da8a8eeef2dc398ff
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0f1cd83cbf42194bc2543002a94315c3b8c2523e f769234840756282e6bfbc1d683aebb443f91518 && git checkout -B drift-repro 0f1cd83cbf42194bc2543002a94315c3b8c2523e && git merge --no-ff f769234840756282e6bfbc1d683aebb443f91518
node scripts/docs-audit/affected-docs.mjs --json 0f1cd83cbf42194bc2543002a94315c3b8c2523e
|
|
Clause-② dual carrier hung by the dispatching seat. Seat: domain:spec#3 This PR's body declares
What happens next, and who does it
Two sequencing facts recorded here so the reviewer does not have to rediscover them
⛔ This comment writes no verdict about the diff: the carrier is a gate, not a judgement. Generated by Claude Code |
…r resolution `content/docs/upgrading.mdx` stated that the same file's `aggregate` and `perMajor` records "still answer the major-boundary question". That is true of `perMajor`, and of `aggregate.converted` / `aggregate.migrated`, which are derived from the ADR-0087 registries across the whole range. It was never true of `aggregate.added` / `aggregate.removed`: those are the same one-release export diff as the per-release section, and the page was the declared contract the artefact did not keep. The page now says so, names `surfaceScope` as the field that carries the pair, and restates the absent-is-not-zero rule the `release` section already carries. Measured free of open-PR holders before editing: 32 open PRs, 364 file rows, instrument lit (it names all four holders of the migrations registry). Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
|
Seat: domain:spec#3 A webhook delivered a
⇒ the artifact's signature is the one this board has measured repeatedly: an aggregator check whose members were cancelled reports What is NOT being done, and why
Generated by Claude Code |
Contract reviewServed-tier: 73/73 Isolated at-tier review of PR #19115 (card #18978). The diff was read as ① Derived judgmentsPublic surface — what a consumer of the published
Accept set — what is refused, before against after.
Derived verdict on the declaration: ② Semver level
③ Boundary flags
Not measured by this reviewer, stated rather than implied: the suite was not re-run locally (this container's checkout is stale relative to origin/main; the head's Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Clause-② carriers CLEARED on both sides — the at-tier record reads PASS. Seat: domain:spec#3 The record this clear rests on
What was writtenOne four-step label write per carrier, each read back at 2026-09-18T21:22Z:
⛔ Neither the implementing dev nor the reviewer wrote or cleared a label — the dev said so in its report and the reviewer was told not to. ⛔ This seat did not render the verdict: it commissioned an isolated at-tier reviewer precisely because it measured below Next on this PRGoverned predicate re-derived on the FINAL file list at 2026-09-18T21:22Z: 0 of 8 paths hit the register ⇒ NOT governed. CI on this head read 35 checks / 0 running / 0 non-green. ⇒ this seat flips it ready and arms the queue, and the landing judgement is then the queue's. Generated by Claude Code |
|
Addendum to the adopted record — the reviewer's hand-back arrived, and it QUALIFIES two things this seat published. Verdict unchanged: PASS. Seat: domain:spec#3 The reviewer's hand-back (which had not arrived when the carriers were cleared at 2026-09-18T21:22Z) is now in. ⛔ It retracts nothing and the PASS stands. What follows is the part the record does not carry, published as declared gaps rather than allowed to pass as coverage. ⛔ Correction to THIS SEAT's own wordingThe carrier-clear comment said the tier stamp was 「every harness
⇒ the ratio and the conclusion are unaffected; 「73 requests」 would have been wrong and is corrected here rather than left standing. Declared gaps — the reviewer ran NOTHING locally, and here is why⭐ This seat measured the reason, and the reviewer's substance holds while its date does not: the shared checkout's HEAD is So, stated rather than implied:
③ carries EIGHT flags, not three — and the hand-back adds three moreThe record's ③ lists 8 (the gate's
One item this seat carries to the maintainer rather than filing
⛔ Nothing here changes the landing: the PR is in the merge queue ( Generated by Claude Code |
Fixes #18978
Clause-②: yes (widening) — one new OPTIONAL key on a published artifact (
aggregate.surfaceScope) and one new optional field onSpecChangesSchema. Nothing is renamed, retired or reshaped; the schema still ACCEPTS a record without it. Contract-review tier.spec-changes.json'saggregate.added/aggregate.removedare filled by a release-time api-surface diff of the artifact being published against the previously published one, so they span one release — under a record keyed by protocol major (from: 10, to: 17), with every entry carrying onlysince: 17/removedIn: 17andperMajor[16 → 17].addedsitting at0beside it. Nothing in the file distinguished one minor's slice from the whole major-boundary delta.1 · The defect, re-measured on a real published artifact
Instrument:
curlthe Release asset through the REST API, then recompute the delta with a hand-written flattener in Python (not this repo's code) over the two published tarballs' ownapi-surface/shard directories.@objectstack/spec@17.4.0Release assetaggregate.addedsincecounter{17: 225}aggregate.removedremovedIncounter{17: 51}aggregate.from/aggregate.to10/17perMajor[16 → 17]added: 0, removed: 0(converted 57, migrated 77)releasesectionnpm pack17.3.0 vs 17.4.0api-surface/addedTrue,removedTrue; 0 only-in-asset, 0 only-in-recompute, both directions, both arraysSo the published arrays are, byte for byte, the 17.3.0 → 17.4.0 one-minor delta, wearing a
10 → 17label. Cross-check: PR #17080's own changeset states the same pair as "gained 225 exports and lost 51".One refinement to the card's premise, stated because it moves a date, not a verdict
@objectstack/spec@17.4.0npm tarballaggregate.added/removed0/0; noreleasesection@objectstack/spec@17.3.0npm tarballaggregate.added/removed0/0; noreleasesection2026-09-09T03:57:51.929Z8b4890343)2026-09-18T11:16:13+00:00⇒ no published tarball carries the mislabelled arrays yet. The lane that will is on
origin/maintoday:release.ymlrunsrelease-spec-changes.sh --prepare(line 1251) and--verify(1260) before the publish, then--attach(1355), and--prepareinvokes the generator with--previous-package. The card's "reach is new" premise therefore holds as a property of the lane, and the first tarball to carry it is the next publish. Today's carrier is the Release-page asset, measured above. This is a sharpening, not a disproof — nothing in the card's argument depends on a tarball already existing.2 · The A/B legs, re-taken
Base:
origin/mainat07c6f822e. Previous artifact:npm pack @objectstack/spec@17.3.0, unpacked. Both legs write the real snapshot path, so each was copied out and the tree restored bygit checkout HEAD -- packages/spec/spec-changes.jsonwith the blob hash re-read each time (9dbc98682…in,9dbc98682…out,git diff HEADempty,git status --porcelainempty).--previous-package PKG_DIRaggregate.added399 (sincecounter{17: 399}),aggregate.removed302 (removedIncounter{17: 302}),perMajor[16 → 17]0 / 0,release17.3.0 → 17.4.0 with 399 / 30243f4766889e— #18889's parent, verified 0 occurrences of the string--previous-packageon disk and 3 of--previous-surface— invoked with--previous-surfaceaggregate,perMajor,protocolVersion,supportFloor,migrateCommandall canonical-hash identical to leg A (aggregate=d8c3e5c4303c2eccon both)Whole-document diff between the two legs: the
releasekey (leg A only) and$comment(which #18889 extended). Nothing else. ⇒ the computation is pre-existing, exactly as the card claimed.One reading the card did not state, and it is the sharpest one: in leg A,
aggregate.added/aggregate.removedare set-identical torelease.added/release.removed. The aggregate record does not merely resemble a one-release slice — it is the release slice, under a major-resolution header.3 · ⭐ The consumer survey the card named as unmeasured
Question: who reads
aggregate.added/aggregate.removedtoday?Radius, declared
objectstack-ai/objectstack@origin/main07c6f822egit grep -Iover tracked and untracked files, whole tree, noheadanywhereobjectstack-ai/objectui@origin/main05a49f2ee(fetched for this survey)git grep -lI PATTERN origin/mainnpm pack17.3.0 and 17.4.0, pluspackages/spec/package.jsonfiles[]content/docs/upgrading.mdx,skills/objectstack-upgrade/SKILL.md(the published skill catalog),docs/adr/0087Outside the radius, named as outside it: the
objectstack-ai/cloudrepository (not checked out in this container); any third-party or private consumer of the npm artifact or of the Release-page asset; and thespec_changesMCP tool, which is prose only —git grep spec_changesover R1 returns docs, ADRs, changelogs and code comments and zero implementation, so there is nothing there to read anything.Instrument, in two stages
spec-changes.json, plus every site naming a key that is distinctive to this manifest (perMajor,supportFloor). Enumerable and small; each hit was then read.⭐ Lit controls, so a zero is a reading
spec-changes.json, found by stage 1git grep -n "spec-changes\.json"packages/cli/src/utils/spec-release-changes.ts:80, which readsdoc.releaseat line 106 — a real, shipping readergit grep -n perMajor/supportFloorskills/objectstack-upgrade/SKILL.md:219+ itsnode -esnippet at 229-236git grep -lI PATTERN origin/mainin../objectui@objectstack/spec→ 1628 files;api-surface(another published spec artifact) → 3 filesResult
aggregate.added/removed?packages/cli/src/utils/spec-release-changes.ts:106(ships in@objectstack/cli)doc.releaseand the lengths of its four arraysscripts/check-release-spec-changes.mjsaggregateIds()aggregate.converted[].conversionId,aggregate.migrated[].migrationId,release.*packages/spec/scripts/build-spec-changes.tspreviousRelease()(reads the PREVIOUS tarball)aggregate.converted[].conversionId,aggregate.migrated[].migrationIdscripts/check-adr-0087-registration.mjs(parser-rot witness)migrationIdoccurrencesskills/objectstack-upgrade/SKILL.md— published to customer projectsperMajor[].converted,perMajor[].migrated,protocolVersion,supportFloorcontent/docs/upgrading.mdx.release.*; and, for withdrawals only,.aggregate.converted[].conversionId/.aggregate.migrated[].migrationIdobjectuirepositoryspec-changes→ 0 files,perMajor→ 0,supportFloor→ 0,spec_changes→ 0spec_changesMCP toolscripts/regen-artifacts.mjs,check-regen-pending.mjs,objectui-changeset-digest.mjs,check-published-files.mjs,docs-audit/affected-docs.mjs⇒ Zero readers of
aggregate.added/aggregate.removedin the reachable radius. Every field-level reader ofaggregatereadsconverted/migratedonly. Confirming probes:git grep -nE "aggregate(\.|\[[\"'])(added|removed)"over R1 returns 0 rows; the loosened, case-insensitive variant returns 11 rows, all the English phrase "aggregate added to the spec" about SQL aggregate functions.But there is a declared contract, and it is the one the defect breaks.
content/docs/upgrading.mdx:338says, of this very field: "The same file'saggregateandperMajorrecords are unchanged and still answer the major-boundary question." They do not. That sentence is the class-(b) contract text — a machine-readable surface that does not say what it means — and it is what makes this a defect rather than an unused field.Why the survey licenses the shape taken
The dispatch allows two shapes: gate the aggregate arrays as
release.*is gated, or relabel them at the resolution they actually carry.Gate-only cannot be the whole fix here, and that is a measurement, not a preference: there is no computable "correct" 10 → 17 export delta to gate against, because tarballs before protocol 15 ship no
api-surfacesnapshot at all. A gate that merely refused today's shape would wedge every release until the producer changed — and the producer changing is the relabel. So the gate is not an alternative to the relabel; it is the negative control for it.And with zero readers, relabelling is free: nothing downstream can break, so the honest fix is available at no migration cost. That is what the survey buys.
⛔ Not taken, and reported instead: removing the fields, or ceasing to emit them. The survey lands exactly where the card guessed it might — nobody reads them — so the removal question is live, and it is the maintainer's. See
## Acceptance notes.4 · What changed
A record whose export arrays are non-empty now carries the version pair they were diffed between:
packages/spec/src/migrations/spec-changes.ts—SpecSurfaceScopeSchema+SpecSurfaceScope, an optionalsurfaceScopeonSpecChangesSchema,SurfaceDiff.scope, andsurfaceScopeProblem(record), which is the refusal. The record spreads the key in rather than assigningundefined, so a record with no export diff serialises exactly as before.packages/spec/scripts/build-spec-changes.ts— reads the previous version off the previous artifact's ownpackage.json(--previous-package PKG_DIR, or the sibling of a--previous-surfacesnapshot), OMITS the arrays loudly when it cannot, and refuses outright to write a non-empty unlabelled array.scripts/check-release-spec-changes.mjs—verifyAggregateSurface()recomputes the aggregate's claim from the same two tarballs the release section is checked against, and refuses an absent, mislabelled or untrue scope in both directions. Self-test roster 15 → 23 batteries. The failure headline now names which claim disagreed.packages/spec/src/migrations/spec-changes-surface-scope.test.ts— new.packages/spec/spec-changes.json(one line — its$comment) andpackages/spec/api-surface-declarations/root.txt(+6 / -0).⛔ Not narrowed on purpose.
SpecChangesSchemastill accepts an unscoped diff, because every manifest published so far carries one and a schema that refused them would narrow what an already-shipped artifact parses as. The refusal lives at the producer and at the publish gate.⛔
packages/spec/src/migrations/registry.tswas not touched (held by #19095, #19090, #19084, #18319). The change is additive, so it declares no ADR-0087 disposition and needs no migration entry:node scripts/check-adr-0087-registration.mjs --base origin/main→ "this PR adds no declared-breaking changeset".scripts/regen-artifacts.mjs(held by #19024) andcontent/docs/releases/**were not touched either. The public entry barrelpackages/spec/src/migrations/index.tswas deliberately left alone, which is whycheck:api-surfaceis green with no export-name churn.5 · ⭐ Acceptance controls
Control 1 — a test that fails on today's composition (acceptance 1)
Ablation via
node scripts/ablation-replace.mjs, which proves the mutation reached disk before running anything:The reported failure is the real one:
expected 'the 10 → 17 record carries 2 added and 1 removed export(s) with no surfaceScope…' to be null. Unablated: 7 / 7 pass. No ablation artefact remains — restore proved by blob equality withHEADand an emptygit diff HEAD, not by an exit code.tail, so the wrapper printedcommand exited 0while the suite had failed. The run above redirects first and captures$?before any pipe. Only the second reading is cited.Control 2 — ⭐ preserved truth (acceptance 2), shown rather than asserted
Same generator invocation, same real 17.3.0 tarball, before the fix and after; every record compared by canonical JSON:
And on the committed artifact, per-key against
HEAD:aggregateunchanged,perMajorunchanged,protocolVersionunchanged,supportFloorunchanged,migrateCommandunchanged,$commentchanged — a one-line diff (1 insertion, 1 deletion). The per-release section #18889 added is untouched in both readings, andcomposeReleaseChangesstill returns exactly its six keys (pinned in the new test).Two further preserved-truth readings: all 15 pre-existing gate self-test batteries still pass unchanged, and
pnpm --filter @objectstack/spec check:generatedreports "All 16 generated artifacts are up to date".Control 3 — ⭐ a negative control that distinguishes fixed from switched off (acceptance 3)
The gate run against four constructed publish trees, each carrying the real committed
api-surface/and a realpackage.json, with the real unpacked 17.3.0 tarball as--previous:goodbad-prefixmain's generator produces todaybad-unscopedsurfaceScopedeletedbad-wrongscopesurfaceScope.fromVersionset to17.2.0The
bad-prefixrow is the load-bearing one: the new gate refuses the artifact today's code actually produces, so it is a check that can still fail rather than one that was switched off. Eight further refusals are pinned as self-test batteries (absent scope, wrongfromVersion, wrongtoVersion, an invented export, an omitted real removal, a claim the previous tarball could not have produced), each alongside two GREEN batteries — a matching scoped claim, and the unscoped-empty registry-only projection that must stay accepted.The producer half, both directions:
Control 4 — the card's own numbers, re-measured after the change (acceptance 4)
Instrument: HEAD generator,
--previous-packagepointed at the unpacked published 17.3.0 tarball; counters computed bycollections.Counterover the emitted JSON.aggregate.from/to10/17(unchanged — it still answers the major question forconverted/migrated)aggregate.addedsincecounter{17: 399}aggregate.removedremovedIncounter{17: 302}aggregate.surfaceScope{fromVersion: 17.3.0, toVersion: 17.4.0}← new; this is the fixperMajor[16 → 17]added: 0, removed: 0(unchanged, and now honest by construction: the record says nothing about exports)release6 · Verification
pnpm --filter @objectstack/spec build(forced fresh, under the shared verify lock)VERDICT command-exit 0;check-dts-emitted: 34/34pnpm --filter @objectstack/spec typecheck && … test(under the lock)VERDICT command-exit 0— 495 test files, 14527 tests, all passingnode scripts/check-release-spec-changes.mjs --self-testpnpm --filter @objectstack/spec check:generatedpnpm lint(full repo union, at final commit0c548868c)--format jsoncounts)pnpm check:nul-bytes@objectstack/cliunit tier,src/utils/spec-release-changes.test.tsscripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack)pnpm check:type-check-debt--re-measureneeds the whole workspace build closure on disk and onlypackages/specwas built. Its own words: "This is NOT a pass and NOT a finding". Its non-re-measure invariants reported 0 findings on all three layers. Left to CI, which builds the closure first.Downstream reach, with a lit control:
git grep -nE "\b(SpecChangesSchema|SurfaceDiff|SpecSurfaceAdd|SpecSurfaceRemove)\b"outsidepackages/specreturns 0 rows; the same instrument findscomposeMigrationChain(a sibling export of the same module directory) inpackages/cli/src/commands/migrate/meta.ts. ⇒ no package outsidepackages/specnames any changed declaration, so no other package's tests are owed. Export names are unchanged (check:api-surfacegreen); only declaration text moved (api-surface-declarations, +6 / -0).Acceptance notes
aggregate.added/aggregate.removedin the whole reachable radius. The card itself floats "if the answer is nobody, the cheapest honest fix may be to stop emitting it". It was ⛔ not implemented here — removing a published machine-readable capability is a maintainer decision — and this PR makes the surface honest instead, which is strictly compatible with a later removal. Recorded as an open question.content/docs/upgrading.mdxis corrected here, not merely reported. Line 338 said 'The same file'saggregateandperMajorrecords are unchanged and still answer the major-boundary question'. That is true ofperMajor, and ofaggregate.converted/aggregate.migrated, which are registry-derived across the whole range — and it was never true ofaggregate.added/aggregate.removed. The page was the declared contract this artefact did not keep, so correcting it is the doc half of this defect rather than opportunistic cleanup. The path was measured FREE of open-PR holders first (32 open PRs, 364 file rows, instrument lit by all four holders of the migrations registry).surfaceScopeis accepted, because that is the honest registry-only projection. Turning "must not lie" into "must speak" would be a new publish requirement, and that call is not this gate's. The residual hole is narrow: a bug that silently emptiedaggregate.addedwhilerelease.addedstayed correct would pass. Worth a card if the maintainer wants the stronger rule.--previous-surfacehas no caller left in the repository.git grep -- "--previous-surface"finds only the generator's own argv parsing and its docblock; every lane uses--previous-package(scripts/release-spec-changes.sh:87). It was kept working — and taught to derive its scope from the snapshot's siblingpackage.json— rather than retired, because retiring a flag is not this card.cut-rc.ymlattaches, and never prepares. It callsbash scripts/release-spec-changes.shwith no mode, which defaults to--attach, so the RC lane uploads the committed registry-only manifest and never runs--verify. Not a defect (the committed copy claims nothing), and not this card — noted because it is the one lane the new gate never sees.Clause-②: yesmeans it and [finding] spec-changes.json aggregate.added/removed carry a one-release slice labelled at major resolution — pre-existing, but #18889 moves it from the Release asset into the npm tarball #18978 oweneeds:contract-review; that label is the seat's to apply and ⛔ never this branch's to clear.packages/spec/spec-changes.jsonbypnpm --filter @objectstack/spec gen:spec-changes;packages/spec/api-surface-declarations/root.txtbypnpm --filter @objectstack/spec gen:api-surface-declarations. Both were named stale bypnpm --filter @objectstack/spec check:generatedfirst, and only those two were regenerated (--fixis deliberately narrow). Noorigin/mainmerge was performed on this branch, so themerge=os-regensilent-resolution hazard on that path was never entered.content/docs/api/client-sdk.mdxandcontent/docs/kernel/contracts/metadata-service.mdxare still accurate: both were anchored by a NAME COLLISION on the generic identifiersfromVersion/toVersionbetween this PR's new published-version STRINGS and the REST metadata-history routes' INTEGER version parameters (rest-server.ts:8209readsbody.toVersionforPOST /meta/:type/:name/rollback;client-sdk.mdx:229-230spells the SDK keysfrom/to;metadata-service.mdx:87declaresversion: number). Neither page mentionsspec-changesat all.content/docs/upgrading.mdxis the one genuinely-mine row and is corrected in this PR. ⛔content/docs/releases/v17/17-1.mdxis release-owned and was not edited — it is also not wrong: the same collision put it there, its only mention of the route is line 294 in a security context, and it never namesspec-changes.api-surface-declarations/root.txtandspec-changes.jsonyielded no anchor, so pages documenting those are outside its run — andspec-changes.jsonis this card's subject. A full read ofcontent/docs/**,docs/**andskills/**finds exactly one page stating a claim about the aggregate export arrays' resolution:upgrading.mdx:338, corrected here. The publishedskills/objectstack-upgrade/SKILL.mdpoints only atperMajor[].converted/perMajor[].migrated/protocolVersion/supportFloor— all unaffected and all still true.content/docs/releases/v15.mdx:521-523claims only that the file is generated, ships and attaches: still accurate.docs/adr/0087:210-213states no falsehood (its 'compose' claim is about the registry-derived arrays), though it is where the ambiguity originates — a governed-surface question, left to the maintainer.Seat: domain:spec#3,session_019srGWGCBBCBHqcDoRZpQRh) at 2026-09-18T21:06Z, from the implementing dev's final report. The dev correctly refused to PATCH this body:.claude/agents/os-dev.md:56says the PR body is written once, on the call that opens the PR, and later corrections are named in the report for the seat to write — and:184makes that clause govern over any dispatch word. ⛔ Nothing else in this body was touched, and ⛔ no verdict about the diff is written here: the clause-② review is an isolated at-tier reviewer's, andneeds:contract-reviewstays on both carriers until it lands.Generated by Claude Code
Generated by Claude Code