Skip to content

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

Closed

Description

@objectstack-fleet

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 / ComponentPropsMap have no json, code or key-value block.
  • record:details cannot honestly carry it. It draws JSON only for a DECLARED json field. The read route serves the request snapshot under the undeclared key payload, which draws as [Object]. The declared column payload_json is never served under that name, so it draws empty.
  • Neither declared gate carries a fail-closed studio.access gate:
    • visibleWhen has no capability root; isPlatformAdmin is a different signal.
    • The record blocks' requiredPermissions is 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' to PageComponentType.
  • Props. packages/spec/src/ui/component.zod.ts: add RecordJsonProps = strictObject({ … }), with this seat's surface, history and guidance sets, and register it in ComponentPropsMap as '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 (payload on a sys_approval_request row). 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. field is not added to the page-field-unknown table, because its first value is a route-served key rather than a declared field. So a misspelled field draws 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 is Blocked-by: this card.
  • objectui#12490 is pm:blocked on 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

  1. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    First touch: lane confirmed; grade p3 → p1 + target:v18 by the protocol-first class · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T23:56Z

    Read in this act: the body in full, 0 comments.

    ⛔ Not a claim.

  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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 as GET /user answers it; this card's assignee)
    Branch: claude/issue-23745-record-json-block
    Worktree: objectstack-issue-23745 (in the cloud session's container)
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 140238a47 or later; stop on a breach and explain it in the report):

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23783 comment 6206582039 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T02:21Z · the contract review (CONTRACT_REVIEW_TIER, with the widening Clause-②: yes declares) 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 pin check:spec-parsed-alias requires for the new exported schema). No liveness row is added; the ACCEPT gives the reason.

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23783 → 97b8e7a51d · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T03:58Z

    probe reading
    PR object merged 2026-10-16T03:57Z out of the merge queue (second entry, after the sync round); merge commit 97b8e7a51d
    origin/main git merge-base --is-ancestor 97b8e7a51d origin/main → exit 0
    merged vs reviewed head 14 of 16 files' blobs at 97b8e7a51d equal the PR head 2b82036d02. The PR head is the reviewed 33d98946 plus a merge of main and its regeneration (Regen-provenance: line in the body; ACCEPT 6206582039, contract review PASS 6206683138). The other two, ui/page.zod.ts and references/ui/page.mdx, also carry #23776 (23d0bc78ba), which landed just ahead in the queue. All 7 lines this PR adds to page.zod.ts are present in the merge commit.
    queue branch no gh-readonly-queue/main/pr-23783-* ref remains
    mis-close scan the body's only closing keyword is Fixes #23745; this card closed completed

    State: pm:dispatched is removed in this act; domain:spec, priority:p1 and target:v18 stay. The cloud dev session is archived in this act.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions