Repository navigation
fix(ci): the Console Pin Gate never counts text objectui's own source carries as the published spec (#21709) - #21717
Merged
objectstack-fleet[bot] merged 3 commits intoOct 4, 2026
Conversation
…n source carries WIP: self-test battery follows. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…ence, on the build and the replay Battery 14 of check-console-injection's self-test: the injected spec plus an objectui literal beginning with (and one equal to) a published-only description passes and stamps a detector objectui does not write; the published spec itself still fails; the replay agrees on both bundles. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…he good build wrote no stamp Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-21709-console-probe-collision
branch
October 4, 2026 11:30
This was referenced Oct 4, 2026
This was referenced Oct 4, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…tead of a tracker number (stage 11) (objectstack-ai#21736) Part of objectstack-ai#20749 Clause-②: no Stage 11 of this card, and the second area of class (e): the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on objectstack-ai#20513. This stage takes the whole `kernel/` directory. Its 102 test-title and test-message literals carried 106 tracker ids citing 69 records. Each id now either states what its record decided, in words (form D), or is dropped where the title already says it. Text only: no assertion, fixture value, test count or code comment changes. ## Census at the base (`3fa850cf00`, the claim's base) Instrument: stage 10's `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`) and stage 9's `census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), both byte-identical to the copies stage 10 used. A literal counts as a test title when its folded message is argument 0 of a `describe` / `it` / `test` call, `.each` / `.skip` / `.only` chains included. Everything else is an "other" string. Both instruments read **1696 messages / 1807 ids in 405 files at the base**, which is stage 10's reading at its head exactly. `kernel/` reads 102 / 106, also stage 10's figure. | directory | files | messages / ids | titles | other | |:--|--:|--:|--:|--:| | `data/` | 95 | 468 / 501 | 445 / 475 | 23 / 26 | | `ui/` | 81 | 392 / 415 | 374 / 397 | 18 / 18 | | `api/` | 40 | 189 / 201 | 181 / 193 | 8 / 8 | | `system/` | 34 | 154 / 165 | 128 / 138 | 26 / 27 | | (files directly in `src/`) | 30 | 118 / 120 | 117 / 119 | 1 / 1 | | **`kernel/`** (this PR) | 36 | **102 / 106** | 92 / 96 | 10 / 10 | | `shared/` | 21 | 85 / 95 | 73 / 81 | 12 / 14 | | `contracts/` | 25 | 63 / 74 | 59 / 70 | 4 / 4 | | `conversions/` | 9 | 34 / 34 | 34 / 34 | 0 | | `security/` | 8 | 28 / 28 | 28 / 28 | 0 | | `ai/` | 9 | 18 / 20 | 13 / 15 | 5 / 5 | | `identity/` | 6 | 15 / 15 | 14 / 14 | 1 / 1 | | `integration/` | 4 | 14 / 14 | 13 / 13 | 1 / 1 | | `migrations/` | 2 | 9 / 12 | 9 / 12 | 0 | | `marketplace/`, `meta-spelling/`, `studio/` | 5 | 7 / 7 | 7 / 7 | 0 | | **total** | **405** | **1696 / 1807** | **1587 / 1692** | **109 / 115** | - **Controls.** Lit, a title: `kernel/capability-metadata-kind.test.ts:66` reads one message with objectstack-ai#5961. Lit, a template-literal expect message: `kernel/cli-command-contribution-retirement.test.ts:74` reads one message. Dark: the `// ─── [objectstack-ai#17178] …` comment at `kernel/execution-context.test.ts:205` reads 0 (the file's messages sit at `:72`, `:167`, `:219`, `:233` and `:237`). Planted in a scratch copy: an id added to a title reads 1 / 1, and an id in an added comment reads 0. - **A wider pattern** (any `#` plus digits) reads 107 / 111 under `kernel/` at the base. The five extra hits are hex colours (`#94A3B8`, `#0f0`) in `functional-completeness.test.ts` and one `§3.10 objectstack-ai#3` section reference in `manifest.zod.ts`, a non-test file. None is a tracker id. At the head the wider pattern reads only those five, and the gate pattern reads 0 / 0. - **At the head:** 1594 messages / 1701 ids in 369 files. `kernel/` reads 0 / 0. Nothing else moved. ## How the area was chosen Stage 10's rule, applied before any card was read: rank whole first-level directories by ids, and take the busiest one within about 10% of the ~100-id bound. The four busiest each exceed the bound alone: `data/` (501), `ui/` (415), `api/` (201) and `system/` (165). The files directly in `src/` (120) are 20% over. `kernel/` (106) is the busiest whole directory within the bound, and its census reads exactly stage 10's 106, so the rule needed no second pass. **Named for the next stages:** `data/` (about five stages, by subdirectory or file group; `data/driver/` alone is 52), `ui/` (about four), `api/` (two), `system/` (two), the files directly in `src/` (one, 120), `shared/` (one, 95), `contracts/` with `conversions/` (one, 108), and `security/`, `ai/`, `identity/`, `integration/`, `migrations/`, `marketplace/`, `meta-spelling/` and `studio/` together (one, 96). ## What each id became Of the 106 ids, 32 now state a decision in words, in 31 literals. 74 are dropped where the title already explains them; two of those (objectstack-ai#14478) sit in literals that also gained words for another id. Every record was read with its comments through REST. 58 answer 200. Ten answer 404, and their decisions were read from what landed. One is in a repository not attached to this session. | record | ids | result | |:--|--:|:--| | objectstack-ai#12007, objectstack-ai#11825, objectstack-ai#12340, objectstack-ai#4914, objectstack-ai#15932, objectstack-ai#11846, objectstack-ai#16059 | 10 of 20 | Every expect message that read "must have zero holders after #N" or "must not be exported after #N" now reads "after its retirement": each record retired the names it lists. The other 10 sit in `describe` / `it` titles that already say what was retired, and were dropped. objectstack-ai#11846 answers 404; its retirement was read from the CHANGELOG entry for landing `0c2334f`. | | objectstack-ai#7280 | 1 | "the ADR-0069 gate posture is a declared field": `authGate` is declared on `ExecutionContextSchema`, not spread behind an `as any`. | | objectstack-ai#17178 | 1 | "SEED_WRITE_EXECUTION_CONTEXT — one spelling of the seed posture": one exported constant replaced the private copies. | | cloud#687 | 1 | "(the founding case: a roll-up that reads 0 forever)". The cloud repository is not attached to this session (403). The decision was read from ADR-0078's "Surfaced by" line and the CHANGELOG paragraph on the founding case: a bare `{ type: 'summary' }` field read 0 forever, and the rule now flags it as an error. | | objectstack-ai#14192 (404) | 4 | "(the silent-drop measurement, inverted)", where the title said "the card's measurement". Dropped from 3 titles that state the refusal. Decision read from landing `4d0d944`: `ManifestSchema` goes strict and refuses unknown keys inside `manifest:`. | | objectstack-ai#10726 (404) | 2 | "removed for the `http.server` mount, maintainer-ruled 2026-08-22", where the title said "Option B". Read from landing `bc56e18` and PR objectstack-ai#12417's body: Option B removes `contributes.routes` and points authors at the imperative `http.server` mount. Dropped once. | | objectstack-ai#4148 | 1 | "the object/field unknown-key warnings survive the generalization", where the title said "the objectstack-ai#4148 behaviours". | | objectstack-ai#4001 | 3 | "(the evidence base for the strict tiers)", where the title said "objectstack-ai#4001 evidence phase". Dropped from 2 titles that state the pinned rule. | | objectstack-ai#4167, objectstack-ai#8687 | 3 | "top-level stack keys (named, then refused at parse)": objectstack-ai#4167 made an undeclared top-level key say so instead of vanishing, and objectstack-ai#8687 ruled Shape B, a strict top level. Dropped once more for objectstack-ai#8687. | | objectstack-ai#15624 | 2 | "cache.ttl → deleted (the unread outer block is retired)". Dropped once. | | objectstack-ai#14478 | 3 | Dropped. The `→ ttlMs` / `→ timeoutMs` renames and "carry their unit" already state the rule: the unit lives in the key name. | | objectstack-ai#15939 | 1 | "RuntimeConfig.resourceLimits.timeout → timeoutMs (its unit was named in JSDoc only)", where the title said "ruling A". Ruling A renames the keys whose unit was named only in JSDoc, per file. | | objectstack-ai#5086 | 2 | "(PUT /meta refuses the inlet)": a code-only kind's create is refused with 403 `NOT_CREATABLE` before anything persists. | | objectstack-ai#7743 | 1 | "UNCHANGED, the field overlay refusal stays", where the title said "objectstack-ai#7743's overlay refusal". | | objectstack-ai#8154 | 1 | "(the consumer contract of the per-type redaction hook)". | | objectstack-ai#21120 | 1 | "stored metadata ROWS — the family-wide seam every exit routes through". This says only what the card's public summary says: one shared seam, which every surface routes through or refuses. | | objectstack-ai#6245 | 1 | "the bound-but-unregistered fence, pinned": schemas are bound for those kinds WITHOUT registering them. | | objectstack-ai#11263 | 1 | "PLATFORM_PLUGIN_WIRED_RUNTIMES — runtimes wired by plugins[], not by a token": a sibling roster was added, and no token was minted. | | objectstack-ai#3366 | 1 | "classifyRequiredCapability — preflight for an installable provider in this edition". | | objectstack-ai#17676 | 1 | "package-registry carve-out — its persistence is always-on core, split from `marketplace`", where the title said "ruling A′". | | objectstack-ai#16365 | 1 | "accepts %s, which the regex refused before the SemVer widening", where the title said "the pre-objectstack-ai#16365 regex". The `%s` values do not change. | | objectstack-ai#17227 | 1 | "dashboard.header.actions stays titled — the first carrier given item-level names". | | dropped only (live) | 45 | objectstack-ai#3308 (2), objectstack-ai#3433, objectstack-ai#3760, objectstack-ai#3786, objectstack-ai#4212, objectstack-ai#4509, objectstack-ai#4587, objectstack-ai#4657, objectstack-ai#4741, objectstack-ai#4834, objectstack-ai#4939, objectstack-ai#5488, objectstack-ai#5961, objectstack-ai#6881, objectstack-ai#6931, objectstack-ai#7893, objectstack-ai#8586, objectstack-ai#10039 (2), objectstack-ai#11169, objectstack-ai#12032, objectstack-ai#12428, objectstack-ai#13613, objectstack-ai#15678 (7), objectstack-ai#16328, objectstack-ai#16334, objectstack-ai#16449, objectstack-ai#17232 (2), objectstack-ai#17445, objectstack-ai#17780, objectstack-ai#18124 (2), objectstack-ai#18791 (3), objectstack-ai#19630, objectstack-ai#20102: each title already states the pinned decision. | | dropped only (404) | 8 | objectstack-ai#10194 (landing `2306a76`: each bound entry is its stack collection's schema), objectstack-ai#10338 (`d2619fd`: `target` optional, the gate holds the flow requirement), objectstack-ai#10724 (`be21955`: the nine dead members tombstoned), objectstack-ai#11330 (`a9ee98992`: the trust-tier text tells the truth), objectstack-ai#11332 (`dce5cd4`: the three dead containers retired), objectstack-ai#13135 (`9e0ba21`: the paper customization protocol retired), objectstack-ai#17147 (2, `aaacf1d5c`: the granted set is registered and refuses nothing, said truthfully). Each title already carries what landed. | ## Readers - **Test-name filters:** none. A tracked-tree search for `-t` and `--testNamePattern` finds only `packages/qa/dogfood/README.md:142` (`-t "owner-scoped"`), which is unrelated. - **Snapshots:** none. `kernel/` has no `__snapshots__`, and no `.snap` file is tracked under `packages/spec`. - **Titles by substring:** every old title, plus a window around each id (256 needles), was searched across the tracked tree outside its own file. No gate, doc or script matches one. At the head, 8 hits remain: three sibling titles in other lanes' or stages' files (`metadata-protocol/src/protocol.capability-write-door.test.ts:183`, `spec/src/api/contract.test.ts:813`, `spec/src/system/auth-config.test.ts:520`), three released `packages/spec/CHANGELOG.md` entries, and one code comment in `kernel/metadata-authoring-lint.ts:268`, which belongs to the comment lane. ## Text-only proof Stage 10's scratch tool (`textonly10.cjs`, md5 `d5e4801dbb4329ab1984da91e92fc47c`) compares base and head file by file on three legs: 1. **Skeleton:** the full AST, with string pieces masked. It must be identical. 2. **Comments:** every comment, byte-equal. 3. **Strings:** each string leaf that changed must sit in a test-call title position, or on one of the 10 declared lines. Those are the expect messages at `cli-command-contribution-retirement.test.ts:74` and `:86`, `plugin-lifecycle-advanced-retirement.test.ts:103`, `:124` and `:141`, `plugin-loading-retirement.test.ts:125`, `plugin-security-scan-result-retirement.test.ts:123`, `preview-mode-retirement.test.ts:193` and `startup-orchestrator-retirement.test.ts:85` and `:106`. Each changed leaf must carry a tracker id before and no `#` plus digits after. - **Result:** 36 of 36 files SAME, 102 changed (92 title, 10 declared), on all three legs. - **Diff hunks:** exactly the 102 planned lines, with every file keeping its line count. - **Controls (10 of 10 as predicted, on scratch copies, each anchor hit once):** identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string with an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; a declared string keeping an id VIOLATION; an undeclared expect message changed VIOLATION; a title re-split into a `+` chain DIFF. **Test counts:** the 36 files were run at the base (in a separate base worktree) and at the head: 841 / 841 tests on both sides, with the same count and status sequence per file in 36 of 36. 388 full test names change, and each equals the base name with the planned replacements applied. ## Changeset: `skip-changeset` Measured, not assumed: - `npm pack --dry-run` of `@objectstack/spec` lists 2068 files under `files[]`. 0 of the 36 touched files are in it, and 0 `*.test.ts` at all. The controls `src/kernel/manifest.zod.ts` and `src/kernel/execution-context.zod.ts` are in it. - In `dist/`, three new phrases and three old ones each read in 0 files. The control `Plugin compatibility ranges (ADR-0025` reads in 20. So this PR publishes nothing, and no changeset is added. ## Verification (at `e0ad8f50af`) - `pnpm turbo run build` over all packages: 71 / 71 (at the first commit), then `@objectstack/spec` rebuilt at `e0ad8f50af`. - `@objectstack/spec`: `vitest run --project local`, 613 files and 18215 passed, 1 todo. `typecheck` exit 0, including `check:test-typecheck`, whose program holds all 36 touched files. - **Gates:** `dispatch-gates --commands` derived 79 families at `e0ad8f50af`, and all 79 exit 0. `--ran` reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN. - **ESLint, a proven narrowing:** `--no-inline-config` over the 36 files, 0 errors and 0 warnings. The population comes from ESLint's own config: 36 configured, 0 ignored. No `parserOptions.project` or `projectService`, so no untouched file's verdict can move. - `check-governed-merges --test`: NOT governed, 204 changed lines. ## Acceptance notes - **Code comments still carry ids** in these 36 files and in the `kernel/` sources, for example `execution-context.test.ts:205`, `metadata-authoring-lint.ts:268` and `functional-completeness.ts:148`. They are the comment lane's, untouched here. - **A sibling title in another package** repeats `objectstack-ai#5961 — capability` (`packages/metadata-protocol/src/protocol.capability-write-door.test.ts:183`). It is that package's test-string stage, not this one. - **The second commit** (`e0ad8f50af`) rewords one title from this PR's first commit, "the one carrier already titled", which read as a tautology, to "the first carrier given item-level names". Every proof above was re-run at that head. - **`origin/main` moved** three commits past the base before this PR opened (objectstack-ai#21717, objectstack-ai#21715, objectstack-ai#21660). None touches `packages/spec`, so nothing was merged. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #21709
Clause-②: no
What changes
The
Console Pin Gate's probes no longer treat text that objectui's own source carries as evidence of either spec. A console bundle carries text from objectui's code as well as from the specs. objectui's component registry writesinputsdescriptions that copy spec.describe()text, either verbatim or as a prefix it then extends. When the spec rewords one of those, the old text becomes published-only. objectui's literal still carries it, so a substring search found "the published spec" in a bundle built from this tree's spec. That is what has turned every merge-queue build red since #21699 (16d241a6af): the object-ganttmarkersdescribe and objectui'spackages/plugin-gantt/src/index.tsx:190.scripts/console-spec-probes.mjs:readHostSourceBlob(dir)reads objectui's tracked code files at the pin (git ls-files; test files excluded; a quote's backslash normalised).chooseProbesnow takes that blob ashostBlob, required. It is a TypeError without it.counts.hostCarried), never decides a verdict, and is never returned as a probe, so it is never stamped. A candidate absent from the bundle is still evidence, whoever else carries it. The rule excuses presence, never absence.scripts/assert-console-spec-injection.mjs: a new required--objectuiflag (objectui's build tree, the git checkout at the pin). Its messages report host-carried text separately, so a pass no longer prints "all N published-only descriptions are absent" when one of them is in the bundle.scripts/build-console.sh(declared, and strictly needed by the route): the single assert call site passes--objectui "$BUILD_ROOT", the tree it just built. The only other way to find that tree is to reach two levels up from--vendored, which couples the assert to a path layout. A required flag means a forgotten call site fails loudly (exit 2).scripts/check-console-injection.mjs: the replay logic is unchanged. It needs no objectui tree, because the build never stamps a host-carried probe. Its self-test gains battery 14, and batteries 12 and 13 now pass--objectui.stampVersion1). The stamp now records the filtered choice, so a cache-hit replay agrees with the build.Reproduction (base
7e0066af7a, the head of queue run37187916146)37187916146, job111393796807("Build the Console SPA at the pinned objectui SHA"). Its triage comment on docs(spec): re-anchor the dead tracker citations in packages/spec/src's test surface to the commits that decided them #21700 quotes✗ Built console still carries the PUBLISHED @objectstack/spec.I could not read the raw job log from this container: the log blob host answered 403. So the per-candidate figure comes from the local build below, at the same commit.scripts/build-console.shat7e0066af7a, run underos-verify-lock.sh. It used objectuiab1879721595, and the vendored@objectstack/specresolved to 17.6.0. Lock held 559s. Result: exit 1,(1 of 38 published-only descriptions), the first of them: "Extra vertical reference lines drawn like the Today marker ({ date, label?, color? })".markerstext. objectui's tracked non-test source carries it. The injected-only candidates present are 26 of 26, and none of those is host-carried.Route: (a), by measurement
markerstext is not a whole literal in the real bundle.plugin-gridchunk. Rewording any of those 9 would recreate this red under (b).37 of 38, detector "Actions to execute during transition". Replaying the good build's stamp beside those assets also exits 1.Pins (battery 14,
node scripts/check-console-injection.mjs --self-test)Base: 44 assertions. Head: 67. Every fixture runs the real assert script and replays its stamp through
evaluate(). The mirrored texts sort first, so an unfiltered pick would choose them.staleDetector= a text objectui does not write. The replay passes.Battery 14 also covers:
chooseProbeswithouthostBlob(TypeError).Ablation. Predicted first and saved, then run through
scripts/ablation-replace.mjsin wrap mode. The mutation wasconst hostCarried = new Set();. On disk the anchor went 1 to 0 and the marker 0 to 1. The restore was proven: blob903c3e80cce1equals HEAD, andgit diff HEADis empty.1 of 38with themarkersdetector, and exit 138 of 38.1 of 38with themarkersdetector, and exit 138 of 38with the published chunk added.The real door on this head
scripts/build-console.shon93ea8e7d80reused the built objectui tree, rebuilt the console at the pin, and ran the fixed assert. Exit 0, with37 of 38 published-only descriptions are absent; 1 ... ARE in the bundle, and objectui's own source at the pin carries each of them too. Lock held 278s. The next CI steps then ran on that dist:pnpm check:console-shaexited 0, andpnpm check:console-injection --require-stampexited 0 (self-test 67, replay passed).93ea8e7d80differs from the final head0bcf6a0a95only in a self-test fixture line incheck-console-injection.mjs, and that self-test was re-run on0bcf6a0a95.ci.yml'sconsolefilter, soConsole Pin Gateruns on this PR. Three of them (build-console.sh, the assert, the probes module) are in the dist cache key, so the key moves and the cache misses. The PR head therefore gets a full console build at the pin with this assert, and that is the CI reading of the real door.Console Pin Gateruns this assert.release.yml's key hashesbuild-console.sh, so its next run rebuilds through the new call site too.Gates (on
0bcf6a0a95)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 32 commands. All 32 exited 0.--ranreconciliation: 32 derived, 32 run, 0 NOT MEASURED, 0 unrun, every one with a recorded exit code. That includespnpm check:console-injection,check:console-sha,check:ci-filter-parity(+ self-test),check:nul-bytes,check:entry-guardandcheck:pm-dispatch-gates(1976 cases, run detached).eslint.config.mjslints all three changed.mjsfiles, so none is ignored.--format jsonreported 3 files, 0 errors and 0 warnings. The config has no type-aware linting, so this diff cannot move any untouched file's verdict.build-console.shis outside eslint's population. The fullpnpm lintis left to CI.Changeset
skip-changeset: nothing published changes. The diff is rootscripts/only, and the root package is private and ships nofiles. I built the console dist (the one published package this build feeds) and grepped it:readHostSourceBlob/hostCarriedhave 0 hits, while the positive control, themarkersliteral, is found in 3 files.Acceptance notes
apps/console/src/__tests__/registry-inputs-spec-parity.test.tsquotes 2 of the 38 published-only describes. Tests are excluded from the host source, so in a real leak those two still count as evidence.7e0066af7a(itshead_sha).origin/mainhas since moved by four commits, and none of them touches this gate's inputs.Generated by Claude Code