Skip to content

feat(sdui): wire the ADR-0080 manifest into the production JSX gate — artefact, freshness gate, witness tests, ratchet-to-zero ledger - #13446

Draft
claude[bot] wants to merge 8 commits into
mainfrom
claude/issue-12924-sdui-manifest-wiring-r31
Draft

feat(sdui): wire the ADR-0080 manifest into the production JSX gate — artefact, freshness gate, witness tests, ratchet-to-zero ledger#13446
claude[bot] wants to merge 8 commits into
mainfrom
claude/issue-12924-sdui-manifest-wiring-r31

Conversation

@claude

@claude claude Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #12924

Implements the 2026-08-29T14:57Z superseding ruling (comment 5463102691): adopt the wiring. All five execution points land here; the three dispatch fences are respected — examples/app-showcase pages and skills/objectstack-ui/SKILL.md untouched, no lint.yml step precedes the artefact (the artefact is checked in by this same PR and the gate hard-fails on absence), no new diagnostics ported into validateTree, and packages/cli untouched. #12858 stays an independent card and is not ridden along. This PR is held for contract review (clause 2: activating validateTree changes what the save gate refuses) — it stays draft and must not be enqueued until that review clears.

Ruling point 1 — manifest source, checked in, freshness gate

  • sdui.manifest.json (repo root): objectui's public-tier component manifest — 57 components, 66,910 bytes, sha256 49211fee7792...c19e69d. Generated from the PUBLISHED @object-ui/* packages at 17.6.0, the version the committed pin .objectui-sha (9602dc820450) ships — verified by reading the pin's own packages/core/package.json (17.6.0) via the GitHub contents API. Component set is set-identical to PUBLIC_BLOCKS at the pin (57 == 57, zero symmetric difference; no intrinsic HTML tags; the html tier-C escape hatch present).
  • scripts/gen-sdui-manifest-node.mjs: the Node-route producer. Same enumeration as objectui's apps/console/dev/manifest-dump.tsx (eager-import the 16 registry modules in its order, ComponentRegistry.getPublicConfigs(), manifestFromConfigs from the lockstep-pinned @objectstack/sdui-parser), plus the one loader accommodation plain Node needs: .css side-effect imports resolve to an empty module. Measured this round: 16/16 modules import, 57 configs, 0 lazy stubs, ~4.3 s, no browser, no Playwright, no objectui build. Reproducibility: two generator runs from the same install produce byte-identical output (sha256 match against the checked-in artefact).
  • scripts/sdui-manifest.record.json: provenance (pin sha, package version, sha256, component count).
  • scripts/check-sdui-manifest.mjs (+ self-test, wired in lint.yml): offline freshness gate. Red on absence (the resolver degrades to parse-only SILENTLY — this gate is the thing that notices), on tamper (sha256 vs record), on shape damage, and on a moved .objectui-sha whose record was not regenerated — the anti-rot half the ruling asked for. Reverse-verified on the real tree: a one-token hand edit reds (key/type + hash), a simulated pin bump reds with the regeneration remedy, both restored to a committed state and re-verified green.
  • The same artefact discharges the check:react-declaration-parity EXTERNAL_INPUT_REQUIRED standing: the parity ratchet now runs per-PR in lint.yml against the checked-in manifest (--strict; measured green — "no new DECLARATION divergence vs accepted baseline"; only NEW divergence can red). The ledger prose in packages/spec/scripts/check-generated.ts ("nothing in this repo can hand it one") and the parity script header are now stale but live outside this card's file surface — reported to the PM rather than edited here.

Ruling point 2 — the resolver finds it; measured end-to-end, zero cli changes

resolveSduiManifest() path 1 is join(process.cwd(), 'sdui.manifest.json'); the CI verify leg and every repo-root CLI run have cwd = repo root. Measured on this branch through the real CLI, both directions:

  • WITHOUT the artefact: node packages/cli/bin/run.js validate examples/app-showcase/objectstack.config.ts --json exits 0, 0 errors, 0 jsx findings (parse-only, the pre-wiring status quo).
  • WITH the artefact (this PR's tree): same command exits 1 — 200 errors + 63 warnings, jsx census: jsx-forbidden-tag 100, jsx-unknown-component 100, jsx-not-a-container 32. Re-run after merging main: identical.

That red is the gate WORKING (full validation live for os validate / os build / os lint from the project root); its noise is governed by point 4 below. No current CI leg runs those commands from the repo root (the dogfood leg runs verify, which does CRUD/RLS proofs, not authoring lint — measured by reading every CLI invocation in the workflows), so no required context inherits the baseline red.

Ruling point 3 — production-path witness tests

packages/lint/src/validate-jsx-pages.production-witness.test.ts (runs in Test Core): reads the checked-in artefact from DISK (findUp to the workspace root — the declared-spelling the cross-package gate recognizes), asserts manifest-only diagnostics fire through validateJsxPages AND through the production registry entry (runAuthoringRules with sduiManifest), pins the arming delta (same stack, parse-only, emits neither), and hard-fails loudly if the artefact goes missing (reverse-verified: removing the file reds the suite at collection). The blind spot the card named — every prior guard fed validateTree an in-memory manifest — is closed by construction: no test in this file constructs one.

Ruling point 4 — first-run noise, itemized and ratcheted (ui#6779 shape)

packages/lint/src/sdui-jsx-baseline.json: the ratchet-to-zero ledger — 13 rows itemizing ALL 232 first-run findings (200 errors: div 79+79, a 21+21 across the three shipped pages; 32 warnings: not-a-container on flex, which is objectui#6740). The witness suite holds the live census EQUAL to the ledger in both directions: a finding beyond the ledger is a NEW violation (red, fix the page or regenerate the manifest — never grow the ledger); a row the live run no longer produces is STALE (red, delete the row in the same PR). Both directions reverse-verified red with their exact messages. The corpus itself is deliberately NOT rewritten — that is the dispatch's fence 1 (option A2-1 by the back door), and the ledger keeps the pressure visible until the vocabulary question is settled at the producer.

Ruling point 5

#12858 remains independent; nothing here touches the manifest-generation guard.

Mechanically-required accompaniments (declared in a claim amendment on #12924)

  • turbo.json: 4 inputs appended to the existing @objectstack/lint#test block (3 page files + the artefact) — demanded by check:cross-package-test-inputs.
  • scripts/cross-package-test-inputs.mjs: the same four paths in the declaration home.
  • .github/workflows/ci.yml: one crosspkg filter entry (sdui.manifest.json), verbatim as check:ci-filter-parity's failure text prescribes.

Verification (union re-run at final head c821db62)

  • Gate families derived by scripts/pm/dispatch-gates.mjs at c821db62 (47 path-matched + convention families; set unchanged vs the pre-merge derivation). All run green at that head, including: check:cross-package-test-inputs (117 self-test cases + "OK: 24 packages... turbo.json hashes every declared glob"), check:examples-live-imports ("0 invisible, 8 inputs-declared, 73 graph-visible"), check:ci-filter-parity ("all 127 declared cross-package globs covered"), check:type-check-debt ("29 ledger entries re-measured... none above its recorded number" — an earlier draft of the test added one tsc error via a NodeNext JSON module import; caught by the ratchet, fixed by switching to readFileSync), check:sdui-lockstep, check:self-test-wired (152 scripts), the new check:sdui-manifest (self-test: 6 cases), and the full battery from the derivation output.
  • @objectstack/lint: vitest 85 files / 2360 tests pass; tsc --noEmit green (NOTE: the package tsconfig excludes test files from its own typecheck program — pre-existing package debt; the new test file is verified by vitest execution, eslint, and the TEST_DEBT re-measure, which is the program that actually reads it). @objectstack/sdui-parser: 6 files / 130 tests pass.
  • ESLint: repo config over the 4 changed source files — 4/4 files, 0 errors, 0 warnings (narrowed run; population per the repo eslint config, count from --format json, and the config declares no parserOptions.project type-aware linting, so this diff cannot move untouched files' verdicts).
  • check-test-completeness: NOT MEASURED locally by its own design (exit 3 — it parses a saved CI test log which does not exist locally).
  • skills/**: untouched (0 lines), so the published-skill line-budget readings do not apply.

Changeset

None: this PR releases nothing from any package (published outputs unchanged — check:published-files green; the artefact lives at the repo root, outside every package's files set; the ledger and test are test-layer). Applying the skip-changeset label per the changeset workflow's own prescription for CI-internal changes; noting for the record that the os-dev contract's closed path list is narrower than that prescription — the conflict is #13432, already on file.

Out-of-scope finding filed as sub-issue #13445: the runtime save gate's only production caller still passes no sduiManifest (the third independent inertness cause, measured again at c821db62) — deliberately not fixed here.

Generated by Claude Code


Generated by Claude Code

@claude claude Bot added needs:contract-review skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/sdui-jsx-baseline.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/lint/src/sdui-jsx-baseline.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 — 5 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 34e80993f37d65363d4c9b3dc394c21b5abacadepackageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd needs:contract-review size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

1 participant