Skip to content

page requires, #20312 stage ③ (spec half): the liveness row flips to live, the describe and the docs state save + load, the lint reason and the ADR-0087 guide entry name the save door #20871

Description

@objectstack-fleet

Filed by the triage seat (objectstack-wide, seat post #6015, session_01AavokzJ5DndAwitDXvKy4U) as the spec half of #20312's per-layer split. ⛔ Not a claim, ⛔ not a dispatch. Graded here: enhancement · priority:p3 · domain:spec · area:studio · pm:blocked.

Blocked-by: #20870

Scope (lands after the engine half, so every sentence is true when it ships)

Pins

  • The liveness gate reads the row live with its cited readers.
  • The describe and docs sentences match (one grep pin, if the repo's doc-parity gate does not already cover it).

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlocked: pm:blocked → pm:queue — #20870 closed as completed

    domain:engine#2 (seat post #20966), running the unlock scan for the upstream it landed · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T09:15Z. ⛔ Not a claim; the domain:spec lane claims it.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01YDt3PzwfrkuFzUBF89WPmM
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20871-page-requires-live
    Worktree: objectstack-issue-20871
    Domain: domain:spec
    Seat: domain:spec#2
    File surface (the card's scope, located at ceb4a939b4):

    • packages/spec/liveness/page.json: the requires row (:9, planned) and its note, plus the regenerated counts.
    • packages/spec/src/ui/page.zod.ts: the requires describe (:903).
    • packages/lint/src/authoring-rules.ts: validateJsxPages' "typescript/sucrase" reason (:445–:451).
    • The ADR-0087 guide: migrations/entries/semantic/18.ui-html-page-div-refused.ts's reason, or a new D3 entry, with the generated region of migrations/registry.ts regenerated, never by hand.
    • content/docs/**: the one "validated at save and load" sentence (cross-lane domain:devx, declared by the card).
    • One .changeset/20871-*.md.
    • ⛔ No engine or door code. ⛔ No objectui edit.
    • Stop on breach and explain in the report.

    Container & model: S, mode:subagent, model: opus (dispatch-gates --tier at ceb4a939b4: no path-derived mandate; floor sonnet · default opus · ceiling CONTRACT_REVIEW_TIER). The at-tier contract review is owed (packages/spec/src/**, non-test) and runs as a separate isolated subagent after the PR opens.
    Clause-②: no
    Thread-read: 5928431965
    Serial constraints cleared:

    Seat terms: p3, under the maintainer's 「p3 的卡也可以派了」 (5952442849). The selection order puts this card first among the dispatchable p3 contract-surface cards. #20281, older, has no spec-lane half left: stage ① landed, stage ② is #20919 (domain:services, closed), and stage ③ is the job cadence. This PR is Fixes #20871.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20871,
      "status": "done",
      "branch": "claude/issue-20871-page-requires-live",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21451",
      "session": "session_01YDt3PzwfrkuFzUBF89WPmM",
      "premise_still_valid": true,
      "summary": "RESUMED RUN: the container restarted and the predecessor run was lost after it pushed 6988b2b967 (0e521b327e feat + 2fb84234e0 regen + two main merges); no PR, no report, no gate result survived. Found DONE on the branch: every Zone 1 item (liveness row planned to live with evidence + producer, the page.zod.ts describe and TSDoc, the validateJsxPages surface reason RUNTIME_HTML_SOURCE_COMPILED_AT_SAVE, the 18.ui-html-page-div-refused reason amended and registry.ts regenerated, the AUTO-GEN references/ui/page.mdx row regenerated, state-counts regenerated, patch changeset) plus a declared README ledger-row note. Re-verified every claim of that content against origin/main (A1 to A5 below) and found nothing to correct. FINISHED in this run: merged origin/main c2c21f357c through os-regen-merge.sh (merge 3e1f0dabff, no regen owed: check:generated all 15 current), rebuilt all packages, re-ran the full gate set and both packages test/typecheck at 3e1f0dabff, a one-shot ablation of the ledger pin, opened draft PR 21451, assigned os-tesla. The spec now says what PR 20852 and PR 21121 made true: a page requires is refused at save when it disagrees with the source, stamped from the compile, re-stamped on draft promotion, and reported at load when it names a plugin the deployment manifest does not carry.",
      "tests": "All at 3e1f0dabff. Full build: turbo run build --filter=./packages/** = 71/71 successful. @objectstack/spec: build VERDICT command-exit 0; check:generated exit 0 \"All 15 generated artifacts are up to date\"; check:liveness exit 0 \"state-counts/ is current\"; test (vitest --project local, --shard=1/2 and 2/2) 300 files 9053 passed + 1 todo, 300 files 8631 passed, both VERDICT command-exit 0; typecheck VERDICT command-exit 0. @objectstack/lint: test 119 files 5585 passed VERDICT command-exit 0; typecheck VERDICT command-exit 0. A2 re-measured: metadata-protocol src/protocol.runtime-authoring-gate.test.ts -t 20312 = 17 passed / 39 skipped, VERDICT command-exit 0, incl. \"refuses a hand-written requires that disagrees with the source\" pinning code INVALID_METADATA status 422. Ablation (one-shot, through scripts/ablation-replace.mjs WRAP mode + own EXIT/INT/TERM trap, absolute path): page.json evidence path runtime-authoring-gate.ts#findHtmlPageSourceGaps rewritten to runtime-authoring-gate-ablated.ts; on-disk proof anchor 1 to 0, replacement 0 to 1, blob a866b58134 to d9665ac1b8; check:liveness went RED (expected direction): \"1 live/planned/experimental/live-elsewhere entr(ies) cite a file that is missing from THIS repo: page/requires\"; same run \"854 pointer(s) written path#symbol, 854 naming a symbol the cited file contains\"; restore proven: blob == HEAD a866b58134, git diff HEAD empty. No dist marker involved (check:liveness reads the JSON source). Lint narrowed and declared (pnpm lint is CI-owned): population from eslint.config.mjs files glob = the 4 changed .ts files of 9; eslint --no-inline-config --format json = 4 results, 0 errors, 0 warnings; invariance: the config enables no type-aware linting (no parserOptions.project, no projectService), so untouched files cannot move.",
      "gates": [
        "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands :: exit 0 :: 110 commands derived at 3e1f0dabff from merge base c2c21f357",
        "the 110 derived commands :: all exit 0 (exit codes written to disk per command) :: --ran reconcile exit 0 \"110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN\"",
        "first-pass non-measurements, both re-run green: check-adr-0087-registration --self-test exit 1 = commit-signing server 503 while writing fixture commits (infra, gate body never reached) then exit 0 \"441 assertions\"; pnpm check:query-options-erasure exit 124 = my 300s per-command cap under contention, then exit 0 in 491s \"ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new\"",
        "pnpm --filter @objectstack/spec run check:generated :: exit 0 :: \"All 15 generated artifacts are up to date\"",
        "pnpm --filter @objectstack/spec run check:liveness :: exit 0 :: \"packages/spec/liveness/state-counts/ is current\"",
        "pnpm check:adr-0087-registration :: exit 0 :: \"this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)\"",
        "pnpm check:empty-changeset :: exit 0 :: \"No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)\"",
        "node scripts/check-changeset-no-major.mjs --base origin/main --event (synthetic payload carrying the exact PR body) :: exit 0 :: \"This diff introduces no major bump\" + \"LEVEL AXIS: this PR declares clause-② no\"",
        "node scripts/check-changeset-fixed.mjs :: exit 0 :: \"fixed group is in sync with 69 public workspace packages\"",
        "pnpm check:doc-authoring :: exit 0 :: \"17283 customer-facing string(s) across 1234 spec sources clean\"",
        "pnpm check:nul-bytes :: exit 0 :: \"OK (scanned 9771 text file(s) ... no raw ASCII control bytes)\"",
        "roster gates with a roster under these paths: check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity :: all exit 0",
        "PR CI at 3e1f0dabff, one read after opening: in_progress (12 completed with 0 failures, 20 in progress); not waited on"
      ],
      "line_budget": "9 files, 89 changed lines (+78 / -11) vs the 5000-line human-merge threshold: under. No skills/** file touched, so no skill line ratchet applies. Governed surfaces touched: none (no .claude/**, docs/adr/**, skills/**, AGENTS.md, CLAUDE.md, NORTH-STAR).",
      "files_changed": [
        ".changeset/20871-page-requires-live.md (new; patch @objectstack/spec + @objectstack/lint; Clause-②: no)",
        "packages/spec/liveness/page.json (requires: planned to live, verifiedAt 2026-10-02, evidenceScope in-repo, evidence, producer, note)",
        "packages/spec/liveness/state-counts/page.md (generated: page 22/1 planned to 23/0 planned)",
        "packages/spec/liveness/README.md (page row Notes cell; declared deviation)",
        "packages/spec/src/ui/page.zod.ts (requires describe + TSDoc: refused at save, reported at load)",
        "content/docs/references/ui/page.mdx (AUTO-GEN, regenerated from the describe)",
        "packages/lint/src/authoring-rules.ts (validateJsxPages surfaceReason RUNTIME_HTML_SOURCE_COMPILED_AT_SAVE; RUNTIME_HEAVY_SOURCE_PARSE TSDoc drops jsx)",
        "packages/spec/src/migrations/entries/semantic/18.ui-html-page-div-refused.ts (reason names the runtime save door)",
        "packages/spec/src/migrations/registry.ts (regenerated by gen:migration-registry)"
      ],
      "readers_and_producer": {
        "save_judge": "packages/metadata-protocol/src/runtime-authoring-gate.ts#findHtmlPageSourceGaps: reads the authored list of a kind html (alias jsx) page and refuses a disagreeing one, rule page-requires-disagrees-with-source, 422 INVALID_METADATA; a draft at its publish",
        "save_stamp": "packages/metadata-protocol/src/runtime-authoring-gate.ts#stampHtmlPageRequires, called from protocol.ts at the save (request.item = stampHtmlPageRequires(...))",
        "promotion_restamp": "packages/metadata-protocol/src/protocol.ts#promoteDraftForPublish, deriveActiveBody applies stampHtmlPageRequires",
        "load_report": "packages/metadata-protocol/src/protocol.ts#reportPageRequiresAbsentAtLoad, called inside loadMetaFromDb after the page hydrated; judged by runtime-authoring-gate.ts#findPageRequiresAbsentFromManifest (kind-agnostic); warns [page_requires_plugin_absent], never refuses",
        "producer": "packages/cli/src/utils/sdui-manifest.ts#registerDeploymentSduiManifest, called at packages/cli/src/commands/serve.ts (registers SDUI_MANIFEST_SERVICE; dev and start spawn serve); read per publish and at load through protocol.ts#resolveSduiManifest. No manifest: nothing registered, one boot line",
        "live_verdict": "live holds: the authored value is READ and a disagreeing value is REFUSED (not merely overwritten), so authoring it changes runtime behaviour (README definition); the README producer table requires a producer because the reader compares against a caller-supplied manifest, and the row cites one"
      },
      "semver_arm": "patch for @objectstack/spec and @objectstack/lint (both publish: spec files[] ships liveness and src/**/*.zod.ts; lint ships dist). Judged by: check-changeset-no-major \"This diff introduces no major bump\" + level axis \"this PR declares clause-② no\"; check-adr-0087-registration \"1 non-breaking changeset(s) seen\"; check-empty-changeset \"1 declaring changeset(s) added\". Clause-②: no in the changeset body and at a line start in the PR body. No gate disagreed.",
      "zone2": {
        "A1": "confirmed at ceb4a939b4: page.json:9 planned with the M3b note; page.zod.ts:903; authoring-rules.ts:450-451 the typescript/sucrase string, used at :1108 (validateJsxPages) and :1122 (validateReactPages); the semantic entry 18.ui-html-page-div-refused.ts exists",
        "A2": "confirmed, see readers_and_producer; nothing for open_questions",
        "A3": "validate-jsx-pages.ts imports parseJsx/compile from @objectstack/sdui-parser, which declares zero dependencies; metadata-protocol runtime-authoring-gate.ts imports the same compile statically, so the kernel already loads it. typescript/sucrase was stale for this rule; it stays true for validateReactPages (Sucrase). New reason: runtime-safe, not wired because the save door already runs the same compile under the same jsx-CODE rule ids",
        "A4": "amended the existing entry: step 18 is unreleased (spec 17.6.0), the entries README makes the entry file the unit of edit, ace770d5fc amended the same entry the same way; every sentence checked (serve resolves project manifest then console copy; dev/start spawn serve; save door compiles on every publish; draft judged at publish; no-manifest boot line; rows at rest not recompiled). registry.ts regenerated, never hand-edited; upgrade guide and spec-changes do not carry step 18 yet and check clean",
        "A5": "the only \"validated at save and load\" sentence in content/docs/** was the AUTO-GEN references/ui/page.mdx requires row, regenerated from the describe and held equal by check:docs (so no extra grep pin, per the card condition); hand-written ui/pages.mdx has no requires row. objectui#11357 not named: the new sentence already says a disagreeing written list is refused, which is what the Studio round trip hits; that issue is now closed",
        "A6": "patch changeset, Clause-②: no; no gate disagreed",
        "A7": "PR 21416 and PR 21413 both merged and are in this branch through the merges; no skills/** edit, zero skills hits"
      },
      "deviations": [
        "packages/spec/liveness/README.md page-row Notes cell edited: outside the claim file surface, but the flip makes its \"live + one planned\" false; declared in the PR body",
        "resume: origin/main moved again after the merge (to 53fd35e3e3, 6 commits incl. spec/lint/registry.ts); PR opened at the measured head 3e1f0dabff without another merge. git merge-tree --write-tree HEAD origin/main is clean, none of the driver-routed paths of this diff moved on main; CI judges the merge ref",
        "labels: the dispatch says \"plus the default PR labels\"; read as the labeler automatic set (documentation, size/s, tooling, protocol:ui arrived on their own, matching sibling PRs of this seat). This run wrote zero labels; skip-changeset does not apply (a changeset exists). Flag it if a named label was meant",
        "the PR body footer uses the AGENTS.md session-URL form, not the harness reminder robot-emoji form; no new commits were authored in this run besides the merge commit os-regen-merge.sh wrote (pre-push check:commit-card-trailers passed: no model identifier)",
        "spec test run as two vitest shards (--shard=1/2, 2/2) to fit the foreground cap; together 600 files, same as the whole suite"
      ],
      "mcp_calls": "0",
      "api_writes": "3 relay dispatches (each POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create = POST /repos/objectstack-ai/objectstack/pulls (PR 21451, draft); assign = POST /repos/objectstack-ai/objectstack/issues/21451/assignees (os-tesla); this comment = POST /repos/objectstack-ai/objectstack/issues/20871/comments. Plus git push of the merge commit 6988b2b967..3e1f0dabff (not REST)",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed · packages/lint/src/runtime-lazy-deps.test.ts 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; test prose, not a published surface; in PR Acceptance notes",
        "carrier: none (承接者:无) · noted, not filed · packages/cli/src/utils/sdui-manifest.ts no-manifest boot line says \"not validated at save\"; that host also skips the load report, so \"at save or load\" would be fuller; not false, domain:cli, pinned by CLI tests; in PR Acceptance notes"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20871,
      "status": "needs_decision",
      "branch": "claude/issue-20871-page-requires-live",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21451",
      "head": "3e1f0dabff (unchanged: no commit, no push, no PR body patch this round)",
      "session": "session_01YDt3PzwfrkuFzUBF89WPmM",
      "premise_still_valid": true,
      "summary": "FIX ROUND for review record 5959399303, the single FAIL item: metadata-form-zod-reconciliation.test.ts:402-407, the root omit row for page.requires, still reads \"declared, not enforced yet, liveness verdict planned\", which this PR makes false. Step 1 measurement (below): the reviewer-suggested platform-derived reason (\"derived from the source at save; a written list that disagrees is refused\") holds only for kind html/jsx on a host with the deployment SDUI manifest. On kind react, full and slotted the platform never writes requires. PageSchema still admits it there, and an authored list is read only by the kind-agnostic load report (warn, still served). On a manifest-less host an html list is stored as written and judged by nothing. So no true holding reason covers every page kind. Per the fix-round step 3 I stopped: no form offer, no invented reason, no row edit, no commit. The offer decision goes up as the one open_questions entry. Everything else in the record was judged right at this head and is unchanged.",
      "measurement": "All readings at head 3e1f0dabff (objectstack) and the .objectui-sha pin 89cad75d55 (objectui). (1) kind html / jsx on a host WITH the deployment SDUI manifest: the platform WRITES requires. stampHtmlPageRequires (packages/metadata-protocol/src/runtime-authoring-gate.ts:725-736) stamps the compiled list at save (protocol.ts:17584, drafts included) and on draft promotion (protocol.ts:19066). A written list that disagrees is READ and REFUSED, 422 INVALID_METADATA page-requires-disagrees-with-source (findHtmlPageSourceGaps, runtime-authoring-gate.ts:657-710). The compiled kinds are COMPILED_PAGE_KINDS = html, jsx (:571; compileHtmlPage bails on any other kind at :634). (2) kind html / jsx on a host with NO manifest: nothing writes or judges it. compileHtmlPage bails at :635, so the stamp returns the body as written and the save judge returns null; findPageRequiresAbsentFromManifest returns null at :621, so the load report skips too. An authored list is stored as written, and one boot line says so (packages/cli/src/utils/sdui-manifest.ts:256-272). (3) kind react, full and slotted: the platform NEVER writes requires (react is not compiled at save, ADR-0081; full and slotted have no source to derive from). PageSchema admits the key on every kind: page.zod.ts:912 z.array(z.string()).optional(), kind enum at :839, and no refinement ties requires to kind (its only other mentions are :643, a message, and :713, an unknown-key alias hint). An authored list is READ by exactly one reader, the load report (findPageRequiresAbsentFromManifest, runtime-authoring-gate.ts:616-627, kind-agnostic by its own TSDoc; called at protocol.ts:23667, reporting at :23784-23797). It warns when a namespace is absent from the manifest, the page is still served, and it never refuses. So on these kinds requires is authored and only reported at load. (4) Writers, exhaustively: the only non-test writer of a page body requires in packages/ is stampHtmlPageRequires. Every other requires hit in cli/runtime is the stack-level capability key (compile.ts:755, validate.ts:691, serve.ts:2765, scaffold-wiring.ts:118, capability-preflight.ts:142). There is no string-keyed write and no conversion. (5) Studio (objectui at pin 89cad75d55): app-shell/src/services/builtinComponents.tsx:262-267 pageSaveBody deletes requires from the save body, registered as fromDraft for type page at :269-279 with NO kind gate. Studio therefore drops the key on every page kind, react included (its TSDoc reasons only about html stamps). No objectui renderer reads a page requires (grep at the pin; only sdui-parser compile produces one). (6) Corpus: zero page-level requires authored in examples/** and packages/apps/** at 3e1f0dabff. The only hits are stack-level capability lists in three objectstack.config.ts files (app-crm:54, app-showcase:107, app-todo:58). (7) The ledger: deleting the row without an offer turns the page top-level test red by construction. reconcileRoot (metadata-form-zod-reconciliation.test.ts:878-885) names every offerable key that is neither offered nor excused, and page is in TOP_LEVEL_TYPES (not union-rooted). The current row (:402-407) is false after the flip, as the review found.",
      "row_new_text": "none: not written. The row stays as it is at 3e1f0dabff (:402-407) pending the decision below. A row edit, a row deletion and a form offer were each ruled out by the step 3 condition, the file rule at :306-309 and the reconcileRoot construction respectively.",
      "tests": "None run this round: the round stopped at step 3 before any edit, so no head moved and nothing new needs covering. Every reading above is a code or git reading, cited file:line at 3e1f0dabff and at the objectui pin 89cad75d55. The worktree was recreated on the branch at 3e1f0dabff (pnpm install exit 0, tree clean) for the readings and removed again clean. The previous round test and gate record at 3e1f0dabff stands as reported.",
      "gates": [
        "none run this round (no diff); the round-1 gate record at 3e1f0dabff stands"
      ],
      "line_budget": "unchanged: 9 files, 89 changed lines (+78 / -11); no governed surface",
      "files_changed": [
        "none this round"
      ],
      "semver_arm": "unchanged: patch @objectstack/spec + @objectstack/lint, Clause-②: no. Options A or C below would each need their own card and changeset; A is a breaking narrowing.",
      "deviations": [
        "this os-dev-report comment is a second card comment on #20871 (the dispatch budgeted one). It is posted because the standing os-dev contract makes GitHub the report authority on every round; the fix-round message forbade only a review reply, and none was posted",
        "no edit despite a FAIL item: the fix-round step 3 condition held (measurement above), so the instruction was to stop and send the decision up"
      ],
      "mcp_calls": "0",
      "api_writes": "1 relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): this comment = POST /repos/objectstack-ai/objectstack/issues/20871/comments. No git push, no pr_create, no label write, no issue_patch this round.",
      "open_questions": [
        {
          "question": "page.requires is now live, but its reconciliation-ledger row (metadata-form-zod-reconciliation.test.ts:402-407) still says \"declared, not enforced yet\". The file (:306-309) says that on enforcement the row is deleted and the offer decided, and that decision belongs to the enforcement. What is the offer decision for page.requires, given that the platform derives the key only for html/jsx pages on a host with a manifest, while react/full/slotted pages may still author it, the platform never writes it there, and only the load report reads it?",
          "options": [
            "A. Narrow the contract, then re-ledger. A separate breaking card (Clause-② no (narrowing), an ADR-0087 semantic entry, FROM/TO in the changeset) refuses requires at parse on the kinds the save door never compiles (react, full, slotted). Then the row re-ledgers here under the platform-derived family (\"derived from an html page source at save; a written list that disagrees is refused\"), which then holds on every kind that admits the key. Cost: one more spec card plus migration; stored non-html rows keep being load-reported (or a conversion strips them); this PR waits for it or lands with an interim row from B. Consumers to check: objectui pageSaveBody already strips the key on every kind, so nothing in Studio regresses.",
            "B. Rule a reason, no contract change. The maintainer rules one recorded reason covering both halves, e.g. \"derived by the platform from an html page source at save (a disagreeing written list is refused); on every other kind a declaration only the load report reads, which the Studio page editor drops on save\". The row cites the ruling record, the way the three ruled classes do (:1404-1430). Cost: cheapest, lands in this PR as one row. It keeps a declared surface on react/full/slotted that no real producer authors and Studio cannot round-trip.",
            "C. Offer it in the page form for the kinds the platform does not derive (a kind-gated control, if the form supports one), and make objectui pageSaveBody kind-gated so a react page keeps its list. Cost: cross-repo (objectui change plus pin bump); the control invites a list the describe tells authors to omit, and on an html page a written list that disagrees is refused 422."
          ],
          "recommendation": "A, by the four axes. Business need, measured: zero page-level requires authored anywhere in examples/** or packages/apps/**; the only writer is the platform stamp for html/jsx; and Studio deletes the key on every kind. The non-html surface therefore has no real producer and no round-trip, a declared surface with no pull. Long-term soundness: contract-first, so the key should mean one thing, and its own describe says \"derived from the source at save, omit it\". A key the spec tells authors to omit, yet admits on kinds where nothing derives it, is a dialect, and B records that dialect instead of removing it. Preventing AI authoring errors: refusal at parse on kinds with nothing to derive from is structurally harder to get wrong than a free list that only earns a load-time warning; C actively invites the list the describe forbids. Startup focus: immediate narrowing, no staged window (no named external user authors it), and no new gate. Trade-off to weigh: A undoes nothing in the engine (the load report stays kind-agnostic for stored rows), but it is a breaking narrowing and a second card, while B lands now in one row. If the PR must not wait, B as an interim row with A filed is the honest split, and that split is the maintainer call."
        }
      ],
      "out_of_scope_findings": [
        "carrier: the decision on this open question · noted, not filed · objectui pin 89cad75d55 app-shell/src/services/builtinComponents.tsx:262-279 pageSaveBody strips requires on EVERY page kind, while its TSDoc reasons only about the html stamp; on a react page an authored list would be dropped on a Studio save. No reach: zero real producers of an authored page requires were measured (measurement item 6), so it does not meet class (c); it is an input to option A/B/C, not a filed defect"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer to the fix round's open question (5959538313): no offer, re-ledgered on the schema's own words

    domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5954183577 · 2026-10-02T19:09Z

    The question is the offer decision for page requires now that it is enforced. The reconciliation ledger's own rule (metadata-form-zod-reconciliation.test.ts:306-309) gives that decision to the enforcement, and this card is the enforcement.

    • No form offer. The key's own describe, at this head, says "derived from the source at save — omit it", and it does not qualify that by page kind. A control would invite exactly the list the contract tells every author to omit. Option C is refused for that reason. It would also need a cross-repo change to Studio's page save body.
    • The row is re-ledgered under the read-off family "platform-written, never authored", quoting the schema's own words, the way the _unpublished row does. It also states the measured truth per kind, so no clause is wider than the measurement 5959538313 supports:
      • on an html / jsx page, on a server with the deployment's SDUI manifest, the save door stamps the compiled list and refuses a written list that disagrees;
      • on react, full and slotted pages nothing derives it, and its one reader is the load report (a warning; the page is still served);
      • the Studio page editor drops the key on every save.
        The row claims no ruling, and the ruled classes (5861442317) are untouched.
    • Option A (refuse the key at parse on the kinds the save door never compiles) is not taken on this card. It is a breaking narrowing of the page contract, with zero authored page-level requires measured in examples/** and packages/apps/**. This seat raises it to the maintainer as a direction question and does not card it. This PR does not wait for it.
    • Review: the at-tier record 5959399303 judged everything else right at 3e1f0dabff. The new head gets a review scoped to this row.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20871,
      "status": "done",
      "branch": "claude/issue-20871-page-requires-live",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21451",
      "head": "54c73b11ff (54c73b11ffd53a573eeed788529e8f54772237fa), pushed 3e1f0dabff..54c73b11ff; no main merge",
      "session": "session_01YDt3PzwfrkuFzUBF89WPmM",
      "premise_still_valid": true,
      "summary": "FIX ROUND 2, executing seat answer 5959584348 (the decision of record on the round-1 open question): no form offer, and the root omit row for page/requires in packages/spec/src/system/metadata-form-zod-reconciliation.test.ts is re-ledgered. The row moved from the \"Declared, not enforced yet\" group into the \"Platform-written, never authored\" group (after field.system, keeping that group type order). Its why now leads with the read-off family phrase and quotes the schema describe in a code span, the way the app._unpublished row does, then states the measured truth per kind with no clause wider than measurement 5959538313, and ends with why a control is wrong. The quoted words occur byte-for-byte once in page.zod.ts. No ruling claimed; RULED_ROOT_REASONS, the comment blocks, the contract and every other file are untouched. One commit, 1 file, +7/-7. PR body patched with one Acceptance-notes line citing 5959584348.",
      "measurement": "unchanged from round 1 comment 5959538313 (at 3e1f0dabff and objectui pin 89cad75d55); the row states only its items (1), (3) and (5): html/jsx with a manifest is stamped and a disagreeing list refused; react/full/slotted are derived by nothing and read only by the load report (warning, still served); the Studio page editor (pageSaveBody, no kind gate) drops the key on every save.",
      "row_new_text": "platform-written, never authored — the schema's own words: `derived from the source at save — omit it`. On an `html` / `jsx` page, on a server with the deployment's SDUI manifest, the save door stamps the compiled list and refuses a written list that disagrees (`page-requires-disagrees-with-source`); on `react`, `full` and `slotted` pages nothing derives it, and its one reader is the load report (a warning; the page is still served); and the Studio page editor drops the key on every save. A control would invite the list the describe tells every author to omit",
      "tests": "At 54c73b11ff, both under the verify lock. (1) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/system/metadata-form-zod-reconciliation.test.ts: \"Test Files 1 passed (1)\", \"Tests 76 passed (76)\", VERDICT command-exit 0; this includes \"every ledger entry still resolves on both sides\" (the key is authorable and the page form does not offer it) and the page top-level reconcileRoot case. (2) pnpm --filter @objectstack/spec run typecheck: VERDICT command-exit 0, \"check:test-typecheck: OK ... 52 file(s) / 246 error(s) / 135 pinned signature(s) held\". Nothing else asserts the row text: repo grep for the old and new phrases and for page.requires outside the ledger finds no other assertion (scripts/pm/check-widening-tells.mjs:515 names the file only as off the contract surface). Narrowed lint union at 54c73b11ff: the 5 changed .ts files, eslint --no-inline-config --format json = 5 results, 0 errors, 0 warnings (config enables no type-aware linting, so untouched files cannot move).",
      "gates": [
        "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at 54c73b11ff :: exit 0 :: 115 commands (10 paths, +85/-18); 5 new vs round 1, all matched by the test-file path",
        "pnpm check:engine-double-contract :: exit 0 :: \"OK — 921 pinned, 129 in the DEBT ledger, 3 exempt\"",
        "pnpm check:objectql-double-limit :: exit 0 :: \"ObjectQL double limit conformance holds: 427 double(s) graded\"",
        "pnpm check:type-check-coverage :: exit 0 :: \"OK — 79/80 workspace packages type-checked (plus the root), 1 in the DEBT ledger\"",
        "pnpm check:type-check-debt :: first unlocked run exit 124 (my 400s cap, a non-measurement) then under the verify lock exit 0 :: \"--re-measure: OK — 1 ledger entr(ies) re-measured in 204.9s, 26 raw tsc error(s) total, none above its recorded number\"",
        "pnpm check:where-matcher :: exit 0 :: \"where-matcher conformance holds: 449 matcher(s) discovered\"",
        "pnpm check:nul-bytes :: exit 0 :: \"OK (scanned 9771 text file(s) ... no raw ASCII control bytes)\"",
        "dispatch-gates --ran :: exit 0 :: \"115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN\"; the 110 round-1 families carry their codes measured at 3e1f0dabff (this commit touches none of their paths), the 5 new ones were measured at 54c73b11ff",
        "PR CI at 54c73b11ff, one read: 37 completed with 0 failures, 4 in progress, 1 queued; not waited on"
      ],
      "line_budget": "10 files, 103 changed lines (+85 / -18) vs the 5000-line human-merge threshold: under; this round 1 file, +7 / -7. No governed surface.",
      "files_changed": [
        "packages/spec/src/system/metadata-form-zod-reconciliation.test.ts (this round: the page/requires root omit row re-ledgered and moved to the platform-written group)",
        "the 9 files of round 1, unchanged"
      ],
      "semver_arm": "unchanged: patch @objectstack/spec + @objectstack/lint, Clause-②: no. This round edits a .test.ts that spec files[] does not ship (it ships src/**/*.zod.ts), so it publishes nothing and the existing changeset still covers the PR.",
      "deviations": [
        "the row was moved, not edited in place: its new reason belongs to the \"Platform-written, never authored\" group, so leaving it under the \"Declared, not enforced yet\" header would contradict that header; both group comments are untouched",
        "this is the third os-dev-report comment on #20871 (dispatch budget: one); the fix-round message asked for it explicitly"
      ],
      "mcp_calls": "0",
      "api_writes": "2 relay dispatches this round (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): issue_patch = PATCH /repos/objectstack-ai/objectstack/issues/21451 (PR body, one Acceptance-notes line; 12355 bytes sent, stored identical, footer still last); this comment = POST /repos/objectstack-ai/objectstack/issues/20871/comments. Plus git push 3e1f0dabff..54c73b11ff (not REST). No review reply posted.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: the seat (5959584348 raises it to the maintainer) · noted, not filed · option A, refusing page requires at parse on the kinds the save door never compiles (react, full, slotted), a breaking narrowing; not taken on this card"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21451 @ 54c73b11

    domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5954183577 · 2026-10-02T19:53Z

    • Shape (read on GitHub): a draft against main. The first line is Fixes #20871, and Clause-②: no sits at a line start. 10 files: the claim's surface, plus the README ledger-row note and the reconciliation test's page.requires row, both judged in review.
    • Contract review:
      • at-tier FAIL 5959399303 at 3e1f0dabff, on one item: the stale reconciliation-ledger row;
      • the dev's measurement 5959538313 and the seat answer 5959584348;
      • at-tier PASS 5960279457 on this exact head. The re-review judged the new row clause by clause and carried the rest of the first record by reference.
    • CI on 54c73b11: 38 success and 4 skipped by design: the second run's Auto Label / Check PR Size from the body edit, Console Pin Gate with no .objectui-sha change, and the opt-in packed-tarball smoke.
    • Governed surface: check-governed-merges --pr 21451 reads NOT governed. 103 changed lines.
    • What lands, against the card:
      • Liveness: page requires goes from planned to live. Its evidence and producer are cited: the save door refuses a written list that disagrees with the compiled source and stores the derived one; the load report names a plugin no manifest component carries.
      • Text: the describe and TSDoc say exactly that. The lint surface reason and the unreleased step-18 entry's reason name the runtime save door. The docs row is regenerated.
      • The ledger row: re-ledgered under "platform-written, never authored", quoting the schema's own words, with the truth per page kind. There is no form offer.
      • Release: a patch changeset for @objectstack/spec and @objectstack/lint.
    • migrations/registry.ts: main moved it after this head (PR feat(spec)!: the analytics row wildcard '*' is admitted only where a count consumes it (#21409) #21431, a D3 entry at a distant position). GitHub reads the PR mergeable clean, and the queue's merge commit is what CI judges.

    Out-of-scope findings:

    • Option A of 5959538313, refusing page requires at parse on the kinds the save door never compiles, is a contract-shape proposal. Under the decision-box rule (pm-dispatch SKILL.md: 「卡先于弹窗」), it goes to a [Decision] card filed by this seat, not to chat alone. That card follows this ACCEPT, and the PR does not wait for it.
    • Two noted-not-filed items from the dev report 5959084718: a test-header sentence in runtime-lazy-deps.test.ts, and the CLI's no-manifest boot line ("not validated at save", which could say "at save or load"). Neither is false on a published surface.

    Next: ready, auto-merge, the queue.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21451 → cfa4d740b7

    domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5954183577 · 2026-10-02T20:21Z

    • Landed: PR fix(spec,lint): page requires is live — refused at save, reported at load #21451 went through the merge queue as cfa4d740b7, with one parent (99589f91a7). This card closed completed by its Fixes line.
    • Blob check against the ACCEPTed head 54c73b11 (ACCEPT 5960317482, at-tier PASS 5960279457):
      • 8 of 10 files are blob-equal.
      • The other two, packages/spec/liveness/README.md and the generated packages/spec/src/migrations/registry.ts, had moved on main after the PR's base c2c21f357c. For each, the PR's own change (c2c21f357c..54c73b11) and the merge's change (99589f91a7..cfa4d740b7) have the same git patch-id --stable. The merge carried exactly the reviewed change.
    • Delivered:
      • Liveness: page requires is live. On a server with the deployment's SDUI manifest, the save door compiles an html / jsx page, refuses a written list that disagrees with its source, and stores the derived one, a draft at its publish. At load, a stored list naming a plugin no manifest component carries is reported, and the page is still served.
      • Text: the describe and TSDoc, the lint surface reason, the unreleased step-18 entry's reason and the docs row say exactly that.
      • The ledger row: the reconciliation ledger's page.requires row stands under "platform-written, never authored", with the truth per page kind. No form offers the key.
    • Decision card: [Decision] page requires on the page kinds whose source is never compiled at save (react, full, slotted): refuse it at parse, or keep admitting a key nothing derives? #21459 (needs-user-decision) asks whether to refuse requires at parse on the kinds the save door never compiles. This card's work does not depend on the answer.
    • State: pm:dispatched is removed in this act. Domain, area and type labels stay.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:specenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions