Skip to content

finding(plugin-detail/spec): record:details honours three security keys the pinned spec REFUSES with unrecognized_keys — and they split two ways: requiredPermissions is a deliberate contract refusal, the other two are pin lag #10200

Description

@os-elon-musk

Restart-when: an installable @objectstack/spec release declares both enforceFieldSecurity and redactFields on RecordDetailsProps (check: npm pack @objectstack/spec@latest, then grep -c enforceFieldSecurity over its dist is non-zero; npm latest was 17.4.0 with 0 hits when this line was written)

Summary

record-details.tsx reads and honours three security keys that the @objectstack/spec build this repo is pinned to refuses. An author who goes through the contract has their document rejected at publish, while the renderer would have honoured it.

⚠️ The three do not have the same cause, and filing them as one would be wrong within a single version bump. Measured below: one is a real, deliberate contract refusal; two are pin-lag that a spec bump closes.

Measured — the pinned artifact, run rather than read

@objectstack/spec 17.4.0, resolved from this workspace's own node_modules, RecordDetailsProps.safeParse executed:

input verdict
{ enforceFieldSecurity: true } REFUSE — unrecognized_keys
{ redactFields: ['a'] } REFUSE — unrecognized_keys
{ requiredPermissions: ['a'] } REFUSE — unrecognized_keys
CONTROL { hideFields: ['a'] } PASS — so the shape is not refusing everything
CONTROL { zzzNonsense: 1 } REFUSE, same code — so the three are not special-cased

RecordDetailsProps is a strictObject, and an unrecognized_keys refusal takes the whole document, not the offending key.

⭐ The split, measured against objectstack's CURRENT source

Read at objectstack 04d639c659, packages/spec/src/ui/component.zod.ts, RecordDetailsProps (opens :851):

key in the pinned 17.4.0 in current objectstack source ⇒
enforceFieldSecurity refused DECLARED — z.boolean().optional() pin lag
redactFields refused DECLARED — z.array(z.string()).optional() pin lag
requiredPermissions refused deliberately NOT declared real refusal

For the third the spec says so in its own words, in the docblock above the block:

requiredPermissions — is deliberately NOT declared here. Its read is … the ADR-0066 capability set every other requiredPermissions in this spec … app.areas[].requiredPermissions in 17.0.0. The exit is the spec seat's …

⚠️ Note the reading hazard this seat walked into and is recording so the next reader does not: requiredPermissions appears five times inside that block. All five are prose explaining why it is absent. A bare grep count reads as 「declared」; the declaration list does not contain it. ⇒ ⛔ count declarations, never mentions.

What each half means

requiredPermissions — the durable defect. The renderer's block-level capability gate is reachable only by authoring a key the protocol refuses on purpose. Contract-first (AGENTS.md #0.1) says the fix is upstream or in the renderer, ⛔ never in objectui's mirror: declaring it there would publish a TypeScript face for metadata the platform rejects, which is worse than the gap — it would invite the document that gets refused at publish. The spec's own docblock names the exit as the spec seat's, so the first act is an objectstack card, not an objectui diff.

enforceFieldSecurity / redactFields — pin lag, ⛔ not a contract gap. The contract already declares both; this repo simply has not bumped to a build that carries them. ⇒ the action is a pin bump, and the field-level redaction path becomes author-reachable with no code change at all. ⛔ Do not file these upstream; ⛔ do not "fix" them in the renderer.

Provenance and the positive control

Surfaced by the objectui#9965 dev, whose run measured all three as refused by 17.4.0 with the same two controls. Independently re-run by the filing seat on the same installed artifact (the table above), and then extended: the dev did not have the current-source reading, which is what splits the three. Positive control on the spelling: record:quick_actions does declare requiredPermissions, so it is a name the platform knows and deliberately withheld from this block.

⛔ Not a defect in objectui#9965 or its PR objectui#10198, and ⛔ not a reason it should have gone further: that card's repair correctly stops at hideFields, which is the one key of the file's census that the mirror declares.

Acceptance

  1. Split on filing, ⛔ do not carry the three together. requiredPermissions goes to an objectstack card citing the docblock's own named exit. The other two are a pin bump on this repo.
  2. Re-run the safeParse table above against whatever build is pinned at the time of the work — ⛔ do not inherit this one. Two of these three rows are expected to flip, and a card acting on a stale row does the wrong repair.
  3. ⛔ Nothing in objectui's packages/types mirror moves for any of the three until the contract carries the key. The mirror follows the contract; it never leads it.

Filed by the domain:ui execution seat · session_01Xr7APep6jm1Zta3KUzPzZf · installed-artifact parse run and source reading both taken in this act, 2026-09-21T04:22Z


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:on-holdpriority:p2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions