feat(devx): mechanise the sdui-parser lockstep against a vendored record of objectui's side - #13362
Merged
Merged
Conversation
…ord of objectui's side Two copies of the constrained-JSX parser exist: objectui's runs in the renderer, this one runs in the save gate, and #12719 states that they must byte-agree on the accepted grammar and on diagnostic codes. That invariant was enforced by nothing through three manual ports. `check:sdui-lockstep` recomputes this copy's grammar-region blob id and its diagnostic-code set and compares both against a vendored record of objectui's side taken at a named revision. No network, no build and no objectui checkout at check time; `pnpm gen:sdui-lockstep` re-records objectui's side only, so it cannot launder a divergence here. The code extractor resolves a SCREAMING_SNAKE constant at a code position and refuses on one it cannot resolve: the same code is a quoted literal here and a constant reference on objectui's side, so a literal-only scan reports a false 24-vs-23 difference on day one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
Contributor
📓 Docs Drift Check
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): |
This was referenced Aug 30, 2026
os-elon
marked this pull request as ready for review
August 30, 2026 07:57
os-elon
enabled auto-merge
August 30, 2026 07:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #13150
Part of, not a closing keyword, on purpose: the mechanism is complete and green, but it deviates from the card's shape A on one detail the card pinned by name, and the deviation is a measurement rather than a preference. That is for the PM to accept before the card closes — see "The card's premise that measurement falsified" below.What lands
check:sdui-lockstep— one gate script, one self-test, one generated record, wired unfiltered into theLint & Repo Gatesjob.It recomputes this copy of
sdui-parserand compares it against a vendored record of objectui's side (packages/sdui-parser/objectui-lockstep.json) taken at a named objectui revision. Two halves, both from the card:No network, no build, no objectui checkout at check time. Measured cost: ~0.4 s, one TypeScript parse of seven files.
pnpm gen:sdui-lockstepre-records objectui's side from a local checkout (OBJECTUI_ROOT, else../objectui).Why
--updatecannot launder a divergence. It reads objectui's side only — it never opens this tree's parser, so it cannot record "whatever we have now" as accepted. Run it after a port and either the port was byte-faithful and the gate goes green, or it was not and the gate stays red naming the difference. This is the opposite ofpackages/spec/react-declaration-parity.baseline.json, which records a diff and ratchets it; this record holds one side, so there is no diff in it to bless.The re-derived pricing (the card's numbers were taken before PR #13154 merged)
Measured on this branch against objectui
main:0131f27cf86dbabec09b4ae55ae131403667e7e9— objectstackparse.ts:313, objectuiparse.ts:314The
23is off by one in the card and in the #13154 verification note. Both were line-anchored scans;expression-childis stamped by a call whose code argument sits on its own line (parse.ts:161here), so a line-anchorederror('…')grep does not see it. The conclusion is unchanged — the sets are identical — but the number is not, which is exactly why the gate derives it instead of pinning it.The constant-vs-literal trap is handled, and the counterfactual is measured
The same code is a quoted literal here and a constant reference on objectui's side:
Counterfactual, run: a literal-only extractor over the same two trees reports 24 here vs 23 on objectui — the false difference, at the corrected counts. The extractor here resolves an identifier at a code position through the package's own module-level string constants, and refuses on one it cannot resolve (an imported constant, say): silently skipping the unresolvable one is the same false difference with the evidence removed. Both halves are pinned in
--self-test.Reading the AST rather than the text matters for a second reason on this file: the comment that documents the divergence contains the phrase
code: UNCONSUMED_WIDGET_OPTION, and theDiagnostictype and the emitter's ownerror(code: string, …)parameter both spellcode:without stamping anything.The card's premise that measurement falsified
The card and the triage comment both say to record objectui's side at the sha
.objectui-shaalready pins. That is not implementable, and the reason is a fact about the pin rather than about the parser:At the pinned sha objectui's
parse.tsis 302 lines with no delimiter comment at all and noinert-expressiondiagnostic: the ported block does not exist there. A record taken at the pin would record a state this copy must not match, and the gate would be red on day one for pin-lag reasons that have nothing to do with drift — or, if the difference were written into the record as accepted, the gate would be vacuous from the first commit.So the record carries its own objectui revision (
objectui.rev, with its commit date), and separately records the pin that was in force when it was taken..objectui-shaand the parser lockstep move on different cadences: the pin governs which console SPA is built and shipped; the lockstep is a source-to-source relationship.What the gate can reach, and what it cannot
The one offline instrument that reaches into the second class is the pin clause, and it is why the gate reads
.objectui-shaat all rather than merely naming it..objectui-shanames the objectui build whose renderer ships, so a bump is the moment a parser divergence stops being latent and starts being shipped — and it is the one moment somebody is guaranteed to have an objectui checkout in hand, since the pin cannot be bumped without one. When the live pin has moved past the recorded one, the gate refuses with a one-command remedy.Console Pin Freshnessgate (#10134, maintainer ruling 2026-08-20: which objectui revision we pin is a decision recorded in an objectstack issue, never derived from objectuimain). Nothing here reads objectuimain, nothing proposes a pin, and no pin bump is ever called stale by this gate. It asks a different question: given that you are moving the pin, has parser parity been re-verified against what you are moving it to.The #12956 constraint, verified end to end
The triage comment required that a
.objectui-shadiff name this gate rather than the gate being picked up by accident. The plain'.objectui-sha'literal does not do it — measured:extractWatchHintsadmits a leading-dot literal only from a fixed allowlist (.claude,.changeset,.github,.gitattributes), so a root dotfile outside it falls through both that admission andhintCovers' bare-word refusal. The gate therefore declares the documented subtree spelling (ROOT_DIR_WATCH_HINTS = ['.objectui-sha/**'], the idiomcheck-agent-test-spelling.mjscarries asAGENTS.md/**), pinned in--self-testin both directions so the declaration cannot drift from the file the gate opens.Before / after,
node scripts/pm/dispatch-gates.mjs .objectui-sha:and
packages/sdui-parser/src/parse.tsderives it too, viagate source 'packages/sdui-parser/src'.Reverse verification — five ablations, each proven on disk
Run from the committed implementation; each leg proved its mutation reached disk (removed-text count 0, injected-text count ≥ 1, file blob id ≠ its
HEADblob id, an empty hash treated as failure), restored withgit checkout HEAD -- ABSOLUTE_PATHunder anEXIT/INT/TERMtrap, and proved the restore by an emptygit diff HEAD. Finalgit status --porcelainwas empty. No build ordist/is involved — the gate reads source and a JSON record, so there is no rebuild leg to report.parse.ts[grammar-drift], region blobc115f92b…vs recorded0131f27c…'not-a-container'→'not-a-containerX'[code-drift]— only herenot-a-containerX, only objectuinot-a-containerREFUSED … Nothing was compared.objectui-sha[pin-moved]Leg C is the one that matters: the naive implementation the triage comment warned about is green on A/B/D/E too and red on C, which is the false positive that would have made the gate untrustworthy on day one.
Local verification
Gate families derived from the actual diff by
node scripts/pm/dispatch-gates.mjs(no paths — the tool computes the change set off the merge base), re-run at the final commit3c7936a46don a clean tree: 42 of 45 green, including the two convention-triggered obligations a new gate script incurs (node scripts/pm/bare-root-worklist.mjs --self-test,pnpm check:pm-dispatch-gates— 897 cases).The other three are NOT MEASURED, not red, and each says so itself:
node scripts/check-test-completeness.mjs— exit 3,Nothing was measured(it needs aturbo run testlog CI tees; there is none locally).pnpm check:dual-build-cjs-loads— exit 3,PREREQUISITE NOT MET … ⛔ This is NOT a pass: nothing was measured(needs every package'sdist/).pnpm check:type-check-debt— needs the built workspace closure; its siblingcheck:type-check-coverageran green. This diff adds no TypeScript and no package export, so neither can be moved by it; CI runs both regardless.Also run:
pnpm --filter @objectstack/sdui-parser run typecheck(clean) and its suite (6 files, 130 tests passed);pnpm check:nul-bytes(7408 files, none) plus a directgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over every touched file (no match).ESLint — a declared narrowing, with its three readings. The repo-wide
eslint . --no-inline-configis CI's run; locally the changed files were linted directly:--format jsonreports 2 entries, 1 in population —scripts/check-sdui-lockstep.mjs, 0 errors 0 warnings — and the record JSON reported by ESLint's own config asFile ignored because no matching configuration was supplied, i.e. outside the linted population by the config's account rather than by my guess. The narrowing excludes nothing: this repo's singleeslint.config.mjsnever enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for any file — stated and measured with a positive control in that file's own header — so nothing in this diff can move the verdict on a file it does not touch, and no ESLint config, ignore file or tsconfig is edited here.Declarations
origin/main; the only files touched are a new gate script, a new generated record, apackage.jsonscript pair and alint.ymlstep.@objectstack/sdui-parserpublishesdist,README.md,CHANGELOG.md(filesin its manifest); the new record sits at the package root and ships in none of them —pnpm check:published-filesgreen. No new runtime dependency: the gate imports onlynode:*, the repo's ownscripts/ts-parse.mjs/scripts/invoked-as.mjs, and the workspace's existingtypescript.skip-changesetapplied: this PR releases nothing. Tooling, a workflow step and a non-published data file only.docs/adr/**,.claude/**,skills/**,AGENTS.mdorCLAUDE.mdin the diff.Open question for the maintainer
The pin clause is the one design decision worth ratifying rather than assuming, because it couples two cadences that are otherwise independent: it makes a
.objectui-shabump requirepnpm gen:sdui-lockstep, and if that regeneration surfaces an unported objectui parser change, the pin bump goes red until the port is done. My reading is that this is the invariant rather than a spurious coupling — a pin bump that ships a renderer speaking a newer dialect than this repo's save gate is the #12719 failure mode, arriving in production — but it is a policy choice about when re-verification is due, and it is cheap to drop (delete thepin-movedbranch and its self-test cases) if the maintainer would rather the two move independently.Generated by Claude Code