fix(spec,lint): page requires is live — refused at save, reported at load - #21451
Conversation
… load The liveness row flips planned to live, citing the save door and the load report; the describe states what happens at save and load; the validateJsxPages surface reason no longer names typescript/sucrase; the ui-html-page-div-refused guide entry names the runtime save door. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…veness counts Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…ge-requires-live # Conflicts: # packages/spec/liveness/README.md
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 138 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 77392bf8d416819d259088552604fc210b93d5f0 && git checkout 77392bf8d416819d259088552604fc210b93d5f0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b94a2a7277857d1bad1153e0ce5b34d9d89d50b5 54c73b11ffd53a573eeed788529e8f54772237fa && git checkout -B drift-repro b94a2a7277857d1bad1153e0ce5b34d9d89d50b5 && git merge --no-ff 54c73b11ffd53a573eeed788529e8f54772237fa
node scripts/docs-audit/affected-docs.mjs --json b94a2a7277857d1bad1153e0ce5b34d9d89d50b5
|
Contract reviewServed-tier: Inputs read: card #20871 (body; comments 5928431965 unlock, 5954183577 claim, 5959084718 dev report), PR #21451 (body, 9-file list, net diff against ① Derived judgmentsAccept set — unchanged, judged RIGHT. Public surface — no export movement, judged RIGHT. Liveness verdict
Lint reason, judged RIGHT. ADR-0087 entry text, judged RIGHT sentence by sentence (project manifest then console copy; Docs, judged RIGHT. The only "validated at save and load" sentence under Counts, judged RIGHT. One derived statement the diff makes false and leaves standing — judged WRONG, the FAIL item. ② Semver level
Step 18 is unreleased, confirmed: ③ Boundary flags
Verdict basis. The derived judgments, the liveness flip, the semver arm and the three named boundaries are all right at this head. One ledgered statement in Implemented-by: VERDICT: FAIL Generated by Claude Code |
…econciliation ledger The root omit row for page requires said "declared, not enforced yet", which the requires flip to live makes false. The ledger's own rule gives the offer decision to the enforcement, and the seat's answer of record (5959584348) is no form offer. The row now opens with the read-off family and the schema's own words, "derived from the source at save -- omit it", and states the measured truth per page kind: the save door stamps and judges an html/jsx page on a server with the deployment's SDUI manifest; on react, full and slotted pages nothing derives it and the load report is its one reader; the Studio page editor drops the key on every save. It moves to the platform-written group. No ruling is claimed and no contract changes. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Re-review after record 5959399303 (FAIL at ① Derived judgmentsThe delta. The failed item, now RIGHT. The stale row (old
Right group: the header paragraph defines the family as read off the key's own What the delta could move, re-judged. A ② Semver level
③ Boundary flags
Verdict basis. The single FAIL item of 5959399303 is answered at this head: the row is true clause by clause, byte-exact where it quotes, in the right group, read-off not ruled, and within the file's rule. The delta moves nothing else; the semver arm still matches what the diff publishes; every check on the head is green. Flag 4's second half is a seat act outside this PR, named here so it is not lost. Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20871
Clause-②: no
Summary
This is the spec half of #20312 stage ③. The engine half landed first:
250dec897).This PR makes the spec say what those landings made true. No runtime code changes.
packages/spec/liveness/page.json: therequiresrow moves fromplannedtolive. It carriesverifiedAt: 2026-10-02andevidenceScope: in-repo. Itsevidencenames the save door, the promotion re-stamp and the load report, each asfile#symbol. Itsproducernames the host that supplies the second input, the deployment's SDUI component manifest. The liveness README's producer table asks for one, because the reader compares the authored value against something a caller supplies.packages/spec/src/ui/page.zod.ts: therequiresdescribe used to say "(validated at save and load)", while the ledger said "declared, not enforced yet". The describe and its TSDoc now state what happens:kind: 'html'page's source is compiled (alias'jsx'too). A written list that disagrees with the source is refused (422 INVALID_METADATA,page-requires-disagrees-with-source). A draft keeps the list until its publish, which refuses it. The derived list is stored.packages/lint/src/authoring-rules.ts:validateJsxPagesno longer shares theRUNTIME_HEAVY_SOURCE_PARSEreason ("parses authored source through typescript/sucrase"). It gets its own reason,RUNTIME_HTML_SOURCE_COMPILED_AT_SAVE. That constant's TSDoc no longer lists jsx page bodies.validateReactPageskeeps the old reason, which is true for it (Sucrase).reasonof18.ui-html-page-div-refused.tsnow names the runtime save door.migrations/registry.tswas regenerated withgen:migration-registry, never by hand. The existing entry is amended rather than a new D3 entry added. Step 18 is unreleased (@objectstack/specis at 17.6.0), the entries README makes an entry file the unit of edit, andace770d5fcamended this same entry'sreasonthe same way.content/docs/**was therequiresrow ofcontent/docs/references/ui/page.mdx. That tree is AUTO-GEN, rendered from the describe, so it was regenerated rather than hand-edited. It now matches the describe, andcheck:docsholds the two equal, so this PR adds no separate grep pin. The hand-writtencontent/docs/ui/pages.mdxhas norequiresrow and no such sentence.liveness/state-counts/page.mdwas regenerated.pagegoes from 22 live and 1 planned to 23 live and 0 planned (24 classified).patchfor@objectstack/specand@objectstack/lint, withClause-②: no. No accept set moves.Declared deviation from the claimed file surface
packages/spec/liveness/README.mdalso changed: thepagerow of the hand-written state table. Its Notes cell said "live + one planned", which this PR makes false. It now records the flip.check:livenessholds the row set and the counts, but never a Notes cell's text.Premise checks
A1, positions at
ceb4a939b4, all confirmed:liveness/page.json:9wasplanned, with the note "save/load enforcement of plugin presence is deferred (M3b)".page.zod.ts:903was therequiresline.authoring-rules.ts:450-:451held the "typescript/sucrase" reason.validateJsxPagesused it at:1108andvalidateReactPagesat:1122.migrations/entries/semantic/18.ui-html-page-div-refused.ts.A2, is the authored value read, or only overwritten? It is read, and refused when it disagrees. The two metadata: refuse to save or load a page whose
requiresnames a plugin that is not loaded (1 key) #20312 blocks ofpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts(-t 20312) give 17 passed and 39 skipped. They include the case "refuses a hand-writtenrequiresthat disagrees with the source, naming each namespace". That case pins{ code: 'INVALID_METADATA', status: 422 }for three shapes:So authoring the key changes runtime behaviour, which is the README's definition of
live.A3, what
validateJsxPagesparses with.packages/lint/src/validate-jsx-pages.tsimportsparseJsxandcompilefrom@objectstack/sdui-parser, whosepackage.jsondeclares no dependencies.@objectstack/metadata-protocol'sruntime-authoring-gate.tsimports the samecompilestatically, so the kernel already loads it. The rule stays off the runtime surface for a different reason: the save door runs the same compile itself (findHtmlPageSourceGaps), under the samejsx-CODErule ids. The new reason says that.A4, the guide entry's new prose, checked against
main:os serve(whichdevandstartspawn) resolves the manifest from beside the served config, then from the console's copy (registerDeploymentSduiManifest);A5, the docs. See Summary. Studio's round trip of a stale stamp answering
422is exactly what the new sentence describes (a written list that disagrees is refused), so the docs do not name it. objectui#11357 is closed.The readers and the producer (A2)
packages/metadata-protocol/src/runtime-authoring-gate.ts#findHtmlPageSourceGapspackages/metadata-protocol/src/runtime-authoring-gate.ts#stampHtmlPageRequirespackages/metadata-protocol/src/protocol.ts#promoteDraftForPublish(deriveActiveBody)packages/metadata-protocol/src/protocol.ts#reportPageRequiresAbsentAtLoad, called fromloadMetaFromDb, judged byruntime-authoring-gate.ts#findPageRequiresAbsentFromManifestpackages/cli/src/utils/sdui-manifest.ts#registerDeploymentSduiManifest, called frompackages/cli/src/commands/serve.tsand read per publish and at load throughprotocol.ts#resolveSduiManifestThe ledger gate reads the row. As a one-shot ablation through
scripts/ablation-replace.mjs, the evidence pathruntime-authoring-gate.ts#findHtmlPageSourceGapswas rewritten to a file that does not exist.check:livenesswent red: "1 'live' / 'planned' / 'experimental' / 'live-elsewhere' entr(ies) cite a file that is missing from THIS repo: page/requires".path#symbol, 854 naming a symbol the cited file contains", so the cited symbols are held as well as the paths.a866b58134equals HEAD, andgit diff HEADis empty.Verification at
3e1f0dabffThis run resumed one that was lost to a container restart. Nothing from before the restart is cited.
origin/mainwas merged throughscripts/pm/os-regen-merge.sh(merge3e1f0dabff).registry.tsis not driver-routed, and both sides survived the text merge: this branch's step 18 text, and main's newdashboard-widget-single-series-multi-measure-refusedentry. Every reading below is at3e1f0dabff.turbo run build --filter='./packages/**': 71 of 71 tasks successful. The tree was clean afterwards.@objectstack/spec:build: exit 0.check:generated: exit 0, "All 15 generated artifacts are up to date".check:liveness: exit 0, "packages/spec/liveness/state-counts/ is current".test(vitest run --project local, two shards): 300 files, 9053 passed and 1 todo; then 300 files, 8631 passed. Both exit 0.typecheck: exit 0.@objectstack/lint:testgives 119 files and 5585 passed, exit 0.typecheckexits 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) derived 110 commands. All 110 ran, each exit code written to disk before any reading, and all exited 0.--ranreconciles them: "110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN".check-adr-0087-registration --self-testcould not write its fixture commits: the container's commit-signing server answered503. On re-run: "441 assertions".check:query-options-erasurehit the per-command 300s cap on a contended box. On re-run it exited 0 in 491s: "ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new".pnpm check:adr-0087-registration: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)".pnpm check:empty-changeset: "No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)".check-changeset-no-major --base origin/main: "This diff introduces nomajorbump". Driven offline against this body (--event): "LEVEL AXIS: this PR declares clause-②no, so no package here is declared to have grown a published surface".check-changeset-fixed: the.changeset/config.json"fixed" group "is in sync with 69 public workspace packages".pnpm check:doc-authoring: "17283 customer-facing string(s) across 1234 spec sources clean".pnpm check:nul-bytes: "OK (scanned 9771 text file(s) ... no raw ASCII control bytes)".check:meta-url-spelling,check:authz-resolver,check:error-code-casing,check:filter-alias-parity.pnpm lintis run by CI. Here:eslint.config.mjslints**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}. Of the 9 changed files, exactly the 4.tsfiles are in it.eslint --no-inline-config --format jsonover those 4 files gives 4 results, 0 errors and 0 warnings.parserOptions.project, noprojectService), so this diff cannot move the verdict on any untouched file.mainmoved after the merge. A localgit merge-tree --write-tree HEAD origin/mainat53fd35e3e3is clean. None of this diff's driver-routed paths changed onmain, so GitHub sees the same answer. CI judges the merge ref.Acceptance notes
packages/lint/src/runtime-lazy-deps.test.ts's header says "The two rules that need them stay CLI-only (RUNTIME_HEAVY_SOURCE_PARSE)". After this PR, one registry rule (validateReactPages) carries that constant. This is test prose, not a published surface, and it is not edited here. Carrier: none.packages/cli/src/utils/sdui-manifest.tssays "Page source andrequiresnot validated at save". That host skips the load report too, so the line could say "at save or load". It is not false, it is in adomain:clifile pinned by the CLI's tests, and it stays out of scope here. Carrier: none.validateJsxPagesstill checks syntax and structure without a manifest. The new reason's TSDoc records this. The host announces it at boot, so it is not a finding.skills/**: zero hits for a pagerequiressentence or "validated at save and load". Nothing to list.omitrow forpage/requires(packages/spec/src/system/metadata-form-zod-reconciliation.test.ts) said "declared, not enforced yet", which this PR makes false; it is re-ledgered under "platform-written, never authored" on the schema's own words with the measured truth per page kind, and no form offer, per seat answer 5959584348 (commit54c73b11ff).Generated by Claude Code