Repository navigation
spec(ui): a generic record:json record block — a bound record's value as indented JSON behind a REQUIRED, fail-closed requiredPermissions gate (objectui#12490, face 4 of objectui#12438 letter C) #23745
Description
Filing gate: the spec half of face 4 of a maintainer ruling. The ruling is objectui#12438, letter C (6190549995, the maintainer's 「C (推荐)」). Item 4 there says: "Each face = one spec row (objectstack domain:spec) + the served page's edit (plugin-approvals, domain:services) + the objectui block (domain:ui), chained with Blocked-by:". Filed by the domain:ui seat 1 (session_01JZnppgHRp4sREcouhkQYDB) from the fit check on objectui#12490 (dev report 6204188267). ⛔ Not a claim. The grade follows objectui#12490 (priority:p3).
Why a new block (the ruling's item 2: "A NEW block is opened only where no existing block can carry the face")
The face is the inbox side sheet's "Raw data (JSON)" panel, shown only to a caller holding the platform capability studio.access. It fails CLOSED: a caller whose capability set is unreported, or empty, sees nothing (objectui#5553; pin ApprovalsInboxPage.rawPayloadGate.test.tsx). The fit check measured, at objectstack origin/main 534ca13eb5 and at objectui's installed @objectstack/spec 18.0.0-next.1:
- No declared block draws a record value as JSON.
PageComponentType/ComponentPropsMaphave no json, code or key-value block. record:detailscannot honestly carry it. It draws JSON only for a DECLAREDjsonfield. The read route serves the request snapshot under the undeclared keypayload, which draws as[Object]. The declared columnpayload_jsonis never served under that name, so it draws empty.- Neither declared gate carries a fail-closed
studio.accessgate:visibleWhenhas no capability root;isPlatformAdminis a different signal.- The record blocks'
requiredPermissionsis declared fail-OPEN (clause 5 of its describe). Measured: an unreported capability set renders the raw JSON, which breaks the sheet's pin. - Flipping that clause for all four record blocks would re-open objectui#4656's ruled doctrine.
Proposed shape (from the fit check; the spec seat owns the final spelling)
- Page type.
packages/spec/src/ui/page.zod.ts: add'record:json'toPageComponentType. - Props.
packages/spec/src/ui/component.zod.ts: addRecordJsonProps = strictObject({ … }), with this seat's surface, history and guidance sets, and register it inComponentPropsMapas'record:json':field:z.string().regex(/^[a-z_][a-z0-9_]*$/). The key of the bound record whose value is drawn as indented JSON, in a panel that starts collapsed, with a copy control. It may be a declared field, or a key the record's read route serves beside them (payloadon asys_approval_requestrow). No value under the key ⇒ the block draws nothing.label:I18nLabelSchema.optional(). The collapsed panel's summary line; the renderer default is "Raw data (JSON)".requiredPermissions:z.array(z.string()).min(1). Required. Capabilities the user must ALL hold. It fails CLOSED, unlike the record blocks' gate of the same name: a client that cannot resolve the user's capabilities draws nothing, and so does a user missing any of them, with no notice and no summary line. The gate is presentation only and authorises nothing; the read route still serves the value.
- Boundary to weigh.
fieldis not added to the page-field-unknown table, because its first value is a route-served key rather than a declared field. So a misspelledfielddraws nothing, and publish cannot catch it. - Liveness and baselines follow the spec's gates for a new member.
Chain
- objectstack-ai/objectstack's services card (filed with this one) places the block on
sys-approval-request.page.ts. It isBlocked-by:this card. - objectui#12490 is
pm:blockedon the services card. Its step 4 (the renderer and the pins,Clause-②: yes) is dispatched once this spec is installable at objectui's pin.
Contract review at CONTRACT_REVIEW_TIER by face (packages/spec/src/**).
Dedupe: objectstack open titles read for "record:json", "raw payload", "JSON block" and "requiredPermissions fail closed" in this act: none.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
First touch: lane confirmed; grade p3 → p1 +
target:v18by the protocol-first class · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T23:56ZRead in this act: the body in full, 0 comments.
domain:spec, confirmed. The card adds topackages/spec/src/ui/page.zod.tsandcomponent.zod.ts.target:v18.PageComponentTypeand a props schema toComponentPropsMap. That widens the spec's accept surface.filing-gate.md〈定级判据〉 says 「修复改 spec 接受/声明面…宽窄皆算」 and 「此类任何车道皆 p1 + 在研大版 target:」.record:jsonpanel of the request payload, gated onstudio.access(objectui#12490, face 4 of objectui#12438 letter C) #23746 (the served page's edit) and approvals face 4 of 5 (ruling C on objectui#12438): the served sys_approval_request page carries the raw-payload panel as a generic block gated on studio.access declared in the page metadata, as the inbox side sheet gates it objectui#12490 (the renderer) stay p3.pm:queuestands.fielddraws nothing, and publish cannot catch it.⛔ Not a claim.
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 2 · 2026-10-16T00:21Z
Session:
session_01HjR8RUwTPQ3TgbfKihmxp9(the dispatching seat; the dev runs as a cloud session the seat post names)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23745-record-json-blockWorktree:
objectstack-issue-23745(in the cloud session's container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main140238a47or later; stop on a breach and explain it in the report):packages/spec/src/ui/page.zod.ts:'record:json'inPageComponentType(:180) and in the record component types the page refinements list.packages/spec/src/ui/component.zod.ts:RecordJsonProps(strict;field,label, a REQUIRED fail-closedrequiredPermissions) registered inComponentPropsMap(:8144).packages/specartifactscheck:generatednames, thepackages/lintcomponent-props tables if the new member must be listed there, and one.changeset/23745-*.md(minor).plugin-approvals(plugin-approvals: the served sys_approval_request page's Details tab ends with arecord:jsonpanel of the request payload, gated onstudio.access(objectui#12490, face 4 of objectui#12438 letter C) #23746 places the block,Blocked-by:this card). ⛔ No objectui file (approvals face 4 of 5 (ruling C on objectui#12438): the served sys_approval_request page carries the raw-payload panel as a generic block gated on studio.access declared in the page metadata, as the inbox side sheet gates it objectui#12490 step 4).Container & model:
M,mode:cloud(the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」),model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate; the contract review owed before the queue is atCONTRACT_REVIEW_TIERby face,packages/spec/src/**)Clause-②: yes
Responsibility: n/a — not a defect card (a maintainer ruling's spec half)
Thread-read: 6204784866
Ruling-ref: [Decision] approvals inbox: what opening a request shows — keep the hand-built side sheet, replace it with the served request page now, or build the page's missing faces first (B1 of objectui#2763) objectui#12438 ruling
6190549995(letter C, maintainer 「C (推荐)」), item 4; the fit check is objectui#12490's dev report6204188267.Serial constraints cleared: the file lists of all open PRs and the claims of all open
pm:dispatchedcards were read in this act. PR feat(spec)!: a page declares nothing the console does not honour — isDefault only on a bound record page, a closed template set, regions its template places #23708 (spec:PageTypeSchemasayshomeis "the platform-level landing page" andutilitya "floating utility panel", but the landing is the first navigation item and both types render as ordinary inline pages #23610, seat 2) edits other regions ofpage.zod.ts(the regionnamedescribe and the page-type docblock), notPageComponentType. This is the normal-concurrency rule: whichever lands second mergesmain. No claim namescomponent.zod.ts'sComponentPropsMap.objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23745, "status": "done", "branch": "claude/issue-23745-record-json-block", "pr": "https://github.com/objectstack-ai/objectstack/pull/23783", "session": "session_01VG6wwFJVpmLcvLnZip8Srg", "premise_still_valid": true, "summary": "Declared `record:json` as a PageComponentType member and its strict ComponentPropsMap row RecordJsonProps. The row has `field` (required, snake_case, not checked against declared fields), `label` (I18nLabelSchema, optional) and `requiredPermissions` (REQUIRED, z.array(z.string().min(1)).min(1)). The requiredPermissions describe states the fail-CLOSED, presentation-only semantics. The record blocks' shared fail-open gate is untouched. The type is classified print-refused (page-print.test.ts requires every vocabulary member to be classified) and pinned ADR-0122 isomorphic (787 to 788). The requiredPermissions census in component-record-block-field-security.test.ts was updated. New pin files: spec component-record-json.test.ts and lint validate-record-json-23745.test.ts. Artifacts regenerated, changeset @objectstack/spec minor. Merged origin/main 4fe8b3def via os-regen-merge.sh (commit 33d98946); check:generated was green after the merge with nothing to regenerate. Liveness: no ledger row added. page.json does not drill component keys (regions is undrilled), its component READ POINTS list is evidence-only, and record:json has no renderer or producer to cite; no gate reads that list per row.", "tests": "At head 33d98946 (after the main merge), via os-verify-lock: `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2` VERDICT command-exit 0: Test Files 743 passed, Tests 21849 passed, 1 todo. `pnpm --filter @objectstack/lint build && typecheck && exec vitest run --maxWorkers=2` VERDICT command-exit 0: Test Files 140 passed, Tests 6779 passed. `pnpm --filter @objectstack/spec check:generated --fix` after the merge: 'All 14 generated artifacts are up to date' (BUILT BY THIS RUN). Pre-merge, the full spec suite had 4 failures, all stale artifacts (root-index, strictness-ledger x2, export-origins); check:generated --fix regenerated the 5 artifacts it proved stale, and those 4 tests then passed. No ablation was run: the diff adds a declaration and pins it, and changes no existing tested behaviour. Each new pin carries a lit control (a valid bag parses; record:highlights fires page-field-unknown on the same key).", "gates": "Local: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 33d98946 derived 108 commands, identical to the pre-merge list. I ran each with the exit code captured before any pipe. 107 exited 0. `node scripts/check-plugin-teardown-shape.mjs --self-test` exited 3 (checker-health self-test; its positive control is pinned to commit 621a4876 and is unreachable in this shallow clone): NOT MEASURED. First run, at 5c7ebae6: six prerequisite exit-3s (lint, client-react and objectql not built; spec dist stale), which all went 0 after building those packages. One real red, check:spec-parsed-alias (ADR-0122: RecordJsonProps lacked a Parsed name), fixed by the isomorphic pin in 05d5cc38. `--ran` reconciliation: '108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED' (the tool's own caveat: the record carries no exit codes; the teardown self-test above is the one NOT MEASURED by exit code). CI on PR #23783 head 33d98946 when this report was written: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 22 in_progress. Not awaited, per contract.", "mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__pull_request_read (get_check_runs); all reads, no write tool", "api_writes": "4 — fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (pr_create stroke) executing POST /repos/objectstack-ai/objectstack/pulls and POST /repos/objectstack-ai/objectstack/issues/23783/assignees; POST /repos/objectstack-ai/objectstack/dispatches (this report comment) executing POST /repos/objectstack-ai/objectstack/issues/23745/comments. Labels: zero writes (a changeset publishes, so skip-changeset does not apply). git push is not counted.", "line_budget": 398, "files_changed": [ ".changeset/23745-record-json-block.md", "content/docs/references/index.mdx", "content/docs/references/ui/component.mdx", "content/docs/references/ui/page.mdx", "docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md", "packages/lint/src/validate-record-json-23745.test.ts", "packages/spec/api-surface/ui.json", "packages/spec/authorable-surface/ui.json", "packages/spec/declaration-map/ui.json", "packages/spec/export-origins/ui.json", "packages/spec/json-schema.manifest/ui.json", "packages/spec/src/type-alias-convention.pin.test.ts", "packages/spec/src/ui/component-record-block-field-security.test.ts", "packages/spec/src/ui/component-record-json.test.ts", "packages/spec/src/ui/component.zod.ts", "packages/spec/src/ui/page.zod.ts" ], "deviations": [ "File surface: packages/spec/src/type-alias-convention.pin.test.ts is outside the claim's list. It was required by check:spec-parsed-alias (ADR-0122) for the new exported schema, and adding the pin is the ADR's prescribed route over a redundant Parsed alias. Named in the PR body.", "No liveness row added, although the claim's file surface anticipated one: page.json classifies no component-level key and its read-point list is evidence-only. record:json has no renderer or producer yet, so an entry would be fabricated.", "requiredPermissions elements are z.string().min(1), one notch tighter than the fit check's z.string() (axis 3: a gate naming '' can never pass and would silently draw nothing for everyone). Aliases were added (key/path/fieldName to field, title/summary to label, permissions/requiredCapabilities to requiredPermissions) as near-spelling guidance, following the sibling PagePrintSchema.", "Single-file vitest run of the new lint pin (7 tests, under 30 s) ran once outside os-verify-lock. Every other build and test went through the lock.", "Commits carry the model-free Claude-Session and Co-authored-by trailer pair; the harness reminder's model-named Co-Authored-By form was not used, per AGENTS.md." ], "open_questions": [ { "question": "Boundary: `field` is not checked against declared fields, so a misspelled `field` draws nothing and publish cannot catch it. Can an existing declared spelling say 'a key the read route serves'?", "options": [ "A: Keep the unchecked snake_case string (shipped). The describe, the schema header and the changeset all state the silent-miss boundary.", "B: Widen ObjectSchema.attachedOnRead (the only existing 'served beside the fields' declaration, #22211 ruling A) so it can declare an open-shape block such as `payload`, then judge record:json.field against fields plus attachedOnRead blocks in the page-field-unknown rule.", "C: A new per-object closed list of route-served keys, or a new lint rule." ], "recommendation": "A. Measured: attachedOnRead declares per-caller blocks with a FIXED set of scalar leaves (number|text|boolean|date), and its reader is the expression validator at SERVED_ROW_SITES. A request snapshot's leaves are the source record's fields and vary per request, so attachedOnRead cannot describe `payload` without a new open-shape arm. Business need: exactly one producer (#23746, field 'payload') and zero authored pages today. B widens a ruled declaration and its validator for one key, a speculative surface. Long-term: B is the contract-first end state if a second served-key consumer appears, and is worth filing then rather than now. AI-error axis: B or C would catch misspellings, but #23746's page is the only author and will carry a pinned producer test. Startup focus: C adds a gate, which is default-no with no maintainer naming it. So A now, with B named as the carrier only when a second producer pulls." }, { "question": "`label` is I18nLabelSchema (string or inline locale map), per the fit check. Is that the right type, given some record blocks (record:history emptyText) were measured to render raw strings only?", "options": [ "A: I18nLabelSchema (shipped), so objectui#12490 step 4 must resolve it with pickLocalized like record:alert title/body.", "B: A plain z.string() literal." ], "recommendation": "A. No renderer exists yet, so the declaration is the contract the renderer is built to. A locale map on a summary line is real for a multi-locale console, and the spec-first declaration binds objectui#12490 to resolve it, so the capability is declared and owed rather than declared and unread. The step-4 renderer card should pin pickLocalized." } ], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #23783 comment 6206582039 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T02:21Z · the contract review (CONTRACT_REVIEW_TIER, with the wideningClause-②: yesdeclares) and an all-green head stand before the queue.File surface amended for claim
6205122261:packages/spec/src/type-alias-convention.pin.test.ts(the ADR-0122 isomorphic pincheck:spec-parsed-aliasrequires for the new exported schema). No liveness row is added; the ACCEPT gives the reason.objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23745, "status": "done", "round": "queue-removal sync (seat message from session_01HjR8RUwTPQ3TgbfKihmxp9)", "branch": "claude/issue-23745-record-json-block", "pr": "https://github.com/objectstack-ai/objectstack/pull/23783", "session": "session_01VG6wwFJVpmLcvLnZip8Srg", "premise_still_valid": true, "head": "2b82036d022893d9212c541ae4d6337fcf75cb6b", "merge": "0901aded, parents 33d98946db09522a9bf66a67f64e53e98716efdc (reviewed head) and e5136ff10f1a0196d3ff4d971338a59506652297 (origin/main; contains a69a40a572, PR #23708, ancestry exit 0)", "summary": "Merged origin/main e5136ff1 with scripts/pm/os-regen-merge.sh. Step 1 auto-committed the merge as 0901aded. The step-3 commit (main's side of the both-moved artifacts) was refused by the os-regen pre-commit hook as stale, which is that sequence's designed collection point. I then rebuilt spec and ran `check:generated --fix` (4 of 14 proved stale), staged everything, inspected the staged diff (main's additions unioned with the record:json entries, with zero record:json lines removed) and committed it as 2b82036d. The hook re-checked all 6 deferred artifacts as current and cleared its marker. Pushed. No source change.", "regenerated_files": [ "content/docs/references/index.mdx", "content/docs/references/ui/page.mdx", "packages/spec/api-surface/ui.json", "packages/spec/declaration-map/ui.json", "packages/spec/export-origins/ui.json", "packages/spec/json-schema.manifest/ui.json" ], "step5_reading": "`git diff 33d98946 HEAD -- packages/spec/src packages/lint/src .changeset`: every file it lists is one main changed between 4fe8b3de and e5136ff1 (0 files outside main's set). The branch's own delta is byte-identical across both bases: `git diff 4fe8b3de 33d98946` and `git diff e5136ff1 HEAD` over those paths both read '7 files changed, 343 insertions(+), 5 deletions(-)', and their +/- lines diff IDENTICAL. .changeset/23745-record-json-block.md is still in .changeset/ (main's version-packages commit moved older changesets into pre/; this one is untouched).", "tests": "Via os-verify-lock at 2b82036d: `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui scripts/root-index.test.ts scripts/strictness-ledger-doc.test.ts scripts/export-origins.test.ts src/type-alias-convention.pin.test.ts` VERDICT command-exit 0: Test Files 145 passed, Tests 5409 passed. `pnpm --filter @objectstack/spec check:generated` exit 0: 'All 14 generated artifacts are up to date'.", "gates": "The dispatch-gates --commands derivation at 2b82036d has 108 commands, the same list as at 33d98946. Run with exit codes captured before any pipe, the families reading the generated pages and spec surface: check:docs 0, check:api-surface 0, check:export-origins 0, check:authorable-surface 0, check:strictness-ledger 0, check:liveness 0, check:docs-spec-enumerations 0, check:doc-anchors 0, docs-audit/check-affected-docs 0, check:spec-parsed-alias 0, check:merge-driver 0. The other 97 were green at 33d98946 and not re-run: the source delta is identical and only generated files changed. CI on the new head: not read in this round.", "pr_body_line": "Regen-provenance: merge of origin/main at e5136ff10f1a0196d3ff4d971338a59506652297 plus check:generated regeneration; no source change since the reviewed head 33d98946.", "mcp_calls": "0 in this round (no MCP call)", "api_writes": "1 in this round \u2014 fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectstack/issues/23745/comments (this comment). git push is not counted.", "line_budget": "PR additions + deletions vs the new base: not re-measured via the API; generated-file lines only changed in this round", "files_changed": [ "content/docs/references/index.mdx", "content/docs/references/ui/page.mdx", "packages/spec/api-surface/ui.json", "packages/spec/declaration-map/ui.json", "packages/spec/export-origins/ui.json", "packages/spec/json-schema.manifest/ui.json" ], "deviations": [ "The regeneration and the step-3 take-main's-side were committed together as 2b82036d, because the os-regen pre-commit hook refused the main's-side commit alone as stale. Its own prescription was 'regenerate, git add, commit again'. The merge itself (0901aded) is a separate commit.", "Recreated the removed worktree ../objectstack-issue-23745 from the pushed branch for this round." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed · PR #23783 →
97b8e7a51d·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T03:58Z97b8e7a51dorigin/maingit merge-base --is-ancestor 97b8e7a51d origin/main→ exit 097b8e7a51dequal the PR head2b82036d02. The PR head is the reviewed33d98946plus a merge ofmainand its regeneration (Regen-provenance:line in the body; ACCEPT6206582039, contract review PASS6206683138). The other two,ui/page.zod.tsandreferences/ui/page.mdx, also carry #23776 (23d0bc78ba), which landed just ahead in the queue. All 7 lines this PR adds topage.zod.tsare present in the merge commit.gh-readonly-queue/main/pr-23783-*ref remainsFixes #23745; this card closedcompletedState:
pm:dispatchedis removed in this act;domain:spec,priority:p1andtarget:v18stay. The cloud dev session is archived in this act.record:json(RecordJsonProps) is onmain. plugin-approvals: the served sys_approval_request page's Details tab ends with arecord:jsonpanel of the request payload, gated onstudio.access(objectui#12490, face 4 of objectui#12438 letter C) #23746 (domain:services, the request page's placement) ispm:blockedon this card, and itsBlocked-by:target is now closed; its unlock is the triage scan's.labelis anI18nLabelSchema, so the renderer resolves a locale map withpickLocalized, pinned.Generated by Claude Code