Repository navigation
fix(scripts): the console spec-injection probes are chosen with the bundle in view (#20646) - #20743
Conversation
…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>
Contract reviewServed-tier: Inputs: card #20646 (body and all 16 comments — the landing-with-a-red-check record ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…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>
Fixes #20646
Clause-②: no
Console Pin Gateis red onmainsincefbec216e2d(PR #20695, the@objectstack/spec/migrationsentry 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.mjsbuilds each spec's blob from every JS file the package'sexportsmap 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/spec17.4.0 that objectui locks at pindd3f7e1be356with the framework spec.f927864ea0(main before the split).)./cloud)fbec216e2d(origin/main)./migrations)./cloud)./migrations, which the console never imports. Meanwhile 102 of the 142 injected-only descriptions ARE in that bundle; the first sorts from./ui../cloudentry, which objectui's shipped source never imports: 116/ui, 62/data, 17/kerneland so on, and no/cloud. A console built WITHOUT the injection carries 56 of the 160 published-only descriptions, and not that one.packages/specis 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
pickProbechose, socheck:console-injection's cache-hit replay is unchanged.Assertion script:
scripts/assert-console-spec-injection.mjscallschooseProbesand prints the counts. The exit ladder is unchanged: 0 verified, 1 published spec bundled, 2 unverified.Self-test:
scripts/check-console-injection.mjsgains battery 13 (roster floor 10 to 11). It runs the real assertion script on fixtures whose first unique candidate is not in the bundle:I checked that the battery can fail: reverting
chooseProbesto 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.fbec216e2ddd3f7e1be356./ui; 102/142 present; 0/160 published)dd3f7e1be356f927864ea0(dark)dd3f7e1be356fbec216e2ddb11afd49670(PR #20706's pin)db11afd49670On this branch's head, the Console Pin Gate's steps run locally all pass:
bash scripts/build-console.shwith the injected spec exits 0; the dist presence assert passes;pnpm check:console-shaexits 0; andpnpm check:console-injection --require-stampexits 0 and replays the new stamp.Verification record
dispatch-gates --commandsfor the 3 changed paths derives 31 families.--ranreconciles 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-testpasses: 44 assertions, battery floor met.eslint --no-inline-configon the 3 changed files reports 0 errors and 0 warnings. The config enables no type-aware linting, so untouched files' verdicts cannot move.scripts/, which ship in no published package (@objectstack/spec-monorepois private).Acceptance notes
./cloudtext 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 (astampVersionbump that invalidates every cached dist under the unchanged cache key), which is outside this claim. Carrier: thedomain:specseat. Noted, not filed.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