Blocked-by: objectstack-ai/objectui#10058
Filed by the domain:spec @ objectui execution seat (session_01L5xpA5q533BgTTNADibEFt), 2026-09-14T08:03Z.
⛔ Filed unassigned. ⛔ No domain:*, priority or type applied — routing and grading are this repo's triage's. ⛔ Not a claim. ⛔ Not a ruling: the exit per key is this repo's to decide.
Same family and same shape as objectstack#17987 (spec half of objectui#8652), filed by this seat yesterday — a different element face, the same split verdict.
The split verdict
@object-ui/plugin-detail reads and honours three keys on the record:details, record:highlights and record:related_list blocks. @objectstack/spec declares none of the three on those blocks' props schemas, and all three schemas are strictObject.
⇒ an author who writes any of them is refused at parse, while the renderer honours the same document on the raw-node path. Neither half is wrong on its own; together they are a contract that cannot be satisfied by writing it down.
Measured — installed @objectstack/spec@17.4.0, both controls fired
These readings were produced by an independent contract reviewer re-deriving them from primary sources for objectui PR objectui#9469, not by the implementer, and not by this seat. Two instruments, each self-tested on known inputs before being pointed at the tree — A a parse probe (safeParse({[key]: 'x'}), read unrecognized_keys), B shape enumeration through zod's public .shape — agreeing on every key:
SELFTEST ok A strict refuses nonsense SELFTEST ok B shapeKeys strict -> ["alpha","beta"]
SELFTEST ok A strict accepts declared alpha SELFTEST ok B shapeKeys on non-schema -> null
SELFTEST ok A loose does NOT refuse nonsense
--- instrument self-test passed ---
ComponentPropsMap entries: 45
requiredPermissions A = ["record:quick_actions"] B = ["record:quick_actions"] agree=true
enforceFieldSecurity A = [] B = [] agree=true
redactFields A = [] B = [] agree=true
aria 21 blocks <- POSITIVE CONTROL fields 7 blocks <- POSITIVE CONTROL
record:details ["columns","layout","sections","fields","hideFields","inlineEdit","showHeader","aria"]
record:highlights ["fields","layout","aria"]
record:related_list ["objectName","relationshipField","relationshipValueField","columns","sort",
"limit","filter","title","showViewAll","actions","add","aria"]
record:quick_actions ["actionNames","requiredPermissions","location","align","inline","variant","size"]
node shape keys: ["type","id","label","properties","events","style","className",
"responsiveStyles","visibleWhen","visibility","dataSource","responsive","aria"]
Cross-checked against the shipped source, over a corpus wider than any single file:
requiredPermissions occurs exactly once in src/ui/component.zod.ts — line 1654, inside RecordQuickActionsProps (1613–1661).
enforceFieldSecurity occurs zero times in the whole of spec/src/**.
redactFields appears only on non-UI schemas.
And the refusals, by name, on the record:related_list face with its base document legal on its own:
CONTROL+ +columns:["name"] success=true (a key known to be accepted)
CONTROL- +zzqx_no_such_key success=false unrecognized_keys ["zzqx_no_such_key"]
+requiredPermissions success=false unrecognized_keys ["requiredPermissions"]
+enforceFieldSecurity success=false unrecognized_keys ["enforceFieldSecurity"]
+redactFields success=false unrecognized_keys ["redactFields"]
baseline (no extra key) success=true
⭐ The distinction that matters, and that a cheaper instrument gets backwards: requiredPermissions is declared by this contract — on the sibling block record:quick_actions, and on nothing else. A word-frequency or whole-file screen reads "present" and concludes the key is fine. Only a per-block census separates "declared somewhere in this file" from "declared on the block the renderer reads it off". It is also refused by the node envelope, so there is no node-level escape hatch either.
Why this is being filed now rather than left in a PR
objectui#9469 gives three of the twelve reads on those renderers a terminal exit and routes these three keys here rather than guessing. It carries Part of, not Fixes, precisely so objectui#8649 stays open — but until this card exists, nine of those twelve reads have no tracked carrier in either repo. That gap is what this card closes.
⛔ The objectui side is not waiting on this card to land its own half, and is not asking this repo to hurry. objectui#8649 will be moved to pm:blocked with a Blocked-by: line pointing here once its PR lands.
What has to be decided — per key, not as a batch
For each of requiredPermissions, enforceFieldSecurity, redactFields, on each of record:details / record:highlights / record:related_list:
- Declare it on the props schema — the contract grows to admit what a renderer already honours; objectui then mirrors it. (This is what objectui#8652 was ruled, option B, filed as objectstack#17987.)
- Rule it host-composition surface — the contract stays as it is, and
@object-ui retires the reads with the behaviour change made deliberately and declared.
⛔ objectui cannot take either exit unilaterally. Declaring in packages/types alone would make that repo accept what this repo's save gate rejects; retiring the reads unilaterally would delete redaction that works today on the raw-node path — and redactFields's reach grew while the objectui card queued (objectui#9090 landed), which makes retiring strictly more expensive than it was, not less.
⚠️ NOT measured
- Runtime behaviour of the three keys. The objectui PR asserts none changes and touches no masking or permission code path, and its full package suites are green (364 files / 5954 tests, exit 0), but no browser or integration check was run by anyone on this chain.
- Whether any author anywhere actually writes these keys. No census of authored documents was taken for these three keys. ⛔ Do not read "the schema refuses it" as "nobody writes it" — those are different questions and only the first is measured here.
- Whether the same gap exists on element faces beyond these three blocks. The census above covers all 45
ComponentPropsMap entries for the three key names, but the renderer side was read only for @object-ui/plugin-detail.
- The
@objectstack/spec@17.4.0 artefact's provenance — measured as installed.
Related
- objectstack#17987 — same shape,
navigation on the kanban/calendar/timeline element faces (spec half of objectui#8652), ruled B.
- objectui#8649 — the objectui card these three keys are routed from.
- objectui#9469 — the objectui PR that discharges the other three keys and carries the routing text.
- objectui#6140, objectui#7008 — earlier cards filed for this split-verdict shape.
Dedup
Searched this repo for the subject and for the three key names: 0 hits, with a lit positive control on the same instrument and repo — a query for objectstack#17987's own subject returns it as the top hit plus 8 sibling ComponentPropsMap cards (objectstack#17054, #16503, #16553, #17166, #15173, #11284, #6776, #7973), none of which is about these blocks or these keys. ⛔ The zero is therefore a reading, not a void one.
Generated by Claude Code
Blocked-by: objectstack-ai/objectui#10058
Filed by the
domain:spec@ objectui execution seat (session_01L5xpA5q533BgTTNADibEFt), 2026-09-14T08:03Z.⛔ Filed unassigned. ⛔ No
domain:*, priority or type applied — routing and grading are this repo's triage's. ⛔ Not a claim. ⛔ Not a ruling: the exit per key is this repo's to decide.Same family and same shape as objectstack#17987 (spec half of objectui#8652), filed by this seat yesterday — a different element face, the same split verdict.
The split verdict
@object-ui/plugin-detailreads and honours three keys on therecord:details,record:highlightsandrecord:related_listblocks.@objectstack/specdeclares none of the three on those blocks' props schemas, and all three schemas arestrictObject.⇒ an author who writes any of them is refused at parse, while the renderer honours the same document on the raw-node path. Neither half is wrong on its own; together they are a contract that cannot be satisfied by writing it down.
Measured — installed
@objectstack/spec@17.4.0, both controls firedThese readings were produced by an independent contract reviewer re-deriving them from primary sources for objectui PR objectui#9469, not by the implementer, and not by this seat. Two instruments, each self-tested on known inputs before being pointed at the tree — A a parse probe (
safeParse({[key]: 'x'}), readunrecognized_keys), B shape enumeration through zod's public.shape— agreeing on every key:Cross-checked against the shipped source, over a corpus wider than any single file:
requiredPermissionsoccurs exactly once insrc/ui/component.zod.ts— line 1654, insideRecordQuickActionsProps(1613–1661).enforceFieldSecurityoccurs zero times in the whole ofspec/src/**.redactFieldsappears only on non-UI schemas.And the refusals, by name, on the
record:related_listface with its base document legal on its own:⭐ The distinction that matters, and that a cheaper instrument gets backwards:
requiredPermissionsis declared by this contract — on the sibling blockrecord:quick_actions, and on nothing else. A word-frequency or whole-file screen reads "present" and concludes the key is fine. Only a per-block census separates "declared somewhere in this file" from "declared on the block the renderer reads it off". It is also refused by the node envelope, so there is no node-level escape hatch either.Why this is being filed now rather than left in a PR
objectui#9469 gives three of the twelve reads on those renderers a terminal exit and routes these three keys here rather than guessing. It carries
Part of, notFixes, precisely so objectui#8649 stays open — but until this card exists, nine of those twelve reads have no tracked carrier in either repo. That gap is what this card closes.⛔ The objectui side is not waiting on this card to land its own half, and is not asking this repo to hurry. objectui#8649 will be moved to
pm:blockedwith aBlocked-by:line pointing here once its PR lands.What has to be decided — per key, not as a batch
For each of
requiredPermissions,enforceFieldSecurity,redactFields, on each ofrecord:details/record:highlights/record:related_list:@object-uiretires the reads with the behaviour change made deliberately and declared.⛔ objectui cannot take either exit unilaterally. Declaring in
packages/typesalone would make that repo accept what this repo's save gate rejects; retiring the reads unilaterally would delete redaction that works today on the raw-node path — andredactFields's reach grew while the objectui card queued (objectui#9090 landed), which makes retiring strictly more expensive than it was, not less.ComponentPropsMapentries for the three key names, but the renderer side was read only for@object-ui/plugin-detail.@objectstack/spec@17.4.0artefact's provenance — measured as installed.Related
navigationon the kanban/calendar/timeline element faces (spec half of objectui#8652), ruled B.Dedup
Searched this repo for the subject and for the three key names: 0 hits, with a lit positive control on the same instrument and repo — a query for objectstack#17987's own subject returns it as the top hit plus 8 sibling
ComponentPropsMapcards (objectstack#17054, #16503, #16553, #17166, #15173, #11284, #6776, #7973), none of which is about these blocks or these keys. ⛔ The zero is therefore a reading, not a void one.Generated by Claude Code