Skip to content

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 into
mainfrom
claude/issue-21709-console-probe-collision
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21709-console-probe-collision

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 writes inputs descriptions 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-gantt markers describe and objectui's packages/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). chooseProbes now takes that blob as hostBlob, required. It is a TypeError without it.
  • The rule (both legs): a candidate that is in the bundle and carried by objectui's source is not evidence. It is counted (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.
  • One new refusal: if every published-only candidate is in the bundle and objectui carries every one, the stale leg has no probe left. That is neither "no skew" nor "absent", so the assert exits 2 (inconclusive). The old code answered that same state exit 1, with a false accusation. It never passed.
  • scripts/assert-console-spec-injection.mjs: a new required --objectui flag (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.
  • Same-class fix on the fresh leg, declared: the same rule applies to the injected-only witness. A witness that objectui writes itself is no proof of the injection. Before this change, a bundle that held objectui's literal and no spec at all passed; now it reads "neither spec appears" (exit 2). The four bounded-fix conditions all hold: same defect class, same function, a file nobody else has claimed, and the same gate family. It is pinned in battery 14.
  • The stamp shape is unchanged (stampVersion 1). The stamp now records the filtered choice, so a cache-hit replay agrees with the build.

Reproduction (base 7e0066af7a, the head of queue run 37187916146)

  • CI: queue run 37187916146, job 111393796807 ("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.
  • Local, the real door: scripts/build-console.sh at 7e0066af7a, run under os-verify-lock.sh. It used objectui ab1879721595, and the vendored @objectstack/spec resolved 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? })".
  • Measured, not assumed: over that real bundle (33.3 MB of JS), the published-only candidates present are exactly 1 of 38, and it is the markers text. 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

  • (b), whole-literal matching, would fix today's collision. The markers text is not a whole literal in the real bundle.
  • It leaves half the family open. In the real bundle, 9 spec describes are whole literals inside objectui's own chunks, for example "Action IDs available for related records" in the plugin-grid chunk. Rewording any of those 9 would recreate this red under (b).
  • At the pin, over the module's own file filter, objectui's non-test code holds 18 literals equal to a spec describe and 18 more that begin with one.
  • (a) covers both shapes. It drops only text whose presence could not have been evidence anyway, and only when that text is in the bundle.
  • Leak detectability (pin 2 on the real bundle): I added the published 17.6.0 JS as a chunk to the real assets. The head assert exits 1 on 37 of 38, detector "Actions to execute during transition". Replaying the good build's stamp beside those assets also exits 1.
  • Cost: the host blob is 32.3 MB and reads in about 0.3 s.

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.

  1. Pin 1 passes: the injected spec plus an objectui literal that begins with a published-only describe, plus a second literal equal to one (which exercises the quote-backslash normalisation). The stamp records staleDetector = a text objectui does not write. The replay passes.
  2. Pin 2 still fails: a bundle carrying the published spec's own JS fails with exit 1 and names a detector objectui does not write. No stamp is written. objectui's test file quotes that detector, so this case also pins that tests are not read as source.
  3. Pin 3, the replay agrees: the replay matches the assert on both bundles: 0 on bundle 1, and 1 ("carries the PUBLISHED") on bundle 2 with the good stamp beside it.

Battery 14 also covers:

  • the all-host-carried stale leg (exit 2, "cannot judge");
  • host-carried text absent from the bundle, which stays a valid detector (exit 0, stamped, replay 0);
  • the fresh-leg case (exit 2);
  • an objectui tree git cannot list (exit 2);
  • chooseProbes without hostBlob (TypeError).

Ablation. Predicted first and saved, then run through scripts/ablation-replace.mjs in wrap mode. The mutation was const hostCarried = new Set();. On disk the anchor went 1 to 0 and the marker 0 to 1. The restore was proven: blob 903c3e80cce1 equals HEAD, and git diff HEAD is empty.

  • Self-test: exit 1, with 13 failures, exactly the predicted list, all in battery 14 (pin 1 ×6, pin 2 detector wording ×2, pin 3 bundle-2 wording ×1, all-host-carried ×2, fresh leg ×2). No other battery failed.
  • Pin 2's exit 1 held with and without the fix.
  • Synthetic bundles under ablation: exit 1 1 of 38 with the markers detector, and exit 1 38 of 38.
  • Real bundle under ablation: exit 1 1 of 38 with the markers detector, and exit 1 38 of 38 with the published chunk added.

The real door on this head

  • End to end: scripts/build-console.sh on 93ea8e7d80 reused the built objectui tree, rebuilt the console at the pin, and ran the fixed assert. Exit 0, with 37 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-sha exited 0, and pnpm check:console-injection --require-stamp exited 0 (self-test 67, replay passed).
  • That build was before the last commit. 93ea8e7d80 differs from the final head 0bcf6a0a95 only in a self-test fixture line in check-console-injection.mjs, and that self-test was re-run on 0bcf6a0a95.
  • PR side: all four changed paths are in ci.yml's console filter, so Console Pin Gate runs 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.
  • Queue: once this merges, every queue build's Console Pin Gate runs this assert. release.yml's key hashes build-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/objectstack derived 32 commands. All 32 exited 0.
  • --ran reconciliation: 32 derived, 32 run, 0 NOT MEASURED, 0 unrun, every one with a recorded exit code. That includes pnpm check:console-injection, check:console-sha, check:ci-filter-parity (+ self-test), check:nul-bytes, check:entry-guard and check:pm-dispatch-gates (1976 cases, run detached).
  • eslint, narrowed: eslint.config.mjs lints all three changed .mjs files, so none is ignored. --format json reported 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.sh is outside eslint's population. The full pnpm lint is left to CI.

Changeset

skip-changeset: nothing published changes. The diff is root scripts/ only, and the root package is private and ships no files. I built the console dist (the one published package this build feeds) and grepped it: readHostSourceBlob / hostCarried have 0 hits, while the positive control, the markers literal, is found in 3 files.

Acceptance notes

  • Dead branch, not touched: the assert's "published spec is gone, but nothing unique ... was found" branch is unreachable, both before and after this change. The "neither" branch and the stale exit cover every way into it. Carrier: none.
  • Parity tests stay evidence: objectui's apps/console/src/__tests__/registry-inputs-spec-parity.test.ts quotes 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.
  • Same-tree pin: the reproduction and the real-door runs used the same tree as the failing queue run, 7e0066af7a (its head_sha). origin/main has since moved by four commits, and none of them touches this gate's inputs.

Generated by Claude Code

claude added 3 commits October 4, 2026 09:40
…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>
@github-actions github-actions Bot added the size/m label Oct 4, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 4, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 10:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 10:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit a2b7328 Oct 4, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21709-console-probe-collision branch October 4, 2026 11:30
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