Repository navigation
Commit a2b7328
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 #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](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent c43a8ae commit a2b7328
4 files changed
Lines changed: 354 additions & 24 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
38 | 51 | | |
39 | 52 | | |
40 | 53 | | |
| |||
56 | 69 | | |
57 | 70 | | |
58 | 71 | | |
| 72 | + | |
59 | 73 | | |
60 | 74 | | |
61 | 75 | | |
| |||
67 | 81 | | |
68 | 82 | | |
69 | 83 | | |
| 84 | + | |
70 | 85 | | |
71 | 86 | | |
72 | 87 | | |
| |||
84 | 99 | | |
85 | 100 | | |
86 | 101 | | |
87 | | - | |
| 102 | + | |
88 | 103 | | |
89 | 104 | | |
90 | 105 | | |
| |||
100 | 115 | | |
101 | 116 | | |
102 | 117 | | |
| 118 | + | |
103 | 119 | | |
104 | 120 | | |
105 | 121 | | |
106 | 122 | | |
| 123 | + | |
107 | 124 | | |
108 | 125 | | |
109 | 126 | | |
| |||
112 | 129 | | |
113 | 130 | | |
114 | 131 | | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
121 | 157 | | |
122 | 158 | | |
123 | 159 | | |
| |||
139 | 175 | | |
140 | 176 | | |
141 | 177 | | |
142 | | - | |
| 178 | + | |
143 | 179 | | |
144 | 180 | | |
145 | 181 | | |
146 | 182 | | |
147 | 183 | | |
148 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
149 | 201 | | |
150 | 202 | | |
151 | 203 | | |
152 | 204 | | |
153 | 205 | | |
154 | 206 | | |
155 | 207 | | |
| 208 | + | |
156 | 209 | | |
157 | 210 | | |
158 | 211 | | |
| |||
165 | 218 | | |
166 | 219 | | |
167 | 220 | | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
168 | 226 | | |
169 | 227 | | |
170 | 228 | | |
| |||
179 | 237 | | |
180 | 238 | | |
181 | 239 | | |
| 240 | + | |
182 | 241 | | |
183 | 242 | | |
184 | 243 | | |
185 | 244 | | |
186 | 245 | | |
187 | 246 | | |
| 247 | + | |
188 | 248 | | |
189 | 249 | | |
190 | | - | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
191 | 256 | | |
192 | 257 | | |
193 | 258 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
376 | 376 | | |
377 | 377 | | |
378 | 378 | | |
| 379 | + | |
| 380 | + | |
379 | 381 | | |
380 | 382 | | |
381 | 383 | | |
| 384 | + | |
382 | 385 | | |
383 | 386 | | |
384 | 387 | | |
| |||
0 commit comments