Uh oh!
There was an error while loading. Please reload this page.
fix(sdui-parser): port the inert-expression diagnostic in lockstep with objectui - #12811
Conversation
…th objectui
A braced attribute value that fails JSON materialization compiles to the
parser's deferred { $expr } marker, and nothing downstream evaluates that
marker — this tier parses, never executes (ADR-0080), and no renderer consumes
$expr. The value reached the renderer as an opaque object, defensive non-array
reads degraded it to 'not declared', and the author's binding vanished with
zero diagnostics anywhere (ADR-0078's prohibited parsed-but-silently-inert
state). validateTree now names it at compile time, warning severity, with the
fix in the message.
There are two copies of this parser and they must agree on the accepted grammar
AND on diagnostic codes; if they drift, the save gate and the renderer speak
different dialects. The emitted diagnostic is byte-equal to objectui PR #6613.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 7543154ba58be2a42710cf82bb62fe2a3db3ec7a && git checkout 7543154ba58be2a42710cf82bb62fe2a3db3ec7a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 26deb31a0b69137b6cb37baebe2b872ed3b9e224 c8cc1ec23415156e5d2a602048f979d7f3108c26 && git checkout -B drift-repro 26deb31a0b69137b6cb37baebe2b872ed3b9e224 && git merge --no-ff c8cc1ec23415156e5d2a602048f979d7f3108c26
node scripts/docs-audit/affected-docs.mjs --json 26deb31a0b69137b6cb37baebe2b872ed3b9e224 |
os-zhuang
commented
Aug 27, 2026
复核:ACCEPT —— 但 ⛔ 尚未武装,CI 还在跑
人工地板:不成立 —— 而这次是被测量判定的,不是被采信的本席在 brief 里把 triage 的「不移动 accept 集合」写成可证伪的前提而不是许可,并说若发现它改变 accept/reject 就停下来报告。你没有采信,而是去量了四个消费者: ⇒ warning 翻不动 独立核过的五项,⛔ 不取转述
⭐ 这是本席今天第四次踩同一形状( ⭐⭐⭐ 比较器在被信任之前先被证明能失败
⇒ 一个只会输出「一致」的比较器,和一个正确报告「一致」的比较器,读起来完全一样。你把它们分开了。这是跨仓不变量唯一可信的证明方式。
|
os-zhuang
commented
Aug 27, 2026
docs-drift 机器人的提醒:已实测,无需动作 —— 而对照选的是一个既有诊断码
实测( ⭐ 关键在于对照选的是 ⇒ 有 3 个页面谈 状态不变:ACCEPT,head Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Closes#12719
What this is
The mechanical port of the
inert-expressiondiagnostic from objectui PR objectui#6613 (commit0db4fb3) into this repo's hoisted copy of the parser.interpretBracematerializes strict-JSON values only. Anything else — the single-quoted array every JSX author writes, unquoted object keys, any JS expression — compiles to the deferred{ $expr }marker, and nothing downstream evaluates that marker: this tier parses, never executes (ADR-0080), and no renderer consumes$expr. The value reached the renderer as an opaque object, defensive non-array reads degraded it to "not declared", and the author's binding vanished with zero diagnostics anywhere. That is ADR-0078's prohibited parsed-but-silently-inert state, reported from production as objectui#6598.validateTreenow names it at compile time, warning severity, with the fix in the message.The invariant this serves
There are two copies of this parser, and they must byte-agree on the accepted grammar and on diagnostic codes. If they drift, the save gate and the renderer speak different dialects — a page can save clean and render inert, or the reverse, which is surface-dependent and therefore intermittent from the author's point of view.
The emitted diagnostic is byte-equal to objectui's. Verified by extracting the
diagnostics.push({...})block from bothvalidate.tscopies and comparing bytes: equal.The accepted grammar was already in lockstep, and this PR does not touch it.
interpretBraceis byte-identical across the copies (sha256 of the extracted function body:aaf9c50d96522928on both sides). The comparator was proved able to detect a disagreement before being trusted to report none — mutating the objectui side with the objectui#6614 grammar widening flipped the verdict toAGREE=Falsewith a different hash, so the agreement reading is a measurement rather than a pattern that cannot match.This does NOT move accept/reject behaviour
The dispatch called this a falsifiable premise rather than a permit, so it was measured rather than adopted:
compile()setsok: !diagnostics.some((d) => d.severity === 'error'). A warning cannot flip it.runtime-gate.tssplits findings intoerrors: added.filter((f) => f.severity === 'error')— "the reason to refuse the write" — andadvisories: added.filter((f) => f.severity !== 'error'). A warning is filed as an advisory on a 2xx, never a 422.os lintexits non-zero on error-severity findings only (if (errors.length > 0) process.exit(1), over a list filtered toseverity === 'error').packages/lint/src/validate-jsx-pages.ts, which maps each diagnostic to a finding of the same severity.A test case pins this directly: every inert spelling still compiles with
ok === trueand warning-only diagnostics, so a later escalation to error has to move a pin consciously.What it buys, and what it does not
Lockstep — not an author-visible gate. This repo resolves no
sdui.manifest.json(none in the tree;@objectstack/console/dist/sdui.manifest.jsonis absent), soresolveSduiManifest()returns undefined andvalidateJsxPagesruns parse-only —validateTreeis not reached from the production gate today. The warning is recorded in compile output and no production surface displays it. Wiring the manifest is deliberately out of scope per triage.Also out of scope per triage, and untouched here: any widening of the accepted literal grammar (single-quoted strings, unquoted keys), which waits on objectui#6614.
Tests
packages/sdui-parser/src/__tests__/inert-expression.test.ts, ported from objectui#6613 and extended. Both halves of the contract are pinned:inert-expressionwith the remedy in the message — that it names JSON, carries "double-quoted strings and keys", and shows the corrected spelling beside the broken one. "Stopped being silent" is satisfied by any diagnostic at all, so it is not what gets pinned.Ablation. The fix was reverted on disk and the pins observed to fail, then restored:
inert-expressionanchor 1 → 0, old-guard anchor 0 → 1, blobfd17e946…→c220cb87…(a no-op edit aborts the script).Tests 4 failed | 3 passed (7)— the predicted direction, stated before running. The four warning-assertion cases go red; the strict-JSON, accept/reject and unknown-prop cases stay green, because the fix does not move those.git diff HEADempty, restored blobfd17e946…equal to the HEAD blob, anchors back to 1 / 0.Tests 7 passed (7).Suites (all at final head
c8cc1ec23, tree clean):pnpm --filter @objectstack/sdui-parser testTest Files 3 passed (3) · Tests 21 passed (21)pnpm --filter @objectstack/sdui-parser typecheckpnpm --filter @objectstack/lint test(the sole importer, on a built closure)Test Files 81 passed (81) · Tests 2295 passed | 5 skippedpnpm lint(full repo,eslint . --no-inline-config)The package's
typecheckgenuinely covers the new test file —tsc --listFilesreports it in the program (count 1), so this is not the excluded-tests green-over-nothing shape.Downstream narrowing, declared. 54 packages are transitive dependents, but
packages/lint/src/validate-jsx-pages.tsis the only file in the repo that imports from@objectstack/sdui-parser; the other 53 depend on@objectstack/lint, not on this package's symbols. Its full suite is green above. CI runs the rest regardless.Gates
All 12 point-named gates green, plus the changeset family the dispatch list predates (it was derived before this changeset existed) and the convention-triggered set for a new test file:
check:changeset-gate-self-tests,check:objectql-double-limit,check:objectui-changeset,check:pm-half-states,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,release-rehearsal-clone --self-test,check:query-options-erasure,check:engine-double-contract,check:where-matcher,check:type-check-coverage,check:nul-bytes— all exit 0.Two declared readings that are not green results:
node scripts/pm/check-half-states.mjs(bare, live-board form) exited 3 with its ownPREREQUISITE NOT METbanner — the container'sGITHUB_TOKENis a 14-character proxy placeholder, so nothing was swept. That is a refusal to measure, not a finding. The form CI runs,check:pm-half-states(--self-test), passed.check:type-check-debt --re-measurewas not run: it re-measures per ledger entry, andpackages/sdui-parsercarries notest-typecheck-debt.json. The structural half (check:type-check-coverage) is green and the package typechecks clean including the new test file.Changeset
.changeset/sdui-parser-inert-expression-lockstep.md—@objectstack/sdui-parser: minor. This package is published (v17.2.0,private: false), so theskip-changesetroute does not apply. Minor rather than patch: it matches the bump objectui#6613 took on its own copy, and a new author-visible diagnostic is a functional addition rather than a pure bug fix. Never major.Out-of-scope finding, filed not fixed
#12810 — the two copies still disagree on diagnostic codes after this port. objectui's copy carries
dashboard-widget-options(objectui#5709) and a union-armcheckTypebuilt oninputTypeArms(objectui#3832); this repo's copy has neither, so union-typed inputs draw no diagnostic here and a differentcode/severitypair there. Recorded rather than repaired in this PR: triage scoped this card to the one diagnostic, and porting the union-arm half would change what this copy accepts and rejects — a different class of change from this one. That issue remains open for triage.Refs: objectui#6613 (source of the port) · objectui#6598 · objectui#5709 · objectui#6614 · #11148 (the same two-copy port pattern) · ADR-0078 · ADR-0080.
Generated by Claude Code