Repository navigation
sdui-parser: port inert-quick-add and member-type-mismatch from objectui, re-record the lockstep and bump the pin - #18723
os-try-charles wants to merge 4 commits into
Conversation
… objectui
Both diagnostics existed only in objectui's copy of the parser, so the save
gate and the renderer disagreed about what a page means -- the dialect split
the lockstep invariant forbids.
- inert-quick-add: `quickAdd` on <object-kanban> reaches no control, because
the Quick Add button is gated on a runtime slot no parsed page can write.
Replaces the false `unknown-prop` ("has no prop quickAdd") the prop walk
emitted, which contradicts the contract spec still publishes.
- member-type-mismatch: the coarse type check one level down, over the member
kind `ManifestInput.of` declares. Brings the `of` key and its three readers
(validator, serializer canonicalization, codegen element type) with it.
Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
… the new pin Bumps .objectui-sha to the objectui revision the port was taken from, re-records objectui's side of the lockstep there, and follows the bump's second half by regenerating the tracked SDUI manifest. The manifest regenerates BYTE-IDENTICAL (sha256 49211fee7792cf51 before and after): both pins ship @object-ui 17.6.0, so the published registry inputs did not move. Only the record's objectuiSha stamp follows the pin. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
…ngeset check:dispatcher-error-vocabulary cannot reduce a kebab-case SCREAMING_SNAKE constant at a `code:` position, so objectui's `code: INERT_QUICK_ADD` is an unresolvable-constant finding here. This is the same documented divergence `unconsumed-widget-option` already carries: stamp the inline literal, keep the exported constant, and pin the two equal next door. The lockstep resolves the identifier through the constant, so both copies still agree on all 26 codes. The changeset is OWED and measured: both codes reach the published dist (the package ships `files: [dist]`), against a negative control that reads zero. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check15 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e12592ef8a530f503ed35a13e8a385db345936f6 && git checkout e12592ef8a530f503ed35a13e8a385db345936f6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c5d3d1d8b69e84497373c5232a654f0bbffe2b76 8a778067eb58d9b79241f6546016ea411c1269c8 && git checkout -B drift-repro c5d3d1d8b69e84497373c5232a654f0bbffe2b76 && git merge --no-ff 8a778067eb58d9b79241f6546016ea411c1269c8
node scripts/docs-audit/affected-docs.mjs --json c5d3d1d8b69e84497373c5232a654f0bbffe2b76 |
|
Director seat, summon #25 ( The maintainer's instruction today, verbatim: 「1天之前的pr都帮我诊断修复继续处理完,并跟进到合并」. This PR is 2.9 days old and red on ⛔ This seat does not touch the branch. If this PR shows no new output within three hours of this comment, the director seat recovers the claim on #17645 under the maintainer's takeover instruction and dispatches the rework itself. Generated by Claude Code |
…lands via #19398 first The Single-Claim Path Guard on the pin-bump PR fails with "At most one open PR may modify a declared single-writer path": .objectui-sha is a declared single-writer path and this draft still rewrites it (53ded82bf7a4 to cfcc17d9dd04), so it counts as an open claimant. Per ruling batch #200 item 4 the pin bump lands on its own PR first. So this branch releases the claim: both pin-carried files are restored to main's blobs byte for byte (.objectui-sha and scripts/sdui-manifest.record.json, the latter because check-sdui-manifest requires record pin == .objectui-sha). Nothing else is touched — the parser port, its tests and the changeset are unchanged. check:sdui-lockstep is RED in the interim, by design: [pin-moved], the lockstep record was taken at cfcc17d9dd04 while .objectui-sha now reads 53ded82bf7a4. This PR stays draft and re-records the lockstep at the new pin in a merge lap after the pin bump lands. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-authored-by: Claude <noreply@anthropic.com>
|
Closed as superseded by the director seat ( This PR's two-code port ( Generated by Claude Code |
…from the PIN, not the record (objectstack-ai#18875) Fixes objectstack-ai#18819 Clause-②: no ## The defect, re-measured on this branch's base `origin/main` `36583e989b`. The generator read its two provenance fields at two different moments: ``` :129 const version = arg('--objectui-version') ?? record.objectuiPackagesVersion; :199 objectuiSha: pin, :200 objectuiPackagesVersion: version, ``` `objectuiSha` is re-read from the LIVE `.objectui-sha`; `objectuiPackagesVersion` fell back to the value already sitting in `scripts/sdui-manifest.record.json`. A regeneration after a pin bump therefore wrote the NEW pin under the OLD version string — a record whose two objectui fields describe two different commits, which is exactly the record `scripts/check-sdui-manifest.mjs` check 4 rejects. The person that reds is the one who followed the documented bump procedure, so the producer is where it gets fixed. ## Step one, before writing any fix: is that path reachable today? The counterfactual on `:129` is that the defect is unreachable if `arg('--objectui-version')` is never empty. It is reachable. **Paths, not counts.** **No automated caller exists at all.** No `package.json` script, no workflow step and no shell line executes this generator. `scripts/bump-objectui.sh` and `scripts/build-console.sh` only `echo` the command. Every invocation is therefore a person or an agent typing one of the spellings the tree documents. Of those spellings, six omit `--objectui-version`: | path | spelling | |:---|:---| | `scripts/check-sdui-manifest.mjs:228` | `Regenerate: node scripts/gen-sdui-manifest-node.mjs` — printed when the artefact is missing | | `scripts/check-sdui-manifest.mjs:275` | `Regenerate: node scripts/gen-sdui-manifest-node.mjs` — printed on a sha256 mismatch | | `scripts/sdui-manifest.record.json:10` | `Regenerate + re-record: node scripts/gen-sdui-manifest-node.mjs` | | `packages/spec/scripts/manifest-prescription.ts:81` | ``regenerate it and its record with `node scripts/gen-sdui-manifest-node.mjs` `` | | `scripts/gen-sdui-manifest-node.mjs:8` | the bare usage line | | `scripts/gen-sdui-manifest-node.mjs:9` | the `--modules-root DIR` offline usage line | Two of those six are printed **by the gate itself**. The remaining spellings do carry the flag: `AGENTS.md:156`, `docs/releases-maintenance.md:116` and `:279`, `scripts/bump-objectui.sh:11` and `:739`, `scripts/build-console.sh:297`, `scripts/check-sdui-manifest.mjs:301` `:303` `:320`. The other paths that name the generator carry no command at all and are listed here for completeness: `.github/workflows/lint.yml:799` (a comment), `packages/spec/scripts/check-generated.ts:391`, `packages/spec/scripts/check-react-blocks-declaration-parity.ts:85`, and `packages/spec/scripts/check-react-blocks-declaration-parity.test.ts:565` `:569` (a pin test on the prescription text). ## A or B — decided by that reading, not by preference Zero automated call sites means option B's stated cost ("every call site has to change") is nearly zero *in code*. But B is not free: it would turn all six documented no-flag spellings into refusals, including the `--modules-root` offline mode, and it would refuse even when the answer is sitting in the objectui checkout the bump already required. So: **A for the default, B for the residue.** - `--objectui-version` still wins when passed. - Otherwise the version is what `packages/core/package.json` DECLARES at the pinned commit, read through `check-sdui-manifest.mjs`'s exported `readPinnedDeclaredVersion` — the same function check 4 judges the written record with, imported rather than re-typed. Producer and gate now read one oracle; two readers of two oracles is where this drift came from. - When that oracle cannot answer (no objectui checkout, or one that does not carry the pin) the generator REFUSES and names both remedies. An unread version is never converted into a version string — Route and surface ownership §3, prefer failing to falling back, and Prime Directive objectstack-ai#12's ban on a lenient `??` fallback in place of fixing the producer. Importing the gate's exported oracle changes no gate: `scripts/check-sdui-manifest.mjs` is untouched in this diff, and its CLI body is behind `isEntrypoint(import.meta.url)`, so importing it runs nothing. ## Verification Every leg below ran against a fixture tree in a scratchpad, or against the real repo with a trap and a proved restore. `.objectui-sha` and `scripts/sdui-manifest.record.json` are read-only this round; neither is in this diff. ### LIT — pin bumped, `--objectui-version` not passed. Both legs read. A synthetic objectui checkout with two commits (`a183cfbf…` declaring `17.6.0`, `51ecffcc…` declaring `18.0.0-fixture`), a stub `@object-ui` install behind `--modules-root`, and a fixture record naming the OLD sha at `17.6.0`. The fixture pin is the NEW sha. | leg | generator | record written | `check-sdui-manifest.mjs` | |:---|:---|:---|:---| | before | `36583e989b` | `51ecffccfb32 / 17.6.0` — new pin, **old version** | **exit 1**, check 4 red | | after | this branch | `51ecffccfb32 / 18.0.0-fixture` | **exit 0**, green | The before leg's gate output is check 4 naming the defect verbatim: ``` the record's two objectui fields describe different commits: objectuiPackagesVersion is "17.6.0", but objectui 51ecffccfb32… — the pin the record itself names — declares "18.0.0-fixture" in packages/core/package.json ``` ### DARK — pin not bumped. Reads zero, on the REAL artefact. Not a fixture: the real `scripts/gen-sdui-manifest-node.mjs`, the real pin `53ded82bf7a494f54e344e19099dbf00854b8694`, and the real published `@object-ui/*` 17.6.0 set installed once and reused by both legs so the install cannot be the variable. The base generator was swapped in on disk, its arrival proved by anchor counts before the leg ran (`?? record.objectuiPackagesVersion` = 1, `readPinnedDeclaredVersion` = 0, and a `git hash-object` differing from the HEAD blob), then restored. ``` leg after (fixed) : 57 components, 66910 bytes, sha256 49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d leg before (base) : 57 components, 66910 bytes, sha256 49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d cmp: BYTE-IDENTICAL ``` That sha256 is also the one the tracked record already carries, and the tracked `sdui.manifest.json` HEAD blob is still `78f870e42fe9` — unmoved. Restore proved by state, not by an exit code: `git diff HEAD` empty and `git status --porcelain` empty afterwards. ### Two further legs - **Refusal residue.** Pin bumped, no flag, oracle unreachable (`OBJECTUI_ROOT` at a directory with no `.git`): base generator exits 0 and silently writes the drifted record; this branch exits 1, writes no manifest, and leaves the record untouched. - **Explicit precedence.** `--objectui-version 99.1.2-explicit` is honoured unchanged on both legs; the success line now also names where the version came from. ### Gates - `node scripts/check-sdui-manifest.mjs` — **exit 0** on the clean tree, and its check 4 leg resolved for real: "@object-ui 17.6.0 is the version objectui 53ded82bf7a4… declares in packages/core/package.json (read from /home/user/objectui)". - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 26 commands; all 26 run, **all exit 0**, and `--ran` with exit codes recorded reconciles: "26 derived famil(ies) accounted for — 26 run, 0 NOT-MEASURED (a DERIVED zero)". - The seven roster families the tool flags as "roster under scripts, which one of your paths is in — silence is not evidence in EITHER direction" were run too. Six exit 0. `pnpm check:published-readme-exports` exits **3 — PREREQUISITE NOT MET** (46 packages unbuilt); that is NOT MEASURED, not a finding, and this diff moves no package source and no README. - `pnpm lint` (`eslint . --no-inline-config`), the whole repo, **exit 0** at `1c3a721ac1`. - `pnpm check:nul-bytes` exit 0; an independent control-byte scan of the changed file is clean. No changeset: nothing published moves. `scripts/**` appears in no package's `files[]` and the root package is `private`, so no released package's contents change. ## Acceptance notes Noted, not filed — none is a reproducible defect, a declared-contract violation, or a metadata-authoring trap, so none earns a card under Prime Directive objectstack-ai#10: - `scripts/check-sdui-manifest.mjs` check 4's header paragraph, and its check-4 failure text at `:318-319`, both explain the producer default this PR removes ("The generator REUSES the version already recorded when `--objectui-version` is not passed"). After this lands those sentences describe history rather than current behaviour. The gate is fenced read-only for this card, so they are left exactly as they are. The check itself stays load-bearing — it still catches a hand-edited record, an explicitly-wrong `--objectui-version`, and any other producer. - `packages/spec/scripts/check-react-blocks-declaration-parity.ts:88-89` says the node producer "enumerates the PUBLISHED `@object-ui/*` packages at the version named in `scripts/sdui-manifest.record.json`". That sentence is wrong-by-default after this change. Not fixed in place: editing it pulls `packages/spec` into the diff and with it a whole gate family this card does not otherwise owe, against the bounded-in-place-fix condition of adding no new verification surface. - The residual hole this PR deliberately does not close: an operator who passes `--objectui-version` **explicitly and wrongly** still writes a drifted record. The generator could warn when an explicit value disagrees with the resolvable pin oracle. Check 4 already reds the resulting record, so this is a loudness improvement, not a correctness gap. - objectstack-ai#17735 is not addressed here — whether the published tarballs at a version were built from the pinned commit is that card's producer question, and it carries `needs-user-decision`. This change moves the version's SOURCE and nothing about that relation. objectstack-ai#18814's check 4 is not modified here either. Fenced files honoured: `scripts/sdui-manifest.record.json` and `.objectui-sha` are held by open PR objectstack-ai#18723 and are read-only in this round. The real DARK leg did rewrite the record as a side effect of running the generator, exactly as warned; both were restored from HEAD and the restore is proved by an empty `git diff HEAD`, never by an exit code. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
…ult, not the record (objectstack-ai#18956) Fixes objectstack-ai#18879 Clause-②: no PR objectstack-ai#18875 retired the generator's record-derived version default. `scripts/gen-sdui-manifest-node.mjs` now resolves the version in one order — `--objectui-version` when passed, otherwise what the PINNED objectui commit declares in `packages/core/package.json` (through `check-sdui-manifest.mjs`'s own exported `readPinnedDeclaredVersion`) — and REFUSES when neither answers. Three passages still described the retired default as current. This repairs those three, and leaves four others alone because they are conditional, not broken. ## The card's six sites are not one class, and there is a seventh I enumerated every site on this tree and classified each. The census phrase the card used (`gen-sdui-manifest-node`, 30 hits tree-wide) plus two wording sweeps (`REUSE`, `already in the record` / `already recorded` / `already sitting`) is what the enumeration is taken from. Class (a) — literally false today, repaired here: 1. `scripts/check-sdui-manifest.mjs` check 4's problem text (was :318-319) 2. `packages/spec/scripts/check-react-blocks-declaration-parity.ts` (was :87-88) 3. `scripts/check-sdui-manifest.mjs` header, check 4's own rationale (was :43-47) — NOT on the card's six; see "The seventh site" below Class (b) — conditional, not broken, deliberately untouched: 4. `scripts/check-sdui-manifest.mjs:228` (artefact missing) — flagless `Regenerate:` prescription 5. `scripts/check-sdui-manifest.mjs:275` (sha256 mismatch) — flagless `Regenerate:` prescription 6. `scripts/sdui-manifest.record.json:10` — flagless prescription; also held by open PR objectstack-ai#18723 7. `packages/spec/scripts/manifest-prescription.ts:81` — flagless prescription, and its own text already points the reader at where the exact call is printed ## (a) verbatim, before and after ### 1. check 4's problem text Before (the false sentence is the REASON CLAUSE for the prescription on the line below it): ``` describes a registry this pin does not name. The generator REUSES the version already recorded when --objectui-version is not passed, which is how a regeneration re-records a new pin under an old version. Regenerate against the pin: node scripts/gen-sdui-manifest-node.mjs --objectui-version 9.9.9-what-the-pin-declares ``` After: ``` describes a registry this pin does not name. The generator reads this version from the PIN, never from this record, so a disagreement came from outside that default: a version named explicitly on some other run, a hand edit, or a record written before the default was fixed to read the pin. Regenerate against the pin: node scripts/gen-sdui-manifest-node.mjs --objectui-version 9.9.9-what-the-pin-declares ``` Both blocks are real output, captured by driving `checkTree` against the fixture shape the script's own self-test uses (a green tree plus a throwaway objectui whose declared version disagrees with the record). The prescription line is unchanged. Why the new sentence is true of today's generator: `gen-sdui-manifest-node.mjs:149-168` takes `--objectui-version` when passed, otherwise calls `readPinnedDeclaredVersion(pin)` and `fail`s on any status but `resolved`; it never reads `record.objectuiPackagesVersion`. Check 4 only runs when `pin === record.objectuiSha` (check 3 returns early otherwise), so at this point the record names the LIVE pin and its version field disagrees with what that pin declares. The three routes the new sentence lists are the ones that survive: an explicit `--objectui-version` naming another version, a hand edit, and a record written before that default was fixed. ### 2. the node producer's description in the parity gate header Before: ``` // node `scripts/gen-sdui-manifest-node.mjs` writes the TRACKED repo-root // artefact under plain Node — no browser, no objectui build. It // enumerates the PUBLISHED `@object-ui/*` packages at the version named // in `scripts/sdui-manifest.record.json`. ``` After: ``` // node `scripts/gen-sdui-manifest-node.mjs` writes the TRACKED repo-root // artefact under plain Node — no browser, no objectui build. It // enumerates the PUBLISHED `@object-ui/*` packages at the version // `--objectui-version` names, else the one the PINNED commit's // `packages/core/package.json` declares — and REFUSES when neither // answers. `scripts/sdui-manifest.record.json` is what that run // WRITES, never where it reads the version from. ``` Why the new sentence is true of today's generator: the record is the run's OUTPUT (`gen-sdui-manifest-node.mjs:229-240` writes `nextRecord` with `objectuiPackagesVersion: version`), never its input. The paragraph forty lines below this one in the same header already says the version is "read off the pinned commit's `packages/core/package.json`", so the file now agrees with itself. ### 3. the seventh site — the same defect, one grep-phrase away The card's census missed it because the wording differs: the problem text says "the version already recorded", the header says "the version already in the record", so the card's own firing control never sounded on it. Before: ``` * with nothing asking whether V is C's version at all (objectstack-ai#18611). The live * way to write that record is the generator's own default — * `--objectui-version` unset makes it REUSE the version already in the * record, so a regeneration after a pin bump keeps the OLD version string * while re-recording the NEW pin, and checks 1-3 stay green throughout. ``` After: ``` * with nothing asking whether V is C's version at all (objectstack-ai#18611). The * generator's own default does not write such a record — `--objectui-version` * unset reads the version off the PIN and REFUSES when the pin cannot be * read — but an explicit wrong version, a hand edit, or a record written * before that default was fixed still produce one, and checks 1-3 stay * green throughout. ``` Repaired in place rather than filed: same file, same defect class, same gate family, mechanical, and the correct shape was already pinned by the sibling passage above. A full scan of all 25 open non-release PRs (file lists read one by one) found no other claim on this file. ## Firing control `git grep -n 'version already recorded' -- '*.mjs' '*.ts' '*.md'` - before: 1 hit (`scripts/check-sdui-manifest.mjs:318`) - after: 0 hits, exit 1 The instrument is not dead. Two positive controls of the same command shape, run on the same tree after the edit: `'version already sitting in the record'` answers 1 hit in `scripts/gen-sdui-manifest-node.mjs`, and `'Regenerate against the pin'` answers 1 hit in `scripts/check-sdui-manifest.mjs` — the very file the zero is claimed about, so the grep demonstrably still reaches it. ## (b) decided, with live readings, and the decision differs from triage Triage framed `:228` / `:275` as "a gate handing you a command it will itself refuse", and half of its `p2` audience argument rests on that reading. I agree with the PM's narrowing, and I did not take the reading off the source: I ran the generator both ways in a scratch root, so nothing in this tree was written. The scratch root was made discriminating on purpose — its record was seeded with `objectuiPackagesVersion: "0.0.0-BOGUS-SCRATCH"`, a version that exists on no registry. Had the retired default still been live, the run would have tried to install it. Leg 1 — no resolvable pin (`OBJECTUI_ROOT` pointed at a path with no checkout): exit 1, nothing written, and the refusal carries its own two remedies: ``` ✗ gen-sdui-manifest-node: no --objectui-version, and the pinned commit's declared version could not be read: no objectui checkout at /nonexistent-objectui (no .git there). Set OBJECTUI_ROOT to one that carries the pin. ⛔ This script does NOT fall back to the version in scripts/sdui-manifest.record.json: after a pin bump that re-records the NEW pin under the OLD version string, and check-sdui-manifest.mjs check 4 reds exactly that record. Either: - point OBJECTUI_ROOT at an objectui checkout carrying 53ded82bf7a4…, or - pass --objectui-version {the @object-ui version that pin ships} explicitly. ``` Leg 2 — resolvable pin, full run including the network install: exit 0. ``` → installing @object-ui/* 17.6.0 into /tmp/sdui-manifest-gen-OcPzor (npm, network)... → enumerating the registry (16 modules)... ✓ wrote sdui.manifest.json (57 components, 66910 bytes, sha256 49211fee7792…) ✓ re-recorded scripts/sdui-manifest.record.json at pin 53ded82bf7a4 / @object-ui 17.6.0 (version from packages/core/package.json at objectui 53ded82bf7a4… (read from /home/user/objectui)) ``` It resolved `17.6.0` — the version objectui declares at the pin — over the bogus `0.0.0-BOGUS-SCRATCH` sitting in the record, and overwrote the record with it. The manifest it produced carries sha256 `49211fee7792…`, byte-matching the artefact tracked here. So `:228` and `:275` are not broken. The flagless call they print writes the correct artefact whenever the pin's declared version is readable, and when it is not, it refuses loudly with the remedy in its own failure text rather than writing a drifted record. That is the behaviour objectstack-ai#18875 was for. They are conditional, and a conditional prescription that fails loudly and names its own fix is not the shape this repo files cards about. Sites 6 and 7 in the list above are the same conditional shape; site 7 (`manifest-prescription.ts:81`) even printed itself during this round's gate sweep, intact and actionable, when `check:react-declaration-parity` ran without `MANIFEST` set. One consequence worth writing down rather than acting on: because the flagless default now reads the pin, the flagless call would ALSO repair the check-4 record. The flagged prescription is kept unchanged anyway — it names the value in the command, which is the self-evidencing form, and this round is prose only. ## DARK — the gate's verdicts do not move `scripts/check-sdui-manifest.mjs` run before and after the edit, three arms, both streams captured to files and compared with `cmp`: | arm | exit before | exit after | stdout | stderr | |---|---|---|---|---| | plain (resolves the `../objectui` sibling) | 0 | 0 | identical, 290 bytes | identical, 0 bytes | | `OBJECTUI_ROOT` unresolvable | 0 | 0 | identical, 496 bytes | identical, 0 bytes | | `OBJECTUI_ROOT` unresolvable + `--require-objectui` | 1 | 1 | identical, 0 bytes | identical, 295 bytes | `--self-test` is green and its output is byte-identical before and after: 11 cases, battery floor 11. The strings this PR edits live on failure paths, so a green tree never prints them — which is exactly why the verdict bytes do not move. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, run one by one with each exit code landed to disk before anything read it, then reconciled with `--ran` carrying those codes. ``` ✓ dispatch-gates --ran: 72 derived famil(ies) accounted for — 70 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3). ``` The two NOT MEASURED are `check:dual-build-cjs-loads` and `check:lean-entry-closure`. Both exit 3 — their own `PREREQUISITE NOT MET` code — because they load built output and this worktree has no full `pnpm build`. Both self-tests passed (93 and 22 cases). Neither can be moved by a comment-only diff; they are declared to CI. One gate exits 1 under the derived spelling and 0 under CI's: `check:react-declaration-parity` refuses when `MANIFEST` is unset, which is a prerequisite, not a red. Run as `lint.yml` runs it — `MANIFEST="$PWD/sdui.manifest.json" ... --baseline react-declaration-parity.baseline.json --strict` — it exits 0 with "no new DECLARATION divergence vs accepted baseline". Derivation stability: the 72-family list came out byte-identical at all three tree states this branch passed through (`625db0e853`, `0ea26d37e8`, `92eee4dcc5`), so the tool's STALE TREE notice — raised because a sibling agent's fetch keeps advancing the shared `origin/main` ref — changed nothing about what is owed here. ## Tests and lint - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/check-react-blocks-declaration-parity.test.ts scripts/build-schemas-check-mode.test.ts` — 2 files, 115 tests, all passed. These are the suites that name the two edited scripts. `scripts/check-sdui-manifest.mjs` has no sibling test file in `scripts/`; its `--self-test` is its suite, and it is above. - `pnpm --filter @objectstack/spec typecheck` — exit 0. - `pnpm --filter @objectstack/spec check:scripts-typecheck` — exit 0; this is the program that compiles the edited `packages/spec/scripts/**` file. - `pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated` after merging `main` — all 15 generated artifacts up to date. - Dependency-closure build `pnpm --filter '@objectstack/spec^...' build` is an empty run here: "No projects matched the filters" — `packages/spec` has no workspace dependencies. - Lint, the whole union, as CI runs it, at `92eee4dcc5`: `eslint . --no-inline-config --format json` over a population of **6855 files** read from eslint's own JSON output — 0 errors, 0 warnings. Narrowed to the two edited files: 2 files, 0 errors, 0 warnings. `eslint.config.mjs:326-328` records that this repo "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file", so nothing in this diff can move an untouched file's verdict either way. ## Changeset `skip-changeset`, measured rather than assumed: - No package manifest in the repo declares a `files[]` entry reaching `scripts/` — 76 manifests scanned, 0 hits. - `packages/spec`'s `files[]` is `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `spec-changes.json`. `scripts/` is not among them. - Grepping every one of those published paths (plus all 202 `src/**/*.zod.ts`) for two distinctive strings from this diff returns zero hits, exit 1. Positive control, same command shape: `PageComponentSchema` answers from `dist/identity/index.js` and three siblings. - `scripts/check-sdui-manifest.mjs` is a repo-root script belonging to no package. Nothing published moves, so the tarballs do not move. ## Read-only fences, re-taken at start of work - `scripts/sdui-manifest.record.json` is held by open PR objectstack-ai#18723 (state open, draft, 11 files) — confirmed by reading that PR's file list directly, not inherited. Untouched here. Its own stale-ish prescription is class (b) anyway, so the fence costs this round nothing. - The same scan confirmed objectstack-ai#18723 does NOT hold `check-sdui-manifest.mjs`, `check-react-blocks-declaration-parity.ts` or `gen-sdui-manifest-node.mjs`, and no other open PR holds any of them. - `scripts/gen-sdui-manifest-node.mjs` is this round's source of truth and is untouched. - No self-test making every printed prescription runnable was added. That is a new gate and it is not on this card. - No product code under `packages/spec/src/`. ## Acceptance notes Observed, deliberately not filed: - The card counts "six" sites; a full sweep finds a seventh, in the same file, repaired here under the bounded in-place rule. Noted so the census phrase is on record: a wording sweep keyed on one spelling ("already recorded") will not find its sibling spelling ("already in the record"). Who would hit it: the next reader of check 4's rationale — the same audience as the problem text, which is why it is repaired rather than noted. - `docs/releases-maintenance.md:116` / `:279`, `scripts/bump-objectui.sh:11` / `:705` / `:739` and `scripts/build-console.sh:282` / `:297` all print the FLAGGED form and are correct today; `scripts/sdui-manifest.record.json:5` and `check-sdui-manifest.mjs:19-21` describe the version as "the version `.objectui-sha` ships", which is more true after objectstack-ai#18875 than before. None of them is a site. Carrier: none — nothing here needs changing. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
…ED, not a census takedown (objectstack-ai#19290) Fixes objectstack-ai#19077 Clause-②: no Item **2** of objectstack-ai#19077 — the census-side application. Item 1 (`parseDerivedText`, the returnable door) landed as PR objectstack-ai#19112, squash `956e0107a`; nothing here re-does it. Base: `origin/main` = `215840f4353`, fetched 2026-09-20T09:07Z. Every reading below was taken on that base in a dedicated worktree, never on the shared checkout. ## The defect, and what it did `scripts/tenant-audit-census.mjs` stores a declared type's text whitespace-collapsed and re-parses it as a synthetic alias (`type CensusReceiver = ...;`) to read the engine-door rule off it. A type literal may separate its members by a **newline alone** — legal TypeScript — and the collapse turns that separator into nothing, so the synthesis does not parse. Through `parseSourceFile` that did not fail the SITE: it ended the process, and every other site in the corpus lost its verdict with it, under a refusal naming `census-receiver-type.ts`, a file that does not exist in the tree. ## The repair — the four points of the minimal hunk, and where each landed | point | landed | |---|---| | ① the door rule takes the origin `sf` the type text came from | `readTypeTextDoor(typeText, origin)`; `resolveReceiver` hands its own `sf` at all four `inlineEngineDoorOrOther` call sites | | ② the synthetic alias goes through `parseDerivedText` | `readTypeTextDoor`, one call site, the only synthesis in the file | | ③ on failure the report is printed against the SITE and the door reads `false` | the located verdict rides on the site as `derivedFailure`; `main()` prints it under a per-site `::error::` line | | ④ a **declared** `NON_ENGINE_REASONS` arm, placed in `UNDEFENDED_REASONS` | `type-text-not-round-trippable`, in both artefacts under ENFORCEMENT | ## ⛔ Point ④ and the floor — why this PR also edits the gate The non-negotiable is that no importer ends up exiting 0 where it exits non-zero today. Measured, rather than assumed: `lint.yml:2007-2008` invokes `scripts/check-tenant-audit-census.mjs` and **never the generator**, and that gate's own docblock says why (`censusRefusals` exists because `runCensus`'s findings were read by nothing on the way to a CI verdict). So on a corpus holding such a receiver: - **today** — `runCensus` calls `parseSourceFile`, which exits 3; the gate dies with it, CI red. - **with the repair and no gate arm** — the site would be declared in the artefacts, the author would regenerate, and the gate would go **green**. That is the loud process exit traded for a quiet subtraction: the same floor drop in a different costume. - **as landed** — the site is classified, declared in both artefacts, printed against its own file and line with the parse verdict under it, **and refused** by `censusRefusals` through `notRoundTrippableSites`, the one spelling the generator and the gate share. `main()` counts it in its exit code too, for the local run. ⇒ the exchange is a process-wide takedown for a located, per-site refusal. Nothing that exits non-zero today exits 0 after this, and no site leaves the population in silence. ## Acceptance — triage's two inputs, as a pair Both run in `tenant-audit-census.mjs`'s own self-test, which CI drives through `node scripts/check-tenant-audit-census.mjs --self-test` (`lint.yml:2007`). Ten new cases; the file's self-test goes 49 to 59, the gate's 24 to 27, and the gate's `census refusals` battery floor is raised 5 to 7 so the new cases cannot stop running unnoticed. 1. **A newline-separated inline type literal is CLASSIFIED.** The case runs the REAL round trip — a source is parsed, `declaredTypesIn` collapses the declared type exactly as the census does, and the resolver reads the door off the stored text — and reads `other/type-text-not-round-trippable`. **Lit control:** the semicolon spelling of the SAME literal is still PLACED as `engine/inline type literal stating an engine door`, so the first case is not a door that rejects everything. Two more: the arm is in `UNDEFENDED_REASONS` (control: a defensible arm is not), and the failure is carried as located data naming the SOURCE it was derived from. 2. **A genuinely unparseable input is still REFUSED.** A garbage type text lands on the same declared arm and no door is read off it, so it is never placed. The corpus door is untouched: sources are still read through `parseSourceFile`, whose process-exit refusal is pinned by `ts-parse.mjs --self-test` (landed with item 1) and enforced for every `scripts/**` caller by `check:parse-guard` (exit 0 here). **Boundary, pinned:** the verb gate runs first, so a newline-separated literal naming no write verb is never synthesised and cannot reach the new arm — the repair's reach is exactly the defect's reach. ## The census over the live corpus, before and after `node scripts/tenant-audit-census.mjs`, base `215840f4353`: - **before** (2026-09-20T09:08Z): `exit 0`, **576 sources**, 227 write call sites. - **after** (2026-09-20T09:20Z): `exit 0`, **576 sources** — output **byte-identical** (`diff` exits 0), one undefended subtraction, reason `type-not-in-corpus`, door-shaped 0.⚠️ The previous round measured 573 sources; `main` moved between the rounds. The escalation trigger did **not** fire: exit 0 means no receiver of this shape exists in the tree, so the card stays latent and the grading stays triage's. ## Reverse verification — three ablations, each restored byte-identical Driven through `scripts/ablation-replace.mjs`, which proves the mutation landed on disk (anchor count fell, replacement count rose, blob hash changed) and proves each restore (`blob == HEAD blob`, `git diff HEAD` empty). 1. **The door reverts to `parseSourceFile`** — the self-test does not fail, it **dies**: `exit 3`, printing the card's own refusal for `census-receiver-type.ts` with `1:78 ';' expected`. That is the defect, reproduced from the acceptance case. 2. **The arm is removed from `UNDEFENDED_REASONS`** (point ④'s own ablation) — RED, the declaration pin fails, 1 of 59. 3. **The gate's refusal loop is emptied** — RED, 2 of 27 gate cases fail, which is the "quiet subtraction" costume failing to pass.⚠️ Reported rather than quietly retried: ablation 2's first attempt was **refused** by the helper because the replacement text was a substring of the anchor, so the on-disk count could not rise. It restored and never ran the command; the rerun used a dropping anchor. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived from this tree at `ce1b5304101` (4 paths vs merge base `215840f43`, three-dot): **64 families, all exit 0**, each captured BEFORE any pipe, reconciled with `--ran`: `64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN` — a DERIVED zero, every family carrying a code. Five of the 64 first answered `PREREQUISITE NOT MET` (exit 3 / exit 1: not findings, nothing measured) and were re-run after building what they read — `pnpm --filter '@objectstack/lint...' build`, then `pnpm --filter '@objectstack/client-react...' --filter '@objectstack/client...' build`, both through `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0`). All five then exit 0. Outside that total by the tool's own accounting: 52 artifact-roster families, 11 declared wide-population families, 14 pending-changeset families, 2 path-scheduled CI jobs. `pnpm lint` repo-wide is CI's run. The narrowing here is DECLARED and carries all three readings: (1) the reachable population comes from eslint's own config text — `eslint.config.mjs:328` records that this repo runs one config that never enables type-aware linting (no `parserOptions.project`, no typed rules) for ANY file, measured there with a positive control; (2) the targeted count is read from `--format json`: 4 files, 0 errors, exit 0 — the two markdown artefacts report "File ignored because no matching configuration was supplied", i.e. eslint does not lint them at all; (3) invariance — with no type-aware linting anywhere, a 4-file diff cannot move the verdict on a file it does not contain. Taken at `ce1b5304101`. ## Changeset `skip-changeset`, on three readings: (1) the root package is `private: true`; (2) all **70** non-private workspace packages declare a `files[]` (lit control) and **0** of them have an entry that climbs out of its own package directory; (3) none of the four changed paths is under any package directory. ⇒ root `scripts/**`, `content/docs/**` and `docs/audits/**` cannot be in any published payload. `Clause-②: no` follows: this moves a build-time gate's failure convention, not `packages/spec`'s accept set or any public surface. ##⚠️ Declared deviation — three files outside the dispatched file surface The dispatch declared `scripts/tenant-audit-census.mjs` and new tests under `scripts/`. This PR also touches: - `scripts/check-tenant-audit-census.mjs` — the gate-side half of point ④, argued above. Without it the repair lowers the floor, which the dispatch forbids outright. - `content/docs/permissions/tenant-audit-census.mdx` and `docs/audits/2026-08-tenant-audit-write-call-sites.counts.md` — GENERATED regions, rewritten by `node scripts/tenant-audit-census.mjs --write`, which is the only legal way to edit them and is what `check-tenant-audit-census.mjs` demands. Point ④ puts the arm in the enforced undefended table, so the table's prose had to name the third cause or the artefact would explain a row it does not cover. Measured before touching them, 2026-09-20T09:1xZ: **22 open PRs, 623 file rows** (PR objectstack-ai#17076 paged past the 100-row cap so the negative is not a truncation artefact) — **no open PR holds any of the four paths**, nor `scripts/ts-parse.mjs`. Firing control: the map does carry `scripts/` rows (objectstack-ai#19024, objectstack-ai#18414, objectstack-ai#19153, objectstack-ai#18723). Dark control: `scripts/zzz-no-such-file.mjs` has no row.⚠️ Also in the regenerated artefacts and NOT caused by this change: the unenforced corpus-scale block re-stamps its date and sha and moves 573 to 576 tracked sources, because `main` moved since it was last written. ## Acceptance notes — noted, not filed - Triage's **route ①** (stop collapsing the declared type text at storage time) stays available and would let such a receiver be PLACED rather than declared-undefended. It is a much wider change — the collapsed text is also the display text, the index-lookup input and the ledger key — and it is not what the sequenced hunk asked for. Successor: any later card that revisits the census's stored type text. Not a defect in the tree. - The same defect class at the transpile door (`transpileChecked` on a lifted snippet) was already filed as its own card by the `domain:spec#3` seat, with the boundary carried over. ⛔ No second card from here. - No path literal was added to `scripts/tenant-audit-census.mjs` or `ts-parse.mjs`; `check:watch-hint-literal` exits 0. Successor: none needed. --- _Generated by [Claude Code](https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho)_ Co-authored-by: Claude <noreply@anthropic.com>
…ured citations, and fold in the two-code sdui-parser port (objectstack-ai#17645) (objectstack-ai#19398) Fixes objectstack-ai#17429 Part of objectstack-ai#17645 — that card's deliverable (the two-code port) lands here; the director seat ends card 17645 with the landing record after the merge (a seat-written record, not a closing reference). The card's take-over from the offline `domain:devx` seat is the maintainer's instruction (「18723 os-try-charles 已下班,你也接手」), recorded on the card; the carrier reader's cross-author row (C9) cannot be cleared without the offline holder's own `Release:`, so the card is carried as `Part of`, not as a closing reference. Clause-②: yes Corrected from `no` under ruling batch objectstack-ai#201 item 1 (maintainer 「201 A」, record 5753263180 on objectstack-ai#17645): this PR now also carries objectstack-ai#17645's two-code sdui-parser port, so the save gate's diagnostic set grows (`inert-quick-add`, `member-type-mismatch`); the pin bump and the 27 re-measured citations alone moved no accept set. Both landed on this branch: the fold-in commit `6ef8344645` and the citation-spelling fix `fc25a5925d` (the head); the lockstep record needed no re-recording (see the fold-in section). (Pin-bump half, as originally declared:) Declared from the diff, not from the card: nothing in the pin bump adds, removes or renames an authorable key. The two `packages/spec` edits with a published face are `.describe()` SENTENCES (`Dataset` measure `format`, `FormField.span`); `check:authorable-surface` and `check:api-surface` both report their artefacts current with no regeneration, and `pnpm --filter @objectstack/spec check:generated` reports all 15 artefacts up to date after `gen:docs` and `gen:migration-registry` ran. The same authored metadata is accepted and refused as before, so neither arm of the closed pair applies. ## What moved `.objectui-sha`: `53ded82bf7a494f54e344e19099dbf00854b8694` → `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` (objectui `origin/main` tip read 2026-09-20; `cfcc17d9dd04` — the revision PR objectstack-ai#18723 ported from — is an ancestor of it, `git merge-base --is-ancestor` exit 0). Produced by `scripts/bump-objectui.sh --no-commit 87af769e9a3e`, never by hand. Everything the pin travels with: | Artefact | How | |---|---| | `.changeset/console-87af769e9a3e.md` | the bump script's own digest — 584 releasing changesets of 891 added across 1156 non-merge commits, `@objectstack/console: minor`, 98 declared-breaking entries listed | | `sdui.manifest.json` + `scripts/sdui-manifest.record.json` | `node scripts/gen-sdui-manifest-node.mjs`. The manifest is BYTE-IDENTICAL (sha256 `49211fee7792…`, 57 components): the pinned commit declares the same published `@object-ui` `17.6.0` the old pin did, so only the provenance record moves. `check:sdui-manifest` exit 0 | | `packages/sdui-parser/objectui-lockstep.json` | `pnpm gen:sdui-lockstep` against a worktree parked at the new pin (`--update` refuses off the pin). See the RED below | | 27 `.objectui-sha` citations under `packages/spec/src/**` | each re-MEASURED against the new pin's tree — see the table | | `content/docs/references/ui/{dataset,view}.mdx` | `gen:docs`, from the two corrected describes | | `.changeset/17429-pin-bump-describe-corrections.md` | `@objectstack/spec: patch` for those two sentences | Lockfile / override state: `pnpm install` in this worktree produced no `pnpm-lock.yaml` change, and the bump script writes none — the console SPA is vendored as a built `dist/`, not as a dependency, so this bump moves no dependency edge in this repo. ## Fold-in: the two-code sdui-parser port (ruling batch objectstack-ai#201 item 1, maintainer 「201 A」) Commit `6ef8344645` brings PR objectstack-ai#18723's port onto this branch **verbatim**. Eight files, each byte-identical to the blob on `claude/issue-17645-sdui-lockstep-port` (`git hash-object` here vs `git rev-parse claude/issue-17645-sdui-lockstep-port:PATH` there — all eight SAME, no hunk rewritten): | path | blob | |---|---| | `packages/sdui-parser/src/kanban-quick-add.ts` | `20f9f74b9f05` | | `packages/sdui-parser/src/validate.ts` | `156a264073f7` | | `packages/sdui-parser/src/types.ts` | `567bc1259bea` | | `packages/sdui-parser/src/index.ts` | `2ea35795b9cf` | | `packages/sdui-parser/src/codegen.ts` | `76a7d654f1fd` | | `packages/sdui-parser/src/__tests__/inert-quick-add.test.ts` | `723cad21333a` | | `packages/sdui-parser/src/__tests__/member-type-mismatch.test.ts` | `4b87cadaa067` | | `.changeset/17645-sdui-parser-lockstep-port.md` | `7c171fddfc43` | Deliberately NOT brought from that branch: `.objectui-sha`, `scripts/sdui-manifest.record.json` (both already this branch's, at `87af769e9a3e`) and `packages/sdui-parser/objectui-lockstep.json`. ### The lockstep record was neither text-merged nor re-recorded — and needed neither This branch already carries the record **taken at the live pin** in the pin-bump round: `objectui.rev` = `recordedAgainstPin` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2`, 26 diagnostic codes including `inert-quick-add` and `member-type-mismatch`. `--update` re-reads objectui's side only, so a re-record here would have rewritten the same bytes. Attempted anyway, and it REFUSED, which is the generator working: `pnpm gen:sdui-lockstep` exit 1, `[head-off-pin] the objectui checkout is at c2f0f48329de, and .objectui-sha names 87af769e9a3e`; record file byte-unchanged across the attempt (`e0a6988b1c9a` before and after). The remedy it prints — parking the sibling checkout on the pin — was not taken: that is a git write inside a read-only checkout this container shares with other agents, and the record it would produce is the one already committed. **Result — `check:sdui-lockstep` exit 0** on the head: `OK — this copy is byte-identical to objectui@87af769e9a3e over 214 grammar line(s) [blob 0131f27cf86d] and agrees on all 26 diagnostic code(s), across 8 non-test source(s)`. `--self-test` exit 0 alongside. `check:sdui-manifest` exit 0 (manifest intact, sha256 `49211fee7792`, recorded at the live pin). ### The type-check red PR objectstack-ai#18723 carried is closed by the pin, not by a code change The red was `check:objectui-pin-citations` inside the `Type Check · source gates` job. It reds exactly on the two heads of objectstack-ai#18723 that claimed `.objectui-sha` = `cfcc17d9dd04`; on this branch, with the live pin, the gate is exit 0 (27 asserting citations match, 48 historical recorded and not checked, 7 anchor content assertions verified). Reverse-verified here through `scripts/ablation-replace.mjs`: rewriting `.objectui-sha` to `cfcc17d9dd04` turns the gate RED with five anchor-content mismatches; restored and proven (blob back to `035b6937d641`, `git diff HEAD` empty). So the fix is the fold direction itself: the port lands on the branch whose citations are already re-measured at the pin. No edit was made inside `packages/sdui-parser/**` for it, and no `@ts-ignore` or `any` was added anywhere; `tsc --listFiles` confirms both new test files are inside the package program. ### Ported from `cfcc17d9`, re-verified against the LIVE pin objectui DID move `packages/sdui-parser` between `cfcc17d9dd04` and `87af769e9a3e`: one unrelated feature, objectui#6771's retired `body` child-list dialect (`body-dialect.ts` +99, `index.ts` +1, `validate.ts` +31/−7, its own test). It touches neither ported code and stamps only `not-a-container` and `unknown-prop`, both already in the 26-code set — which is why `26 = 26` holds at the live pin with it unported. Against objectui at `87af769e9a3e` the whole non-comment divergence of the five ported sources is four items, three pre-existing (`ValidationResult` vs `ManifestValidationResult`; the `code: 'inert-quick-add'` inline literal forced by `check:dispatcher-error-vocabulary`, pinned equal to the constant by a test row; `provenance.ts` and `assertFullyLoaded` / `RegistryConfigLike.lazy` absent here) and one new since `cfcc17d9` (`body-dialect.ts` absent here — see Acceptance notes). The port strictly REDUCES divergence in every file it touches: `validate.ts` 146 → 73, `index.ts` 114 → 92, `codegen.ts` 30 → 3, `types.ts` 96 → 85 diff lines vs objectui at the live pin. ### Ablations — re-run on this tree Three, each through `scripts/ablation-replace.mjs` (anchor ×1, mutation proven on disk by anchor count and blob change, restore proven by `blob == HEAD` AND an empty `git diff HEAD`): (1) `checkKanbanQuickAdd` stamps nothing — **5 failed / 8 passed** of 13; (2) the validator's member check reads no declared arms — **7 failed / 14 passed** of 21; (3) the codegen narrows no member type — **3 failed / 18 passed** of 21. Baselines 13 and 21 passed; in each leg the failures are the SUBJECT rows and every control row stays green. Both suites import `../index.js`, a relative intra-package specifier, so no rebuild is in the resolution path. ## Citation spelling fix (`fc25a5925d`) CI's `Lint & Repo Gates` went red on `check-issue-citations` at `6ef8344645`: two issue-number citations on docblock lines this branch rewrote. `packages/spec/src/ui/view.zod.ts` — `objectstack#17328` respelled bare `objectstack-ai#17328` (the issue exists, closed completed by merged PR objectstack-ai#17671; the gate probes only unqualified citations, so the this-repo-qualified spelling was judged against a board never asked for it). `packages/spec/src/ui/component.zod.ts` — `objectstack-ai#10274` measured **deleted** by `--probe-cause` (gone from the board and 404 on the web endpoint, not transferred); no replacement guessed — the sentence now states in prose that the number no longer resolves and names the live record (that block plus `check:objectui-pin-citations`). `node scripts/check-issue-citations.mjs` exit 0 at the head (31 judged, 0 findings); `check:generated` unchanged (TSDoc, not `.describe()`). ## Post-landing smoke evidence (triage's condition, issue comment 5690507317) ``` bash scripts/pm/os-verify-lock.sh -c 'bash scripts/build-console.sh' os-verify-lock: VERDICT command-exit 0 · held the lock 653s (10m53s) · waited 0s ✓ 8786 modules transformed. ✓ Bundle canary 'import/jobs' present — framework client is in the bundle. ✓ Console bundle carries THIS tree's @objectstack/spec, and only it. ✓ @objectstack/console dist ready (60228 KB) from objectui@87af769e9a3e ``` | Reading | Value | |---|---| | exit code | 0 | | `packages/console/dist` size | 60228 KB — 51,702,779 bytes, 3,285 files | | content hash | `sha256 259d2dfecaa12507fd3166a3f91c66c52581990e5ccac3899757027fa6ac108d` over `find … -type f \| sort \| xargs sha256sum \| sha256sum` | | provenance stamp | `packages/console/dist/.objectui-sha` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` | | `pnpm check:console-sha` | exit 0 — "Console dist matches the objectui pin" | The console build is GREEN at the new pin. 584 commits of objectui change came with it and none of them broke it. ## The citations — re-MEASURED, never re-pointed 27 asserting citations, all of them stale at the new pin. Each was re-derived by reading objectui at BOTH shas and comparing content, not by arithmetic on a line number. `pnpm --filter @objectstack/spec check:objectui-pin-citations` exit 0; `--verify-anchors` exit 0 with 7 of 7 content assertions verified against objectui at `87af769e9`. ### Corpus-absence records (7 citations + 6 generated copies) These cite a zero-hit count over the pinned tree, not a file:line. Re-counted with `git grep -F -o` at both shas; the METHOD was calibrated first by reproducing the old pin's own numbers exactly (6409 tracked files, `useState` 2304). | Record | Cited | Reads at `87af769e9` | Verdict | |---|---|---|---| | `18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts:48` | 13 exports of `plugin-lifecycle-advanced.zod.ts` + `debounceDelay` = 0 / 6409 files; controls `objectstack` 10171, `@objectstack/spec` 3479 | all still 0 / **8228** files; controls **12966**, **4997** | unchanged verdict, counts re-measured | | `18.kernel-runtime-config-timeout-unit-in-key.ts:45` | `resourceLimits.timeout` 0; controls `timeout` 832, `RuntimeConfig` 236, `resourceLimits` 2 | 0; **1075**, **240**, 2 — and both `resourceLimits` hits are still prose in `packages/app-shell` | unchanged | | `18.logging-durations-unit-in-key.ts:48` | 4 names 0; controls `useState` 2304, `timeout` **702** | 0; **2383**, **1075** | unchanged —⚠️ but `timeout` 702 was WRONG AT THE PIN IT NAMED: that corpus reads 832. An error since written, found only by re-measuring, corrected here | | `18.system-metrics-jsdoc-durations-unit-in-key.ts:70` | 6 def names + 2 keys 0; controls `window` 2710, `timeout` 832, `period` 160, `interval` 156, `metrics` 301 | all 0; **3464**, **1075**, **170**, **170**, **324** | unchanged | | `18.system-tracing-otel-exporter-durations-unit-in-key.ts:66` | 37 exports of `tracing.zod.ts` + 4 key names 0; `Span` 404 / `SpanSchema` 40 unrelated; controls 10171, 3479 | all 0; **486** / **53**; **12966**, **4997** | unchanged | | `18.tenant-schema-cache-ttl-unit-in-key.ts:31` | `schemaCacheTTL` 0; controls `TTL` 112, `tenant` 819 | 0; **156**, **976** | unchanged | | `migrations/registry.ts` ×6 | the generated concatenation of the six above | regenerated with `pnpm --filter @objectstack/spec gen:migration-registry` | follows its entries | ### Read-point records (20 citations + 7 content assertions) | Record | Anchor, as cited | At `87af769e9` | Verdict | |---|---|---|---| | `data/api-methods-batch-conformance.test.ts:64` | `ObjectGrid.tsx:3538-3553` | `:3940-3955` | MOVED, span byte-identical (`git hash-object` `6133933199…` both sides) | | | `hooks/useBulkExecutor.ts:284-289` | `:298-303` | MOVED, span byte-identical (`0108334833…`);⚠️ the FILE is no longer byte-identical (+36/-22), so the record's old identity argument is gone and the span was re-read | | `ui/component.zod.ts` tab-item `icon` + `ui/component.test.ts:376` | `containers.tsx:730-736` | `:853-859` | MOVED, 7 lines byte-identical | | | `containers.tsx:789` (registration `items` input) | `:912` | MOVED **and the LINE CHANGED** — it lost `label: 'Tabs'`, gained `of: 'object'` and a longer description; the member list the record cites is unchanged | | `ui/component.zod.ts` accordion-item `icon` + `component.test.ts:286` | `containers.tsx:919-925` | `:1069-1075` | MOVED, byte-identical | | | `containers.tsx:966` | `:1116` | MOVED **and the LINE CHANGED**, same way | | `ui/component.zod.ts` button `icon` + `component.test.ts:3489` | `form/button.tsx:43`, `:72`, `:74` | unchanged | UNCHANGED | | | `form/button.tsx:85-102` (inputs), `:103-107` (defaultProps) | `:85-97`, `:98-102` | MOVED **and SHRANK** — every input dropped its `label` and `defaultValue`; the four input names and the ABSENCE of an `icon` input, which is what the record asserts, both hold | | | `action/resolve-icon.ts:129-132` (`resolveIcon`) | `:322-328` | MOVED **and REWRITTEN** — the tail no longer indexes `lucide-react`'s `icons` record; it asks `recordIconName` and hands the pair to `lazyIconComponent`. Accept/reject behaviour unchanged | | | `resolve-icon.ts:117-120`, `:100-105`, `:90-92` | `:302-305`, `:153-158`, `:143-145` | MOVED, byte-identical | | | `lib/lazy-icon.tsx:66-92` | `:98-124` | MOVED, byte-identical | | `ui/component.zod.ts` object-metric `icon` | `plugin-dashboard/src/index.tsx:204` | `:237` (registration now opens at `:227`) | MOVED **and the LINE CHANGED** — the input's `label: 'Icon (Lucide name)'` is GONE; the key is still published, now as a bare `{ name: 'icon', type: 'string' }` | | | `ObjectMetricWidget.tsx:142` / `:474`; `MetricWidget.tsx:312-321` / `:373-382`; `lazy-icon.tsx:66-80` | `:174` / `:483`; `:351-360` / `:412-421`; `:98-112` | MOVED, byte-identical | | `ui/component.zod.ts` kanban `limit` ×2 + `component.test.ts:3360` | `ObjectKanban.tsx:264` (`$top: schema.limit ?? DEFAULT_KANBAN_LIMIT`) | `:676` (`$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT)`) | MOVED **and REWRITTEN** — objectui#9925 put a refusal in front of it (a contract-refused cap is dropped and reported at `:553`). The pinned fact, that `limit` lowers into the query's top-level `$top`, holds | | | `ObjectKanban.tsx:71`; `plugin-kanban/src/index.tsx:395-398` | `:84`; `:447-450` | MOVED, byte-identical | | | `ObjectKanban.tsx:143` + `plugin-kanban/src/types.ts:134` (`KanbanSchema.limit?: number`) |⚠️ **VANISHED** | `KanbanSchema` was RETIRED at this pin (maintainer ruling 2026-09-09) and `types.ts` declares the member no more. Re-derived rather than re-pointed: the published twin is `ObjectKanbanSchema`, imported at `ObjectKanban.tsx:10`, declaring `limit?: number` at `packages/types/src/objectql.ts:3735` | | | `ElementDataSourceGate.tsx:236-241` | `:316-331` | MOVED **and EXTENDED** — the branch gained objectui#9899's presence-is-not-authorship test and a `describeDisplacedRowLimit` report | | `ui/component.zod.ts` kanban `quickAdd` + `component.test.ts:3427` | `ObjectKanban.tsx:931`; `plugin-kanban/src/index.tsx:196`; `KanbanImpl.tsx:355` / `:368` | `:1563`; `:313`; `:621` / `:634` | MOVED, byte-identical. The absence re-counted at this pin: `quickAdd` / `onQuickAdd` 0 each in `ObjectKanban.tsx`, against 11 (was 6) for the sibling `onCardClick` | | `ui/dataset.zod.ts:207` measure `format` | `dataset-format.ts:185-198`, `date-display.ts:131-164` / `:117`, `:195` | `:229-264` (routed at `:370`), `:198-233` / `:152`, `:260-262` |⚠️ **SUBSTANCE REVERSED** — see below | | `ui/view.zod.ts:2945` `FormField.span` | the widest-tier-only class claim at `53ded82bf7` | `spanLadderFor`, `form/form.tsx:204-231` |⚠️ **SUBSTANCE REVERSED** — see below | | `ui/view.zod.ts` `ListMapConfig`, 7 content assertions | `ListView.tsx:113` / `:67`; `ObjectView.tsx:1381`; `objectql.zod.ts:562` / `:313`; `ObjectMap.tsx:373` / `:378` | `:146` / `:85`; `:1764`; `:1574` / `:734`; `:385` / `:390` | all 7 MOVED;⚠️ one LOST ITS SYMBOL — `FLAT_MAP_CONFIG_KEYS` became the total `FLAT_MAP_CONFIG_SPELLING` map (objectui#9950), so `style` now DOES reach the flat product, as `mapStyle`. The quote was re-read, not re-pointed | ### The two records whose SUBSTANCE the range reverses Both are claims about what the SHIPPED renderer does, so the pin is what dates them, and both were re-stated from the new tree. Each also carried a published `.describe()` saying the same now-false thing, which this bump is what falsifies — so both sentences are corrected here and `content/docs/references/ui/{dataset,view}.mdx` regenerated from them. That is the `@objectstack/spec: patch` changeset in this PR. - **`Dataset` measure `format`** said "a datetime value ignores it". objectui#8352 is inside the range: `formatMeasureDate`'s datetime arm now SELECTS a formatter — `relative` to `formatRelativeDate` (`:260`), `short` to `formatDateTime(v, { locale, style: 'compact' })` (`:261`), everything else to the bare `formatDateTime(v, { locale })` (`:262`). A date PATTERN is still ignored on both arms, which is the half that survives. - **`FormField.span`** said only the widest container-query tier's class is emitted, so a `'full'` field was one cell of two at 720px (objectstack-ai#17328). objectui#9244 / objectui#9253 (`bd09957380`) are inside the range: `spanLadderFor` emits one clamped col-span class per multi-column tier. ⭐ The previous revision of this block asked in writing to be re-read at the bump that absorbed it; this is that re-read. The `'auto'` half — textarea, markdown, html, richtext and repeater — re-measured UNCHANGED. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (122 commands for this 19-path, 757-line change set). Run locally, exit codes captured before any pipe: ``` pnpm check:sdui-lockstep EXIT 0 (after the fold-in: 26 = 26 codes, grammar region byte-identical) pnpm --filter @objectstack/spec check:objectui-pin-citations EXIT 0 (27 asserting citations match, 7/7 anchors verified) … --verify-anchors EXIT 0 node scripts/check-sdui-manifest.mjs EXIT 0 pnpm check:objectui-bump EXIT 0 pnpm check:objectui-changeset EXIT 0 pnpm check:console-sha EXIT 0 pnpm --filter @objectstack/spec check:generated EXIT 0 (15/15 artefacts current) MANIFEST=… check:react-declaration-parity EXIT 0 (reading unmoved: the manifest is byte-identical) node scripts/check-adr-0087-registration.mjs --base origin/main EXIT 0 node scripts/check-changeset-no-major.mjs --base origin/main EXIT 0 node scripts/check-empty-changeset.mjs --base origin/main EXIT 0 pnpm lint EXIT 0 (repo-wide, eslint . --no-inline-config) pnpm --filter @objectstack/spec test EXIT 0 (503 files, 14710 tests) pnpm --filter @objectstack/spec --filter @objectstack/sdui-parser typecheck EXIT 0 ``` plus 27 further derived families, every one exit 0: `check:nul-bytes`, `check:merge-driver`, `check:watch-hint-literal`, `check:comment-mask-adoption`, `check:comment-mask-corpus`, `check:doc-frontmatter`, `check:docs-section-name`, `check:scripts-symbol-anchors`, `check:spec-docblock-symbol-anchors`, `check:ci-filter-parity`, `check:keyed-text-bounds`, `check:undeclared-dep-imports`, `check:section-landing-index`, `check:liveness`, `check:duration-unit-keys`, `check:llms-txt`, `check:spec-changes`, `check:upgrade-guide`, `check:strictness-ledger`, `check:export-origins`, `check:api-surface`, `check:authorable-surface`, `check:docs`, `check:doc-anchors`, `check:docs-spec-enumerations`, `check:quick-reference-counts`, `check:corpus-claim-drift`, `check:changeset-gate-self-tests`, `check:pm-widening-tells`, `check:published-files`, and both changeset self-tests. The rest of the 122 are left to CI, which runs the whole farm. `origin/main` moved 7 commits while this was built and touches NONE of this diff's 19 paths, so no merge was taken; the queue's rebuilt generation is what validates the join. ## Acceptance notes - `check-issue-citations` blind spot (measured on objectstack-ai#17328 at `6ef8344645`): a citation qualified with this repository's own name (`objectstack#N` or `owner/repo#N`) is recognised as naming THIS repo and resolved against the board, but the board is only asked for unqualified citations and `--probe-cause`'s guard is `!cite.qualifier` too — a live issue reads `allocated-but-absent` and NOT MEASURED under `--probe-cause`. Not fixed here (the gate is not this PR's surface); recorded for the next author of `scripts/check-issue-citations.mjs`. - objectui's `packages/sdui-parser/body-dialect.ts` (objectui#6771, landed between `cfcc17d9dd04` and the live pin) has no counterpart in this copy: for an authored `body` key the two copies stamp different codes (`not-a-container` under a non-container there, the bare `unknown-prop` here), invisible to `check:sdui-lockstep`'s code-SET comparison because both codes are already in the 26-set. Both severities are `warning`, so `compile().ok` and the save verdict are unchanged. A third code path the ruling does not name; carrier: the next sdui-parser port round. - `provenance.ts` and `assertFullyLoaded` / `RegistryConfigLike.lazy` remain absent from this copy (zero diagnostic codes; objectstack-ai#18723's acceptance notes already record `provenance.ts`). `pnpm gen:sdui-lockstep` is unrunnable in this container because the shared sibling checkout sits off the pin and the generator's remedy is a git write in someone else's tree; it cost nothing this round and will bite the next pin bump, which must park its own checkout. - `.changeset/18516-span-auto-wide-types.md` (pending, not yet released) describes the `FormField.span` describe text this PR corrects, including "At this pin only the widest tier's class is emitted". `check:empty-changeset` refuses any diff that MODIFIES a changeset present at the merge base, so it is deliberately left untouched here and reported instead. - The `element:button` `icon` `.describe()` still says the renderer resolves through `lucide-react`'s `icons` map. At the new pin that is one hop indirect (`recordIconName` + `lazyIconComponent`) but the glyph set and the accepted spellings are unchanged, so it was left as written rather than churned. Original author: the director seat's `os-dev` rounds (pin bump; fold-in `6ef8344645`; citation fix `fc25a5925d`), session_012GcsUbuqFGBibkEDMRC1eE; body maintained by the director seat, `session_012GcsUbuqFGBibkEDMRC1eE`. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17645
Clause-②: yes
Executes the director-seat ruling in card comment 5714974800 (decision batch #146 item 3), maintainer's verbatim 「146 同意」 — letter A: port
inert-quick-addandmember-type-mismatchinto this repo'spackages/sdui-parser, re-record the lockstep and bump the pin in the same PR.Ported from
objectstack-ai/objectui(READ-ONLY clone in the dispatch container)cfcc17d9dd04ebb80c092cacb4f4034425f51b29, 2026-09-14T11:40:39Zpackages/sdui-parser/src/{kanban-quick-add.ts,validate.ts,types.ts,index.ts,codegen.ts}What each code actually guards
A reviewer should be able to tell a real port from a string copied across an enum. Neither code is a new red gate: both are warnings, so
compile().ok— the save gate's pass/fail — is unchanged and a page that saves today still saves.inert-quick-add(objectui#8285)quickAddon theobject-kanbantag reaches no control. The Quick Add button is gated on both halves of a pair —quickAddand anonQuickAddhandler — andonQuickAddtakes a FUNCTION, which no page on this tier can write (this tier parses, it never executes; a handler attribute drawsforbidden-attr) and which the board substitutes none of its own for.It replaces the diagnostic the prop walk used to emit for that key rather than joining it. The old reading was
unknown-prop, "has no prop quickAdd" — false against the published contract, becauseComponentPropsMapfor that tag does publishquickAdd. An author who checked the spec found the warning contradicted, concluded the manifest was behind the spec, and kept a key that will never do anything; it also could not be told apart from a typo. Two diagnostics for one mistake is the shapecheckMemberTypesalready refuses, so this onecontinues the walk.It is asked ahead of the declaration lookup on purpose: the claim is about the render path, which a declaration cannot change, so declaring
quickAddmust not silently disarm it. Three things it deliberately does NOT fire on — a falsy value (the renderer's own gate isquickAdd && onQuickAdd, so the author got what they wrote), a braced value this tier never evaluates (opaque here), and any tag outside the one surviving host registration. Each has a control row in the tests.member-type-mismatch(objectui#8067)The coarse type check one level down, over the member kind an input declares.
type: 'array'said a value was a list and stopped there, so a member that drifted from the contract was invisible to every layer that reads a declaration.This is the half that makes it a port and not a string: the code needs a key that did not exist in this copy at all. The change brings
ManifestInput.ofand all three of its readers:errorseverity when anenumarm is present andwarningotherwise;manifestFromConfigscanonicalizesofexactly as it canonicalizestype, and publishes NOoffor an input that declares none (routingundefinedthroughcanonicalizeInputType, whose no-arms fallback is'string', would make every array in every manifest claim string members it was never told it had);.d.tsan author type-checks against and the manifest gate agree.The member walk runs only once the CONTAINER kind was accepted, so a wrong container draws one diagnostic and not two.
Counts, each with a firing control
Base for every diff reading:
51297e9ee17db23f50d38c1431f5a4dcc47632ff.Diagnostic codes — 26 = 26, sets identical element-for-element. Measured with the gate's own AST extractor (
fingerprintOf), both trees, before and after:cfcc17dThe delta was exactly
inert-quick-addandmember-type-mismatch— matching the card's 26/24 reading, so acceptance item 4 is met and nothing was absorbed.The grammar region did not move across the pin hop. This repo's
parse.tsregion and objectui's atcfcc17dare both 214 lines, blob0131f27cf86dbabec09b4ae55ae131403667e7e9— byte-identical, and identical to what the old record held at53ded82bf. So the pin bump ports no grammar change.ofdeclarations in the tracked manifest: 0 of 339 inputs. FIRING CONTROL on the same file: the term"inputs"occurs 57 times insdui.manifest.json, andgit diff BASE HEAD -- sdui.manifest.json | grep -c '"inputs"'reads 0 — the whole-file diff is 0 bytes. So the zero is a reading about the tree, not an artifact of this diff. The discriminating contrast: the same grep forof?:overgit diff BASE HEAD -- packages/sdui-parser/srcreads 2, so the term is one this diff really moves where it moved it.member-type-mismatchhas no firing input in today's shipped manifest, because that manifest is generated from the PUBLISHED@object-ui/*17.6.0 packages, which predate theofdeclarations. It is NOT inert — objectui's SOURCE atcfcc17ddeclaresofin 38 places (e.g. the tabs and panelsitemsinputs incontainers.tsx), so those flow in at the next publish+pin, and the reader is pinned here through the real serializer rather than a hand-built fixture. Without this port, this repo's save gate would silently accept member drift that objectui's renderer reports.objectui was never written to.
git status --porcelainin that clone: 0 files.HEADstillcfcc17d9dd04ebb80c092cacb4f4034425f51b29.Ablation ledger
Three ablations, each through
scripts/ablation-replace.mjs— anchor declared and required to HIT, before/after anchor counts,git hash-objectblob ids either side, restore proven byblob == HEADAND an emptygit diff HEAD, with an EXIT/INT/TERM trap. The tests resolve the subject throughsrc(a relative intra-package import, nodisthop), which is why these run without a rebuild — measured: the suite passed beforedistexisted in this worktree at all.checkKanbanQuickAdd's key guard0d5131868ace→99d89132858e156a264073f7→5cb7912d7f5476a7d654f1fd→ba55afbc8c19In each case the failures were exactly the SUBJECT rows and every control row stayed green — which is the split the control rows exist to produce. A first attempt at ablation 1 was REFUSED by the tool because the replacement still contained the anchor (count 1 → 1, "not the declared drop"); it is recorded here because the refusal is the tool working, and the run that produced the reading above is the corrected one.
The pin bump, and its second and third halves
.objectui-sha:53ded82bf7a494f54e344e19099dbf00854b8694→cfcc17d9dd04ebb80c092cacb4f4034425f51b29.Second half — the manifest — is done and is a no-op in content.
node scripts/gen-sdui-manifest-node.mjs --objectui-version 17.6.0regeneratessdui.manifest.jsonbyte-identical: sha25649211fee7792cf51before and after. Both pins ship@object-ui/*17.6.0, so the published registry inputs did not move; only the record'sobjectuiShaandgeneratedAtfollow the pin.check-sdui-manifestis green.Third half —
check:objectui-pin-citations— is RED and is NOT closed by this PR. See "Acceptance notes".What the record cannot see
check-sdui-lockstepcompares this tree against a VENDORED record of objectui's side. It can see this copy drifting from the recorded objectui state; it cannot see objectui moving AFTER the record was taken. This record was taken atcfcc17d(2026-09-14) — and the card's own reading of objectui is from 2026-09-16. Anything that landed in objectui'spackages/sdui-parserin those two days is outside this record and outside this PR's evidence. The gate says so itself on every green run, and only a later pin bump brings the question back. The container's objectui clone is shallow with exactly one commit, so this cannot be narrowed here.Gates
scripts/pm/dispatch-gates.mjsderived 67 families for this diff; re-derived aftergit fetchand the list was IDENTICAL, so the tool's STALE TREE warning does not move the answer. Reconciled with--ranrecording every exit code: 67 derived, 64 run, 3 NOT-MEASURED, 0 UNRUN (exit 0).check:sdui-lockstep— green, both the gate and--self-test: byte-identical over 214 grammar lines and agreeing on all 26 codes. ⛔ The invariant was never relaxed to buy this:--updatere-reads OBJECTUI's side only and never this tree's parser, so it cannot record "whatever we have now"; the green is the port being faithful.check:sdui-manifest— green (present, intact, fresh at the new pin).pnpm --filter @objectstack/sdui-parser test && … typecheck— green, 176 tests / 10 files.check:nul-bytes— green; plus a direct control-byte sweep over the changed files.check:dispatcher-error-vocabulary— was RED on this port and is now green; see "The one deliberate divergence".check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debteach exited 3 = PREREQUISITE NOT MET (they need a fullpnpm build), which is neither a pass nor a failure.check:query-options-erasure— green at 192s; an earlier 124 was this runner's own 150s timeout, not a finding.check:objectui-pin-citations— RED, deliberately left open. See below.The one deliberate divergence from objectui
objectui stamps
code: INERT_QUICK_ADD. This copy stamps the inline literalcode: 'inert-quick-add'and keeps the exported constant. This is not drift and not a shortcut — it is the same documented divergenceunconsumed-widget-optionalready carries next door:check:dispatcher-error-vocabulary'sobjlitconstshape must reduce a constant at acode:position to a literal, and its literal grammar cannot admit a kebab-case value, so the constant form is an unresolvable-constant finding that cannot be declared away. The gate caught this port doing it, which is the gate working.⛔ It does not weaken the lockstep: that gate resolves an identifier at a code position through the package's own module-level constants, so both spellings reduce to the same code and the two copies still agree on all 26. A test row pins the literal equal to the constant so the two cannot drift apart.
Changeset
OWED, and measured rather than assumed —
.changeset/17645-sdui-parser-lockstep-port.md,minor. The criterion is whether anything PUBLISHED moves:@objectstack/sdui-parsershipsfiles: ["dist", …], and after a build both codes are present in the shipped bytes, against a negative control that reads zero everywhere:inert-quick-addmember-type-mismatchcheckKanbanQuickAddunconsumed-widget-option(positive control)NEVER_SHIPPED_CONTROL_STRING(negative control)That reading is also the answer to the Clause ② question the card asked to have re-judged if it measured otherwise: these codes DO reach a published surface, so the
needs:contract-reviewdeclaration stands. The package's exported API grows by four symbols and one optional type key, which is why this isminorand notpatch.Acceptance notes
check:objectui-pin-citationsis RED and this PR does not close it — this needs a decision.The pin bump invalidates 26
packages/spec/**sources that cite.objectui-shaas "the pin this repo builds against" alongside hand-derived objectuifile:lineanchors. The gate refuses the cheap repair in terms: ⛔ "Do NOT fix this by replacing the sha… The sha is not the record — the objectui file:line ANCHORS beside it are, and they are only true of the tree they were counted in." Rewriting the sha alone would turn a loud, accurate "unverifiable" into a silent, confident lie.Two reasons it is not done here, both measured:
packages/sdui-parser/,scripts/. Re-measuring 26 spec sources of hand-written cross-repo provenance prose is not a bounded in-place fix.git cat-file -t cfcc17d9dd04…answerscommit, while the old pin53ded82bf7a4…answers "could not get object info" — a firing control on a present object, so the absence is real. Deepening it would mean git WRITES inside a READ-ONLY checkout belonging to someone else, which is exactly whatcheck-sdui-lockstep's own header declines to do for the same reason.So this is a genuine third half of "bump the pin" that letter A did not price. Landing it needs either a follow-up card scoped to
packages/spec/**with a non-shallow objectui checkout, or a maintainer ruling. ⛔ Not absorbed silently, and #17645 is the only card this PR closes.Other observations, noted and not filed — neither is a defect, and no PR or person is queued on either file, so there is no taker to hand them to:
packages/sdui-parser/src/provenance.tsexists in objectui and not here. It is deliberately out of scope: it stamps zero diagnostic codes, so the lockstep's code-set comparison is unaffected (26 = 26 with it absent), and the gate does not compare file lists.index.tsalso exportsassertFullyLoadedand its codegen emits aneslint-disablebanner; neither is part of either ruled code and neither is carried here.Generated by Claude Code