Skip to content

fix(scripts): the console spec-injection probes are chosen with the bundle in view (#20646) - #20743

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20646-console-pin-probe
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20646-console-pin-probe

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20646

Clause-②: no

Console Pin Gate is red on main since fbec216e2d (PR #20695, the @objectstack/spec/migrations entry split). Its build step exits 2: "Neither spec appears in the built console — no @objectstack/spec content matched." The injection works. The check was reading text the console never bundles. This PR makes the build-time probe derivation choose with the bundle in view. The fresh leg stops reading entries the console never imports, and the stale leg becomes strictly stronger.

Root cause, measured

scripts/console-spec-probes.mjs builds each spec's blob from every JS file the package's exports map resolves to. The assertion then took the alphabetically first unique .describe() text on each side. A console bundles only the entries it imports, so either probe could come from an entry the bundle can never carry.

The readings below compare the published @objectstack/spec 17.4.0 that objectui locks at pin dd3f7e1be356 with the framework spec.

leg framework fresh witness (carried by) in bundle stale detector (carried by) in bundle old check
dark f927864ea0 (main before the split) "A public export added or removed by one release." (.) yes "Accepted developer agreement version" (./cloud) no exit 0
lit fbec216e2d (origin/main) the same text, now (./migrations) no the same (./cloud) no exit 2
  • The witness moved with the split from the root to ./migrations, which the console never imports. Meanwhile 102 of the 142 injected-only descriptions ARE in that bundle; the first sorts from ./ui.
  • The stale leg was already blind. Its detector sits in the published ./cloud entry, which objectui's shipped source never imports: 116 /ui, 62 /data, 17 /kernel and so on, and no /cloud. A console built WITHOUT the injection carries 56 of the 160 published-only descriptions, and not that one.
  • The spec side is not the cause. The console never imported the change manifest, and the injected spec is in the bundle. packages/spec is untouched here.

The fix (scripts/console-spec-probes.mjs, chooseProbes)

  • Fresh witness: the first injected-only description the bundle DOES carry. With none carried, "neither spec appears" is still exit 2.

  • Stale leg: judged over EVERY published-only description. It fails when ANY of them is in the bundle, and reports the first one found with the count. A bundle the old single pick flagged is still flagged, so this is strictly stronger.

  • Stamp: keeps its shape and version. With nothing published in the bundle, the stamped detector is the same one pickProbe chose, so check:console-injection's cache-hit replay is unchanged.

  • Assertion script: scripts/assert-console-spec-injection.mjs calls chooseProbes and prints the counts. The exit ladder is unchanged: 0 verified, 1 published spec bundled, 2 unverified.

  • Self-test: scripts/check-console-injection.mjs gains battery 13 (roster floor 10 to 11). It runs the real assertion script on fixtures whose first unique candidate is not in the bundle:

    • a working injection verifies, and its stamp replays green;
    • a published-only description the old pick skipped fails the build and writes no stamp;
    • neither side present stays exit 2.

    I checked that the battery can fail: reverting chooseProbes to first-pick semantics made it red with 5 failures, and restoring the fix turned it green again.

Proof

All legs ran against real vite builds of the console, with the old and the new assertion on the same bundle.

console build objectui pin new check old check
injected spec from fbec216e2d dd3f7e1be356 exit 0 (witness from ./ui; 102/142 present; 0/160 published) exit 2
NOT injected (published spec) dd3f7e1be356 exit 1 (56/160 published-only descriptions present) exit 2
a bundle with no spec text — exit 2 ("Neither spec appears") —
injected spec from f927864ea0 (dark) dd3f7e1be356 exit 0 (111/142 present) exit 0
injected spec from fbec216e2d db11afd49670 (PR #20706's pin) exit 0 (102/142; 0/160) exit 2
NOT injected (published spec) db11afd49670 exit 1 (56/160) exit 2

On this branch's head, the Console Pin Gate's steps run locally all pass: bash scripts/build-console.sh with the injected spec exits 0; the dist presence assert passes; pnpm check:console-sha exits 0; and pnpm check:console-injection --require-stamp exits 0 and replays the new stamp.

Verification record

  • dispatch-gates --commands for the 3 changed paths derives 31 families. --ran reconciles them as 30 run, all exit 0, and 1 NOT MEASURED: check:pm-dispatch-gates, whose self-test alone outruns the 590-second foreground cap here. It does not read these files' behaviour.
  • node scripts/check-console-injection.mjs --self-test passes: 44 assertions, battery floor met.
  • Lint, narrowed and proven: eslint --no-inline-config on the 3 changed files reports 0 errors and 0 warnings. The config enables no type-aware linting, so untouched files' verdicts cannot move.
  • No changeset: the diff touches only root scripts/, which ship in no published package (@objectstack/spec-monorepo is private).

Acceptance notes

  • The cache-hit replay's stale leg is still a single stamped detector. At this pin that detector is ./cloud text no console bundles, so on a dist-cache HIT the replay cannot catch a published spec. The build-time leg now can. Fixing the replay needs a multi-detector stamp (a stampVersion bump that invalidates every cached dist under the unchanged cache key), which is outside this claim. Carrier: the domain:spec seat. Noted, not filed.
  • CI on this head: Console Pin Gate (job 109696257740) concluded success. The dist cache missed, so step 11 built the console and ran the changed assertion (105 of 145 injected-only descriptions present, all 160 published-only absent), and steps 12 to 14 passed on that fresh dist.

Generated by Claude Code

…undle in view

The build-time assertion took the alphabetically first unique description from
every exports-map entry, so both probes could come from entries the console never
imports: after the migrations entry split the fresh witness was
@objectstack/spec/migrations text and a working injection read as 'neither spec
appears', and the stale detector was published ./cloud text, absent by
construction. The witness is now the first injected-only description the bundle
carries, and the stale leg fails on ANY published-only description in the bundle.
The stamp keeps its shape.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5e32522c10e4f90b2ef4ff42f4e8878cf39608e7
Local-runs: none

Inputs: card #20646 (body and all 16 comments — the landing-with-a-red-check record 5901417945, the follow-up claim 5901457286, the dev's report 5902247352), PR #20695's comments (the seat's Console Pin Gate note 5901327082 and the queue-triage comment), PR #20743 (body, zero comments, 3-file list, the net diff against main at the head, whose merge base is fbec216e2d; main has since advanced 5 commits, none touching the three files), ci.yml's filter and console-pin jobs and scripts/build-console.sh at the head, @objectstack/spec's exports map and sources at fbec216e2d, f927864ea0 and the tag @objectstack/spec@17.4.0 plus the npm packument for 17.4.0, objectui at pin dd3f7e1be356 (git objects) and the compare to db11afd49670 (API), and the head's 34 check-runs with job 109696257740's log, read once. Nothing built, run or re-run.

① Derived judgments

  • Root cause, read from the code. At f927864ea0 packages/spec/src/index.ts:227 re-exports ./migrations/index.js, so "A public export added or removed by one release." (src/migrations/spec-changes.ts:141) sat in the root entry, which the console imports (13 root imports at the pin). At fbec216e2d that line is gone, the exports map gains ./migrations (root plus 18 subpaths), and the text is reachable only through dist/migrations/index.mjs; objectui at the pin imports @objectstack/spec/migrations nowhere. The old fresh leg keyed on the alphabetically first injected-only candidate, so a working injection read as exit 2. The stale side: "Accepted developer agreement version" is src/cloud/developer-portal.zod.ts:69 at the tag 17.4.0, whose exports map (tag and npm packument agree) carries ./cloud; the tree dropped ./cloud at 776d64cd3d, and objectui at dd3f7e1be356 has zero source imports of @objectstack/spec/cloud (the 11 files naming it do so in docblocks and tests). Both pins lock @objectstack/spec@17.4.0 (the lockfile is unchanged between them). So the detector was unbundled by construction before the split and the witness became so with it: the fault is the derivation's assumption that the first unique candidate sits in an entry the console bundles, not the spec. The seat's hypothesis in 5901327082 holds. Right.
  • The accept-set change, enumerated. Old: fresh = first unique injected candidate in the bundle, stale = first unique vendored candidate in the bundle. New (chooseProbes): fresh = ANY injected-only candidate in the bundle, stale = ANY published-only candidate in the bundle; the exit ladder and the tri-state (null when a side has no unique candidate) are unchanged. Per verdict: the exit-1 set is a superset of the old one (the old detector is an element of the new pool); the exit-2 set shrinks to exactly "no unique text of either spec in the bundle"; the exit-0 set gains bundles that carry some injected-only text but not the first-sorted one and no published-only text, and loses bundles that carry the first witness, lack the first detector, and carry another published-only text. Right, and the direction the order asked for.
  • Bundles the old script rejected that the new one accepts, constructed. Every such bundle is an old exit 2 (never an old exit 1). Case (i), the fbec216e2d bundle itself: the injection worked, 102 (CI: 105) injected-only descriptions were in it, and the old verdict "Neither spec appears" was false — an accident of sort order and entry layout, since the same bundle passed the old script the day before the split with the same text one entry over. Case (ii), the only defective shape in that set: a failed injection whose console-imported entries carry no published-only description while the client's transitive copy of the tree's spec carries injected-only text. There the old exit 2 was luck, not design — its message was still false, and it would have been exit 0 had the first candidate sat in a client-imported entry — and the assert header already assigns that one-sided blindness to the stale leg, which is stronger here, not weaker. At these pins case (ii) is unreachable: a non-injected build carries 56 of the 160 published-only descriptions (the dev's positive control at both pins), so it exits 1 under the new script where the old one exited 2. No bundle the old script rejected by design is accepted. Right.
  • The order's three lines. (1) Yes: a bundle with no unique text from either spec exits 2 under both scripts (freshPresent and stalePresent both non-true), battery 13's third fixture pins it against the real assert script, and the verdict text is unchanged; bundles that carried unique text of one spec and were exit 2 only because the first pick was elsewhere now get a verdict, which is the fix and not a loosening. (2) Yes: every bundle the old stale leg failed is failed, as above; the fixture whose first published-only text is unbundled and second is bundled goes from old exit 0 to exit 1. (3) Yes: three files, no workflow step, no allowlist, no skip; SELF_TEST_BATTERY_FLOOR 10 to 11 pins the roster size of the existing self-test (the file's own comment: a deleted battery must red), and raising it when a battery is added is that gate's internal parameter, not a new gate. Right.
  • False positives of the stronger stale leg. The head's own gate run confirms the dev's reading at dd3f7e1be356 independently: "all 160 published-only descriptions are absent" on a real injected build. At db11afd49670 the reading is the dev's build alone; the compare between the pins adds no @objectstack/spec/cloud or /migrations import in the 300 files the API lists (through packages/app-shell; the remainder is unlisted) and leaves the lockfile untouched, and the pin-bump PR's own head runs this gate. Enough for the two pins in play. If a later pin or lockfile bump brings published-spec text into the assets through objectui's own dependencies, the leg reds with a true statement about the bundle; the message names the count and the first text found, not the asset file it sits in — a nicety, not owed. Right.
  • The stamp and its replay. writeStamp is called with the same five fields, STAMP_VERSION stays 1, and readStamp is untouched. On a passing build staleInBundle is empty, so the stamped detector is stalePool[0], exactly pickProbe's choice; the stamped witness is by construction in the bundle. evaluate() in check-console-injection.mjs is byte-unchanged (the diff touches only the roster, the floor and battery 13), so the replay still asserts the witness present, the detector absent, and the detector not expired against this tree's spec; the head's step 14 replayed the new stamp green. Right.
  • Battery 13. Three fixtures drive scripts/assert-console-spec-injection.mjs through spawnSync: fresh (first injected-only candidate unbundled, second bundled: exit 0, stamp records the bundled witness, replay green), stale (first published-only candidate unbundled, second bundled: exit 1, message names PUBLISHED and the text, no stamp written), neither (shared text only: exit 2, message unchanged). Six registered cases, floor 6; the two unregistered checked += 1 message checks follow the file's idiom. Read against a revert of chooseProbes to first-pick semantics: UNBUNDLED_FRESH sorts before FRESH so the fresh fixture exits 2 and writes no stamp; UNBUNDLED_STALE sorts before STALE so the stale fixture exits 0 and writes a stamp; that is five failures (fresh status, stamp witness, stale status, stale message, stale stamp) and the dev's ablation count. The third fresh expectation (replay green) does not discriminate under that revert, because an unstamped dist is advisory exit 0; the two before it do. Fixture strings are within the 32-to-160 length window. Right.
  • The dist cache on this head. Job 109696257740's log: CACHE_HIT is empty at step 12, the presence assert prints "via scripts/build-console.sh", step 11 ran the vite build and the changed assertion ("105 of 145 injected-only descriptions are in the bundle", "all 160 published-only descriptions are absent"), step 13 matched the pin, step 14 ran the self-test (44 assertions) and the replay, and step 15 saved the dist under Linux-console-dist-b59b7e9a…. The dev's correction is the true reading; the body's second Acceptance note is wrong for this head, and right only as a statement about a future hit. The seat applies pr_body_lines.
  • Gate coverage at the single read of the head's 34 check-runs: Console Pin Gate success (steps 1 to 15 all success; this is the family the diff derives — check:console-injection and check:console-sha ran against a fresh build); Type Check source gates, consumer gates, workspace, debt ledger and TypeScript Type Check success; Test Core 2 to 6, Dogfood Regression Gate, filter, Governed Surface Queue Guard, the three claim guards, Check Changeset, Check PR Size, Auto Label and Check Documentation Links success; Build Core, Build Docs, Dogfood matrix, Dogfood Verify CLI, Temporal Conformance and Packed-tarball smoke skipped by filter or opt-in, plus a second trigger's Auto Label, Check PR Size, Check Changeset and Packed-tarball rows skipped; 0 red; NOT concluded at my read: Lint & Repo Gates (lint and the scripts/ gates the dev's 31 families name) and Test Core (1/6). Nothing red is this diff's; the landing waits for every check, which is the seat's read.

② Semver level

  • skip-changeset matches what the diff publishes: three files under root scripts/, owned by @objectstack/spec-monorepo (private: true) and inside no package's files; nothing from any released package moves. Check Changeset concluded success. Right.
  • Clause-②: no, judged from scripts/pm/clause2-line.mjs's reading (value first after the colon, no arm): a repo gate's internals; no contract accepts or rejects anything new, and no public surface widens or narrows. Right. No changeset owed.

③ Boundary flags

  • Dev deviation, the refused worktree removals (objectstack-issue-20646 at 6ce9c2b349, objectstack-issue-20646-dark at f927864ea0): not a PR fact; both are on the dev machine, clean, for the seat or the maintainer to remove. Answered.
  • Dev deviation, the objectui build tree moved into scratch and reached through a gitignored .cache symlink: local hygiene under the read-only rule for the pin; nothing in the diff. Answered.
  • Dev deviation, the positive-control and second-pin builds run as build-console.sh's own commands by hand: the right method, since the script always injects and reads the pin the claim forbids touching; those readings are the dev's, and the head's gate run is the record's. Answered.
  • Dev deviation, check-console-injection.mjs changes only its self-test: verified by the diff. Answered.
  • Dev deviation, the body's second Acceptance note: verified against the log above; the seat patches the body with the dev's line. Answered.
  • Dev out-of-scope finding, the replay's single stamped detector: on a cache hit the replay re-reads immutable bytes the build-time leg judged against the whole pool before step 15 saved them (release.yml's combined save can store a failed build, but the stamp is written last, so --require-stamp refuses it), and no pre-fix cache under this key can hold an unproven dist at this pin, because the old script exited 2 on a non-injected build and saved nothing. Not a wrong result anyone can reach today; the replay's live value is the witness-presence and expiry checks, both intact. An Acceptance note is the right carrier; a card only if the seat wants the multi-detector stamp on its own merits. Not escalated.
  • Dev NOT MEASURED, check:pm-dispatch-gates: the diff does not touch dispatch-gates.mjs; CI runs it under Lint & Repo Gates, unconcluded at my read. Gate-covered.
  • Reviewer's flag, the counts: the code comment in chooseProbes and the PR body record 102 of 142 injected-only descriptions at fbec216e2d; the head's CI build reads 105 of 145 for the same tree, a difference consistent with a locally reused spec dist (build-console.sh skips the spec build when dist/index.mjs exists). The verdict path reads no count, so nothing turns on it; the comment states its measurement context. Not a defect.
  • Reviewer's flag, cost: the assertion now runs one substring search per unique candidate (about 305) over the concatenated assets instead of two; about thirteen seconds in the head's run, inside a 45-minute job. Noted, not a defect.
  • Dev open_questions: none.

Implemented-by: claude/issue-20646-console-pin-probe
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 01:47
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 9c8f113 Sep 30, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20646-console-pin-probe branch September 30, 2026 02:21
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…s to the commits that decided them (stage 10) (objectstack-ai#20750)

Part of objectstack-ai#20234

Clause-②: no

Stage 10 of the dead-citation sweep: the migration registry's
hand-written entries. Every tracker number in
`packages/spec/src/migrations/entries/**` that no longer exists on the
board now cites the commit that decided it, in ruling C+D form C (ruling
`5749154545` on objectstack-ai#19123). Where the number alone carried the meaning, the
line now says what was decided. That is 131 sites over 21 numbers. All
of them are comments; the entries' string literals carry no dead number.
`migrations/registry.ts` moves only by `gen:migration-registry`. No
entry id, literal, order, `conversionIds` or code token moves, and no
live citation is removed.

## Boundary

- **In:** all of `entries/**`. The gate's census reads 117 sites there,
under the claim's ~120 slicing threshold, so there is no slice. My raw
walk finds 14 more comment sites that the census does not count (see
*Census* below), which gives 131 in total. They are the same dead card
on the line after a counted site, plus one README line, so they are
rewritten with it.
- **Regenerated only:** `migrations/registry.ts`, 128 lines.
`spec-changes.json` and `docs/protocol-upgrade-guide.md` do not move,
because entry comments are never projected into them. Both `check:`
scripts pass with no regeneration.
- **Out, per the claim:** `registry.ts`'s hand-written parts. They still
hold 13 dead sites, listed under Acceptance notes. The six
`18.*-unit-in-key.ts` entries that PR objectstack-ai#20706 edits carry no dead site,
and I did not touch them. I did not touch `conversions/` or other lanes'
sites.

## The anchors (one per number, reused from earlier stages where they
anchored the same number)

| number | sites | anchor | what the rewritten line says it decided |
|---|---|---|---|
| objectstack-ai#13135 | 26 (13 are `re-charter objectstack-ai#13135`) | commit 9e0ba21 | retires
the paper metadata-customization protocol; re-charter of objectstack-ai#12057, which
stays |
| objectstack-ai#8495 | 23 | commit 4bfe1a5 (PR objectstack-ai#8666 kept) | the precedent: the
first 17.x-line narrowing registered under protocol 18 (its own second
commit says so) |
| objectstack-ai#8715 | 16 | commit 2c86fe3 | the ApiKeySchema retirement; its
message names the "route 3" kit (no carrier key, no tombstone, no D2) |
| objectstack-ai#14691 | 11 | commit b3a63d3 | retires the ten inert
`RestServerConfig` keys |
| objectstack-ai#10724 | 11 | commit be21955 | retires the nine dead `contributes`
members |
| objectstack-ai#14369 | 10 | commit a3d5724 | the liveness census that recorded the
15 `dead` rows |
| objectstack-ai#10485 | 6 | commit 35ad101 | retires the `themes` carrier and
`ThemeSchema` |
| objectstack-ai#11846 | 5 | commit 0c2334f | retires preview mode; its own text
carries the ruling record (objectstack-ai#12428 kept) |
| objectstack-ai#14676 | 5 | commit 13c48c2 | retires `connector.errorMapping` |
| objectstack-ai#11332 | 3 | commit dce5cd4 | retires the manifest's three dead
containers |
| objectstack-ai#6361 | 2 | commit 90bbf25 | retires the notification-list `cursor`
on both halves |
| objectstack-ai#10627 | 2 | commit be21955 | that commit records the controlled
monorepo census |
| objectstack-ai#14365 | 2 | commit f60ab90 | the open `z.partialRecord` proposal
that commit's changeset recorded; the retirement leaves no record to
reshape |
| objectstack-ai#14526 | 2 | commit db16b94 | the landing of the client envelope
convergence (anchor block, and the README's measured case) |
| objectstack-ai#6363 | 1 | commit 17d0954 | "ruled jointly with the `unreadCount`
fix" |
| objectstack-ai#6239 | 1 | commit f549a0d | the ViewProtocol retirement sweep |
| objectstack-ai#10726 | 1 | commit bc56e18 | retires `contributes.routes` (Option
B) |
| objectstack-ai#10812 | 1 | commit be21955 | "the cloud census", whose 2026-08-24
reading @5b5925a that commit's message records |
| objectstack-ai#9041 | 1 | commit d491625 | the url-branch refinement (objectstack-ai#9147, live,
stays) |
| objectstack-ai#14996 | 1 | commit db16b94 | the ADR-0087 registration, which
landed in the same squash (its body: "Registration requested on objectstack-ai#14996")
|
| objectstack-ai#14312 | 1 | commit e944fdb | the `oauth.*` binding, which left
`applications.delete` out as a behaviour change (PR objectstack-ai#15445 kept) |

Two lines change without a number, so the sentence still reads:
`patterns`' follow-on line and the `oauth` anchor block's continuation
line. In total 133 lines are removed and 133 added in 86 files, and
every file is balanced.

## Verification record (final head `1ee5841c09`; base `fbec216e2d`)

**Census** (the gate's own `check-issue-citations.mjs --census --json`,
board enumerated, 186 pages):

| subtree | base (00:21Z, frontier objectstack-ai#20740) | head (01:18Z, frontier
objectstack-ai#20743) |
|---|---|---|
| `entries/retired-keys` | 73 | 0 |
| `entries/retired-defs` | 40 | 0 |
| `entries/semantic` | 4 | 0 |
| `registry.ts`, generated regions | 114 | 0 |
| `registry.ts`, hand-written parts | 2 | 2 |
| `chain.ts`, `types.ts`, `index.ts`, `spec-changes.ts`, tests | 0 | 0 |
| **`migrations/`** | **233** | **2** |
| `packages/spec/src` | 235 | 4 |

- **Site-set difference:** 254 sites are only at base. 231 are this
diff's (117 plus 114 copies). The other 23 are `plugin-audit`'s, from
`main`'s objectstack-ai#20737. 0 sites are only at head.
- **Kind** (every census site is a comment): a TypeScript 6.0.3 walker
classes all 131 entry sites as comments. It also finds 13 dead string
sites, all in `registry.ts`'s hand-written rationale (see Acceptance
notes).
- **Probe:** REST `issues/N` without following redirects, over all 302
distinct in-repo numbers of 100 or more in `migrations/`. 277 answer 200
and 25 answer 404. Controls were read at start, every 50 and end: lit
objectstack-ai#20234 200 (8/8), dead objectstack-ai#8710 404 (8/8).
- **Blind spots:** the census does not count 14 of the 131 entry sites.
- 13 are `re-charter objectstack-ai#13135`, because `NON_CITATION_HEADS` reads
`re-charter #N` as an ordinal.
- 1 is in `entries/README.md`, which is outside the gate's
`packages/**/src/**/*.ts` surface.
- **Numbers:** no tracker number is added. The only numbers on added
lines are the live ones kept from the same lines: objectstack-ai#8666, objectstack-ai#12057, objectstack-ai#8586,
objectstack-ai#12428, objectstack-ai#9147 and objectstack-ai#15445. The diff-scoped gate judges them: "every
citation this change adds resolves".

**Residue** (scratch walker over the TypeScript parser, per file, base
vs head, 86 `.ts` files):
- The file with every comment range cut out, everything else byte for
byte, is IDENTICAL.
- The leaf-token stream is IDENTICAL: 38,417 tokens, aggregate
`67c1f6db944e93ed`.
- **Controls** mutate the head text in memory only, 259 of 259 as
expected:
  - Expected identical: a comment insertion in every file.
- Expected to differ: a string-literal edit, a template-literal edit, a
regex-literal edit (where the file has one) and an appended declaration.

**Regeneration** (`gen:migration-registry`):
- `check:migration-registry` exits 1 before and 0 after.
- The registry diff is 128 lines out and 128 in. As (old, new) pairs
they equal the entry diff's pairs, re-indented by four spaces.
- 0 changed lines fall outside the generated regions.
- 5 entry pairs are deliberately not carried: the README line, and the
semantic "Anchors" header lines, which sit above a blank line and so are
not in the carried comment run.
- `check:spec-changes` and `check:upgrade-guide` exit 0 with no
regeneration.

**Build and tests** (under `os-verify-lock`):
- Build: `turbo run build` over `./packages/*` and `./packages/*/*`, 71
of 71, at `845e90fea4` and again at `1ee5841c09`.
- `check:generated`: all 15 artifacts up to date, at both heads.
- spec `local` project: 576 files, 16,991 passed and 1 todo, at both
heads.
- spec `repo` project: 41 of its 45 files, 666 passed, at both heads.
- spec `typecheck`: exit 0; 53 files / 251 errors / 138 pinned
signatures held.

**Gates:** `dispatch-gates --commands` derives 82 gates, and the
derivation is identical before and after the second merge. At
`1ee5841c09` all 82 exit 0. `--ran` reconciles: 82 derived, 82 run, 0
NOT-MEASURED, a derived zero.

**Lint** (a proven narrowing):
- `eslint --no-inline-config --format json` over the 86 touched `.ts`
files: 86 files, 0 errors, 0 warnings.
- `isPathIgnored` is false for all 86.
- `eslint.config.mjs:327-328` states that type-aware linting is never
enabled, so a comment edit cannot move an untouched file's verdict.

**Changeset:** `patch`.
- In the built `dist`, the new wording (for example "the precedent of
commit 4bfe1a5, PR objectstack-ai#8666") appears in `dist/migrations/index.js` and
`.mjs`, and the old wording appears in 0 files.
- Control: an unchanged phrase appears in the same 2 files.
- So the published `@objectstack/spec/migrations` entry carries these
comments.

**Merges:** `origin/main` was merged twice through
`scripts/pm/os-regen-merge.sh`. Neither merge left a regeneration to
commit.
- Since then, `main` has advanced to `97005aed04`, and none of those
commits touches `packages/spec`.
- Driver-free `merge-tree` probes (from a bare `--shared` scratch clone
with no `merge.*` config) exit 0 against that `main` and against PR
objectstack-ai#20706's head `d8bac3877d`.

## Acceptance notes

- **The next stage for this card: 13 dead sites left in `registry.ts`'s
hand-written parts.** They are outside this claim, which says
`registry.ts` is regenerated only.
  - **Comments (form C), 2:**
- `:5940` objectstack-ai#8495, the step-18 doc comment on the persistence placeholder
refusal.
    - `:22819` objectstack-ai#6239, the `RETIRED_DEFS_BY_MAJOR[17]` doc comment.
  - **Author-shown rationale strings (form D), 11:**
- Step 17's `:344` objectstack-ai#6345. It projects into
`docs/protocol-upgrade-guide.md:68`, so that stage regenerates the
guide.
- Step 18's fragments: `:5121` objectstack-ai#14676, `:5455` objectstack-ai#12868, `:5526` objectstack-ai#10329,
`:5544` objectstack-ai#8495, `:5555` objectstack-ai#13135, `:5742` objectstack-ai#10724, `:5745` objectstack-ai#10627, `:5752`
objectstack-ai#10726, `:5799` objectstack-ai#10485, `:5816` objectstack-ai#10926. These are not projected into
either artifact yet.
- **Census blind spots, measured here and not filed** (a coverage
boundary, not one of the three filing classes; new gates default to no):
- `NON_CITATION_HEADS` skips `re-charter #N` even where N is a tracker
card.
  - `.md` files under `packages/**/src` are outside the surface.
- **NOT MEASURED locally, left to CI:**
- `scripts/build-schemas-check-mode.test.ts` hit the foreground cap
alone (exit 124 after 560 s, no output). It parses `registry.ts`'s
source; the residue check above shows that source's code and string
tokens are unchanged.
- The other three heavy `repo`-project files (`def-key-collisions`,
`publish-smoke-boot-failure`, `publish-smoke-port-collision`) were not
run, as in stages 8 and 9.
- **Hypothesis 6's baseline half is falsified by construction.**
`scripts/doc-authoring-prose-id.baseline.json` has no `packages/spec`
row, because its leg excludes `packages/spec`. `check:doc-authoring`
reads strings only, so a comment-only diff cannot move it. It read 808
sites across 229 files at `845e90fea4` and 794 across 227 at
`1ee5841c09`. The difference is `main`'s own re-anchor commits; this
diff touches no row.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants