Skip to content

feat(devx): mechanise the sdui-parser lockstep against a vendored record of objectui's side - #13362

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-13150-sdui-parser-parity-check
Aug 30, 2026
Merged

feat(devx): mechanise the sdui-parser lockstep against a vendored record of objectui's side#13362
os-elon merged 1 commit into
mainfrom
claude/issue-13150-sdui-parser-parity-check

Conversation

@os-elon

@os-elon os-elon commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

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 the Lint & Repo Gates job.

It recomputes this copy of sdui-parser and 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:

  • grammar region — the ported block runs from the literal delimiter comment both copies carry to end of file, so it is addressable without a parser. Compared by git blob id.
  • diagnostic-code set — every code either copy can stamp, read from the AST (not a text scan).

No network, no build, no objectui checkout at check time. Measured cost: ~0.4 s, one TypeScript parse of seven files.

pnpm gen:sdui-lockstep re-records objectui's side from a local checkout (OBJECTUI_ROOT, else ../objectui).

Why --update cannot 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 of packages/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:

card said measured now
grammar region 214 lines, identical blob 214 lines, identical blob 0131f27cf86dbabec09b4ae55ae131403667e7e9 — objectstack parse.ts:313, objectui parse.ts:314
diagnostic codes 23 each side, sets identical 24 each side, sets identical

The 23 is off by one in the card and in the #13154 verification note. Both were line-anchored scans; expression-child is stamped by a call whose code argument sits on its own line (parse.ts:161 here), so a line-anchored error('…') 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:

this repo   code: 'unconsumed-widget-option'      packages/sdui-parser/src/dashboard-widget-options.ts
objectui    code: UNCONSUMED_WIDGET_OPTION        (export const … = 'unconsumed-widget-option')

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 the Diagnostic type and the emitter's own error(code: string, …) parameter both spell code: 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-sha already pins. That is not implementable, and the reason is a fact about the pin rather than about the parser:

.objectui-sha            9602dc820450…   2026-08-25
objectui main            (today)         2026-08-30      211 commits ahead
objectui commits touching packages/sdui-parser in that range:  2
  470365140  feat(sdui-parser): materialize the JS literal subset (#6669)
  0db4fb36c  fix(sdui-parser): braced non-JSON values draw an inert-expression warning (#6613)

At the pinned sha objectui's parse.ts is 302 lines with no delimiter comment at all and no inert-expression diagnostic: 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-sha and 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

  • CAN see — this copy moving away from the recorded objectui state: a port that is not byte-faithful, a local edit to the lockstep region, a codemod that reformats it, a code added or deleted on one side only.
  • CANNOT see — objectui moving after the record was taken. That direction needs objectui at check time, which is shape B, out of scope by ruling. It is stated in the gate header, in its green output on every pass, and here.

The one offline instrument that reaches into the second class is the pin clause, and it is why the gate reads .objectui-sha at all rather than merely naming it. .objectui-sha names 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.

⚠️ This is not a resurrection of the retired Console Pin Freshness gate (#10134, maintainer ruling 2026-08-20: which objectui revision we pin is a decision recorded in an objectstack issue, never derived from objectui main). Nothing here reads objectui main, 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-sha diff name this gate rather than the gate being picked up by accident. The plain '.objectui-sha' literal does not do it — measured: extractWatchHints admits a leading-dot literal only from a fixed allowlist (.claude, .changeset, .github, .gitattributes), so a root dotfile outside it falls through both that admission and hintCovers' bare-word refusal. The gate therefore declares the documented subtree spelling (ROOT_DIR_WATCH_HINTS = ['.objectui-sha/**'], the idiom check-agent-test-spelling.mjs carries as AGENTS.md/**), pinned in --self-test in both directions so the declaration cannot drift from the file the gate opens.

Before / after, node scripts/pm/dispatch-gates.mjs .objectui-sha:

before   check:console-injection, check:console-sha
after    + pnpm check:sdui-lockstep   [lint.yml]   matched via .objectui-sha ⇢ gate source '.objectui-sha/**'

and packages/sdui-parser/src/parse.ts derives it too, via gate 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 HEAD blob id, an empty hash treated as failure), restored with git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap, and proved the restore by an empty git diff HEAD. Final git status --porcelain was empty. No build or dist/ is involved — the gate reads source and a JSON record, so there is no rebuild leg to report.

leg mutation expected observed
control none exit 0 exit 0
A one character inside the ported region of parse.ts red exit 1 · [grammar-drift], region blob c115f92b… vs recorded 0131f27c…
B 'not-a-container''not-a-containerX' red exit 1 · [code-drift] — only here not-a-containerX, only objectui not-a-container
C respell our literal the way objectui spells it (constant + declaration) green exit 0 — the decomposition survives the trap
D delete the record file red, and never a quiet pass exit 1 · REFUSED … Nothing was compared
E rewrite .objectui-sha red exit 1 · [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 commit 3c7936a46d on 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 a turbo run test log 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's dist/).
  • pnpm check:type-check-debt — needs the built workspace closure; its sibling check:type-check-coverage ran 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 direct grep -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-config is CI's run; locally the changed files were linted directly: --format json reports 2 entries, 1 in populationscripts/check-sdui-lockstep.mjs, 0 errors 0 warnings — and the record JSON reported by ESLint's own config as File 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 single eslint.config.mjs never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) 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

  • Contract accept/reject behaviour: UNCHANGED. No parser, validator, schema or runtime file is edited. The parser's accepted grammar, its diagnostics, their codes, severities and messages are all byte-identical to origin/main; the only files touched are a new gate script, a new generated record, a package.json script pair and a lint.yml step.
  • Published surface: UNCHANGED. @objectstack/sdui-parser publishes dist, README.md, CHANGELOG.md (files in its manifest); the new record sits at the package root and ships in none of them — pnpm check:published-files green. No new runtime dependency: the gate imports only node:*, the repo's own scripts/ts-parse.mjs / scripts/invoked-as.mjs, and the workspace's existing typescript.
  • No changeset, and skip-changeset applied: this PR releases nothing. Tooling, a workflow step and a non-published data file only.
  • Governed surface: not touched. No docs/adr/**, .claude/**, skills/**, AGENTS.md or CLAUDE.md in 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-sha bump require pnpm 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 the pin-moved branch and its self-test cases) if the maintainer would rather the two move independently.


Generated by Claude Code

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/sdui-parser/objectui-lockstep.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/sdui-parser/objectui-lockstep.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e5ed9434365e6a7520b5070da441f0552030b37dpackageMentionDocs.

@github-actions github-actions Bot added ci/cd dependencies Pull requests that update a dependency file labels Aug 30, 2026
@os-elon os-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 30, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 30, 2026 07:57
@os-elon
os-elon enabled auto-merge August 30, 2026 07:57
@os-elon
os-elon added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit 4feeadb Aug 30, 2026
38 checks passed
@os-elon
os-elon deleted the claude/issue-13150-sdui-parser-parity-check branch August 30, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants