Repository navigation
feat(spec): declare requiredPermissions on record:details / highlights / related_list with one true describe shared with record:quick_actions - #19913
Conversation
…s / related_list with one true describe shared with record:quick_actions The three record blocks refused requiredPermissions by name while objectui's renderers read it as an ADR-0066 capability set, fail-closed, at the pinned console. Declare it on all three with record:quick_actions' shape, and give all four ONE describe (seat ruling A): capabilities not object actions, all required, a notice in place of the content, presentation only, and fail-open when the client cannot resolve capabilities. The quick_actions describe was false on object scoping, so its published text changes too. Rewrites the texts that said the key was deliberately not declared, flips the absence pin to accept pins (instruments A/B, lit controls aria/fields), and corrects the pending pair changeset that the declaration makes false. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…e for the record-block requiredPermissions declaration gen:schema adds the three ui/Record*Props:requiredPermissions rows; gen:docs adds the three table rows and carries the new shared describe onto record:quick_actions. check:generated: all 15 artifacts up to date. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9a9bfaab55707c5231ec037cd38c1d7c0307ca40 && git checkout 9a9bfaab55707c5231ec037cd38c1d7c0307ca40
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7ddf396109b9d423cf52c2e5b69b0e34f235e7f5 b19ac16c77e812659a5a98d5740b82876b265ebf && git checkout -B drift-repro 7ddf396109b9d423cf52c2e5b69b0e34f235e7f5 && git merge --no-ff b19ac16c77e812659a5a98d5740b82876b265ebf
node scripts/docs-audit/affected-docs.mjs --json 7ddf396109b9d423cf52c2e5b69b0e34f235e7f5
|
|
Contract reviewServed-tier: Reviewed and posted 2026-09-23T18:24Z by the at-tier review subagent the ① Derived judgmentsShared describe (
Pins: head run 25/25 green. My ablation (three declarations replaced, anchor hits 3, decl lines 4→1, then Census (head Generated files: Shipping sentences: describe — true (above). New changeset ② Semver levelBoth changesets ③ Boundary flags
Blocking: (1) Implemented-by: VERDICT: FAIL |
…he gate hides the block At the pinned console the capability gate renders an insufficient-permissions notice in place of the block's content; it does not hide the block. Deletions only: the pending pair changeset ends at "not a member of this pair", the constant's docblock drops "hides a block and", the family header drops "and fail-closed", the new changeset drops the "hides the bar" clause, and the shared describe ends at "it fails open." (an empty requiredPermissions list sets no gate, so "a resolved empty set gates" misread). Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…ord-block requiredPermissions describe gen:docs: the four requiredPermissions rows lose "; a resolved empty set gates like any other" and nothing else. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…round 2 Deletions only, per the seat's ruling on the two held questions: the RecordDetailsProps family header drops "hides the whole block and" (it now reads "presentation only: it authorises nothing."), and the new changeset drops "; a resolved empty set gates" so it ends at "(fails open)." like the describe. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
门禁评论
|
Contract reviewServed-tier: Reviewed and posted 2026-09-23T19:56Z by the at-tier review subagent the ① Derived judgmentsShared describe (
The five properties ruling
Pins: head 25/25. Instrument A with each block's base document: all three accept Census (head, git objects, Generated files: Every sentence that ships: describe — true (above). New changeset ② Semver levelBoth changesets ③ Boundary flags
Blocking: none. Implemented-by: VERDICT: PASS |
Maintainer confirmation — the DELIBERATE CORRECTION of
|
…cord-block-required-permissions
The os-regen driver kept the branch's side of ui.json in the merge of origin/main (6696056); main's thirteen DocNavItem keys were the dropped side. gen:schema on the merged tree restores them next to this branch's three record-block requiredPermissions keys, and nothing else. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
…s at objectui f8a9d0fb0596 origin/main moved `.objectui-sha` from 62597c588072 to f8a9d0fb0596, and the shared docblock of the block-level requiredPermissions gate asserts the pin it was read at. Every one of its 21 anchors was re-read at the new pin: the cited range holds the same text it held at 62597c588072 in all 21. record-details.tsx moved +1 (an import line) and record-related-list.tsx moved +34 (an import block and the props-type docblock), so nine anchors take new numbers: record-details 186/223/234/234-242 -> 187/224/235/235-243 and record-related-list 184/202/229/242/242-250 -> 218/236/263/276/276-284. record-highlights.tsx, record-quick-actions.tsx, MePermissionsProvider.tsx, PermissionProvider.tsx and usePermissions.ts are byte-identical across the hop, so their twelve anchors keep their numbers. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: 101/101 Isolated at-tier reviewer subagent, run by the Reviewed 2026-09-25T03:55Z by the contract-tier review subagent the ① Derived judgments(a) The merge is faithful.
(b) The re-measure holds at
(c) Changesets, test, mdx unchanged. Blob ids at (d) No stale pin left in the PR's own diff; the gate is green on the head. (e) The fix-round section of the PR body, sentence by sentence. True and measured: the dequeue cause (#20036 = ② Semver levelUnchanged by the round and still correct: both changesets ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…d:details / record:highlights / record:related_list and read it un-cast (objectui#8649) (objectstack-ai#11184) Fixes objectstack-ai#8649 Clause-②: yes — declaring `enforceFieldSecurity`, `redactFields` and `requiredPermissions` on the three record blocks' published props types and registry inputs widens the accepted authoring surface to exactly what `@objectstack/spec` 17.5.0 accepts on those blocks, and no further. This PR waits as a draft for the director seat's contract review. ## What this does The card's last open item: the nine reads of the field-security triple in `@object-ui/plugin-detail`, on `record:details`, `record:highlights` and `record:related_list`. PR objectui#9469 (`Part of`) took the other three reads and routed these nine to the platform as objectstack#18159. That card closed `completed` with PR objectstack-ai/objectstack#19913 (ruling A), and `main` now installs `@objectstack/spec` 17.5.0, so the routing has expired into "align the mirror": - **`@object-ui/types`**: `RecordDetailsComponentProps`, `RecordHighlightsComponentProps` and `RecordRelatedListComponentProps` each gain `enforceFieldSecurity?: boolean`, `redactFields?: string[]` and `requiredPermissions?: string[]`. - **Renderers**: the nine reads in `record-details.tsx`, `record-highlights.tsx` and `record-related-list.tsx` lose their `(schema as any)` cast. For example, `(schema as any).enforceFieldSecurity === true` becomes `schema.enforceFieldSecurity === true`. The `record-related-list.tsx` props docblock that said "Do not reopen it to admit a key the renderer reads through a cast" is rewritten, because the contract now declares the three keys on that block. - **Registry inputs** (`packages/plugin-detail/src/index.tsx`): each of the three blocks publishes the three keys. The types are the contract's (`boolean`, or `array` of `string`), and each description is that block's own `.describe()` text from the installed spec, verbatim. - **The console guard** (`registry-inputs-spec-parity.test.ts`): this card's nine `OWED TO objectui#8649` entries are struck. `OBJECTUI_11111_LEDGER_CAPS.unpublishedKeys` goes from 57 to 48, the owner-count pin `'objectui#8649'` goes from 9 to 0, and the six new array inputs get member pins. No other owner's entry was touched. - **Changeset**: `minor` on `@object-ui/types` and `@object-ui/plugin-detail`, stating the widening. ⛔ No runtime permission, gating or masking behaviour changes (triage floor `5619608221`). The proof is under "Honest types, same behaviour". ## Premises re-measured on `main` before any edit (Partition 2) **1. What 17.5.0 declares.** The installed `node_modules/@objectstack/spec/package.json` reads `17.5.0`. Two instruments were run over the contract's own block-tag map `ComponentPropsMap`, and each was self-tested on known schemas first: - A is a parse probe that reads `unrecognized_keys`. - B enumerates `.shape`. ``` enforceFieldSecurity A = [record:details, record:related_list, record:highlights] B = same agree redactFields A = [record:details, record:related_list, record:highlights] B = same agree requiredPermissions A = [..the three.., record:quick_actions] B = same agree aria / fields many blocks CONTROL (lit) zzqx_no_such_key none CONTROL (lit) ```⚠️ On the first pass the two instruments disagreed on one block, `user:profile`. Its props schema is `z.never()`, so it refuses every key without `unrecognized_keys`, and instrument A read that as "not refused by name" for every key, including the nonsense control. That block is now excluded from A's population and named in the output, and the two instruments agree on every key. Declared shapes, identical on the three blocks: `enforceFieldSecurity` is `z.boolean()`, `redactFields` is `z.array(z.string())` and `requiredPermissions` is `z.array(z.string())`. All three are optional with no default. The `enforceFieldSecurity` and `redactFields` describes are block-specific. The `requiredPermissions` describe is the one ruling `5798783314` shares word for word with `record:quick_actions`. ⇒ **Premise holds:** all nine keys are declared, on exactly those three blocks. **2. The read sites.** The checker (membership, via `getPropertyOfType` on each binding) and an expression probe (`getTypeAtLocation` on each read) were run over the three renderers. The probe refuses to report while any `TS2307` is present: its first run in this fresh worktree had 48 of them and was discarded, the dependency closure was built, and the probe was re-run. - Before: all 15 textual reads (nine sites, where `requiredPermissions` and `redactFields` are each read twice in their ternary) are cast, typed `any`, and are not members of the binding. - After: none is cast. Each read carries its declared type: `boolean | undefined`, `string[] | undefined`, or `string[]` inside the `Array.isArray` narrowing. - Expectation vs spec: `=== true` against `z.boolean()`, and `Array.isArray(...) ? ... : []` into a `string[]` against `z.array(z.string())`. Both match, so there is no behaviour question. **3. Honest types, same behaviour.** Before and after, each renderer was transpiled with `removeComments`: ``` record-details.tsx js IDENTICAL (sha256/16 336f03c50dbc3cc5 both sides, 9083 bytes) record-highlights.tsx js IDENTICAL (1936043763ee768a both sides, 2734 bytes) record-related-list.tsx js IDENTICAL (c42d56544b3a6130 both sides, 6397 bytes) CONTROL `=== true` -> `== true` on the enforceFieldSecurity read js DIFFERS (instrument fires) CONTROL a comment inserted at the same site js IDENTICAL (comments stripped) ``` The `@object-ui/types` change is interface-only. **4. Registry inputs.** The three blocks' `inputs` live in `packages/plugin-detail/src/index.tsx`. `recordDetailsInputs.spec-parity.test.ts` and its two siblings asserted only the forward half: no input that the spec does not accept. The reverse half lives in the console guard. **5. The ledger.** See "What this does". **6. objectui#10200's premise does not hold on 17.5.0.** That card was written against 17.4.0 and says `record:details` "honours three security keys the pinned spec REFUSES". On the installed 17.5.0: - `RecordDetailsProps` accepts all three keys, by both instruments above, with value-level parses in the new pins. - Its ruling item 1 (stop reading `requiredPermissions`) was withdrawn by ruling A on objectui#10281. - Its item 2 (the pin bump) landed as objectui#11086. - ⇒ Neither of its two premises stands. This PR does not work or edit that card; this is a reading for its seat. ## Tests New and updated pins: - `detailRendererUndeclaredKeys-8649.test.ts` is this card's own pin. It now carries: - `Equal` type pins for each block: the mirror members equal the contract's `ComponentPropsInput` members, spelled out, and each renderer binding carries them. - Source-text legs showing that each of the nine reads is still read un-cast, each with controls. - Value-level parses of the triple on each block. - A note on why the binding pins are split per block: in one nested tuple the three compared unequal under the identity trick, although each compares equal on its own. - `record-details.hideFieldsUncast-9965.test.ts` and `record-related-list.relationshipValueFieldUncast-9475.test.tsx` now have empty cast ledgers, as both files said this card's landing would do. - The 9965 program leg lost its only cast and `any` reads, which it used as calibration. It now measures a virtual control file in the same program, which pins that the checker reports a cast read as `any`, an un-cast read as `string[] | undefined`, and an undeclared read as `any`. - A new leg pins that the triple is still read, and read with the mirror's type. - `record-related-list.propsRefusal-9963.test.tsx`: the three refusal rows become acceptance rows, with wrong-type and misspelling refusals kept as `@ts-expect-error` controls. - The three `record*Inputs.spec-parity.test.ts` files check each key. The key is declared on the block. The published type is proved on values: an accepted value parses, and a rejected value is refused at that key (`redactFields: [1]` is refused at `redactFields.0`, the member). The description equals the installed describe. - `RecordRelatedListRenderer.columnMembers.test.tsx`: its CONTROL leg asserted the probe keys were unpublished. It now asserts that an input exists if and only if `RecordRelatedListProps` declares the key. Per triage carry `5627847529`, that file is still not cited as a declaration. - `record-highlights.fieldSecurity-8649.test.tsx` is **new**. No test drove `redactFields` or `enforceFieldSecurity` on `record:highlights`. It pins which chips paint their value, with a control for each row. **Behaviour unchanged, cited:** - The gating is pinned for all three blocks by `record-blocks.requiredPermissions-gate.test.tsx`, under a real `MePermissionsProvider`. - The field folds are pinned by: - `record-details.unresolvedIdentityFailClosed-9054.test.tsx` (details); - `RecordRelatedListRenderer.redactedDerivation-9053.test.tsx` and `RecordRelatedListRenderer.unresolvedIdentityFailClosed-8793.test.tsx` (related list); - the new highlights file. - All of them pass unchanged at this head. The only edits to the 9054 and 8793 files are docblocks whose "renderer-only key" sentence the declaration made false. **Runs at head `bf0385366`** (exit codes captured before any pipe): - `vitest run packages/types/`: exit 0, `Test Files 283 passed (283)`, `Tests 6496 passed (6496)`. - `vitest run packages/plugin-detail/`: exit 0, `Test Files 225 passed | 1 skipped (226)`, `Tests 2235 passed | 8 skipped (2243)`. The skipped file is `summaryChip.dateOnlyZone-10183.test.tsx`, which carries its own skip condition and is not touched here. - A union of the console and cross-package readers of these surfaces: exit 0, `Test Files 11 passed (11)`, `Tests 452 passed (452)`. The files are the eight `apps/console` tests that name the three blocks, `RecordDetailView.pageHeaderTitleFls-10499.test.tsx`, `public-tier.test.ts` and `check-handler-key-read-sites.test.ts`. - Type check, run after rebuilding `@object-ui/types`: `pnpm --filter @object-ui/types run type-check` (including `tsconfig.test.json`) exits 0, and `pnpm --filter @object-ui/plugin-detail run type-check` exits 0. - Lint: `@object-ui/types` exits 0 with 0 errors, and `@object-ui/plugin-detail` exits 0 with 0 errors. `eslint --format json` on the console guard file reports 1 file, 0 errors and 0 warnings. ## Ablations: direction predicted first, each mutation shown on disk, restored by blob-hash equality with `git diff HEAD` empty All three legs went through `ablation-replace.mjs` (a literal anchor that must hit, a trap-armed restore on an absolute path) while holding the verify lock. 1. **Re-cast one read.** `schema.enforceFieldSecurity === true` became `(schema as any).enforceFieldSecurity === true` in `record-highlights.tsx`. - Prediction: exactly the highlights `enforceFieldSecurity` un-cast leg goes red, and the emitted JS does not move. - Observed: anchor 1 to 0, blob moved. `Tests 1 failed | 26 passed (27)`, and the failing case was that leg. `record-highlights.tsx js IDENTICAL` under the mutation, so the source-text pin is the only instrument that can see a re-cast. -⚠️ The first attempt never ran: the lock timed out (exit 99) and the mutation was restored untouched. It was re-run with the mutation inside the held lock. - **1b, the negative leg on its own.** Only the second of the two `requiredPermissions` reads in `record-related-list.tsx` was re-cast (`? schema.requiredPermissions` became `? (schema as any).requiredPermissions`), so the liveness half still holds. - Prediction: the negative leg of that case fires on its own, and the objectui#9475 guard, now without carve-outs, names the key. - Observed: `Tests 2 failed | 35 passed (37)`. The first failure was "expected ... not to match" the cast matcher; the second was `expected [ 'requiredPermissions' ] to deeply equal []`. 2. **Undeclare one key.** `enforceFieldSecurity` was removed from `RecordDetailsComponentProps`, then `@object-ui/types` was rebuilt, because `plugin-detail`'s `tsc` resolves it through `dist/`. - Prediction: the type pins go red in this card's test file only, `record-details.tsx` still compiles through its index signature, and the 9965 program guard goes red on the membership leg and the triple-type leg. - Observed: - The dist proof: declarations in `dist/record-components.d.ts` went from 3 to 2. - `tsc -p tsconfig.test.json` exited 2 with six errors, all in `detailRendererUndeclaredKeys-8649.test.ts`: three TS2344 (the details mirror pin, the every-block shape pin and the details binding pin), the TS2339 pair that accompanies the first two, and TS2353 on the details fixture. There were zero errors in the renderer. - The 9965 guard showed `Tests 2 failed | 8 passed (10)` on exactly the two predicted legs. - The direction held. The count was higher than predicted (6 errors, not 4) because of the TS2339 companions. - Restore: blob equals HEAD, then a rebuild brought the dist count back to 3. -⚠️ The first attempt was refused by the tool before running: its replacement was a substring of the anchor, so its count could not rise. It was redone with a marker replacement. 3. **Unpublish one input.** The `record:highlights` `redactFields` input was replaced by a comment. - Prediction: two highlights parity legs go red, and the console guard reds on the reverse direction and on the member-pin population. - Observed: `Tests 4 failed | 239 passed (243)`. The four were those two parity legs, `record:highlights publishes every top-level key its spec props schema declares`, and `every member pin names a key that is still array/object-armed on a covered block`. ## Gates run locally (verdict lines read from each gate) Exit 0: - Changeset gates: `check-changeset-presence` ("1 changeset(s) added"), `check-changeset-no-major`, `check-changeset-fixed`, `check-changeset-overwrite` ("0 modified"), `check:changeset-claims` (report-only, see the notes below), `check:pending-changeset-literals`. - Text gates: `check:control-bytes`, `check:new-line-citations` ("0 new citation(s)"), `check:spec-symbols`, `check:handler-key-reads`, `check:installed-pin-claims`. - Governance: `check-governed-queue-guard --test` over the diff reads "NOT GOVERNED", and `--self-test` passes. - Coverage: `check-type-check-coverage`, `check-lint-coverage`. - Test hygiene: `check:test-path-roots`, `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape`. - Package gates: `check:element-data-source-declaration`, `check:unreferenced-sources`, `check:phantom-deps`, `check:self-import`. **NOT MEASURED:** - `check:sdui-registration-pins`: its prerequisite was not met (exit 2, "No console build to weigh"). This diff moves no registration array or `sideEffects` entry. - `check:spec-floors -- --cross-check` and `check:published-dist`: both need a full-repo build, so they are left to CI. - The repo-wide `pnpm lint` and the full `pnpm test` farm are CI's runs. ## Acceptance notes - **objectui#10224** covers this card's ground for two of its asks: `requiredPermissions` inputs on the three record blocks, and the `record-related-list.tsx` docblock in carrier note `5826463187`. Both are done here. Its `record:quick_actions` description ask is not in this PR's scope, so objectui#10224 remains open for that ask. The seat can narrow it. - **A pending note goes false:** the last paragraph of `.changeset/8649-detail-renderer-undeclared-keys.md`, from this card's first half, says the three keys are deliberately NOT declared. That was true against 17.4.0. The claim's file surface names one changeset, so that body is not edited here; this PR's changeset states the supersession instead. A prose-only correction of that body is a one-paragraph change once the surface allows it. It is raised as an open question in the report. - **objectui#11168** edits the same guard file in parallel. The shared lines are the `unpublishedKeys` cap and the owner-count pin, and whichever PR lands second merges `main` and re-derives them. The `57` in the bookings comment is now worded as "57 at the bump" and points at the cap constant, so it no longer needs re-deriving. - **Not touched:** objectui#9475, `record:quick_actions`, `record-reference-rail.tsx`. - The three renderer docblocks above the `requiredPermissions` gate say an unheld capability "hides the whole block". The renderer draws an insufficient-permissions notice, which is what the spec describe says. This is an observation only, not changed here. carrier: whoever takes objectui#10224's `record:quick_actions` description ask, which has the same wording drift one block over. - **README:** the package README and the docs guide are not updated. They are outside the claim's file surface. The published input descriptions carry the contract text. ## Round 2: text only, head `2b17f990b` This round follows the seat's ACCEPT `5907807607`. It adds two commits on top of `bf0385366`, with no rebase, no force-push and no rewrite of a pushed commit. - **`731b1de1d`, docs(plugin-detail).** The docblocks above the `requiredPermissions` capability gate in `record-details.tsx`, `record-highlights.tsx` and `record-related-list.tsx` said an unheld capability "hides the whole block / strip / section". Each now says the content is withheld and an insufficient-permissions notice (`role="status"`) renders in its place. That is what the renderers do, and it is what the contract's `requiredPermissions` describe on these blocks says: "this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place". The related-list docblock also says that the automatic child-object read gate above it is a different gate, and that one does hide the section (it returns `null`). Comment text only. - **`2b17f990b`, chore(changeset).** One dated correction note is appended to the end of each of two pending changesets, under the standing rule 「Allow the appended note (Recommended)」: - `.changeset/8649-detail-renderer-undeclared-keys.md`: the paragraph that begins "Three keys are deliberately NOT declared", and the census under it, are superseded, because this PR declares the triple on the three blocks. - `.changeset/10155-record-blocks-capability-gate.md`: "hides the block" is corrected to the notice behaviour. The capability set and the fail-closed verdict stand. The note names the related list's child-object read gate as the one that does hide. **Proof that the round is text only:** - **Append-only.** `git diff --numstat origin/main -- FILE` reads `11 0` for each changeset. The first 4927 bytes (8649) and the first 2269 bytes (10155) of the new files are byte-identical to the blobs on `main` (`cmp` exit 0), so no existing line and no frontmatter moved. `check-changeset-overwrite` lists both as modified, with the same declarations at base and now. - **Emitted JS**, measured with round 1's instrument (transpile with `removeComments`, `bf0385366` against `2b17f990b`): ``` record-details.tsx source DIFFERS js IDENTICAL (336f03c50dbc3cc5 both sides, 9083 bytes) record-highlights.tsx source DIFFERS js IDENTICAL (1936043763ee768a both sides, 2734 bytes) record-related-list.tsx source DIFFERS js IDENTICAL (c42d56544b3a6130 both sides, 6397 bytes) CONTROL `=== true` -> `== true` on the enforceFieldSecurity read js DIFFERS on all three (instrument fires) CONTROL a comment inserted at the same site js IDENTICAL on all three (comments stripped) ``` The three JS hashes are the same ones round 1 measured after its change. **Runs at head `2b17f990b`** (exit codes captured before any pipe): - `vitest run packages/plugin-detail/`: exit 0, `Test Files 225 passed | 1 skipped (226)`, `Tests 2235 passed | 8 skipped (2243)`. - `pnpm --filter @object-ui/plugin-detail run type-check`, after building the package's dependency closure: exit 0. - `pnpm --filter @object-ui/plugin-detail run lint`: exit 0 with 0 errors. The warning count on each of the three renderers equals round 1's. - Named gates, each exit 0: - `check-changeset-presence` ("1 changeset(s) added"), `check-changeset-no-major`, `check-changeset-fixed`. - `check-changeset-overwrite` (report-only): "1 changeset(s) added, 2 modified, 0 deleted". - `check:changeset-claims` (report-only): 6 pending changesets name a file this PR touches, against 7 in round 1. The one that left the list is the 8649 changeset. It left because this PR now modifies it, and the gate's went-false reading skips the changesets a change adds or modifies. That is not a verdict on its prose. - `check:pending-changeset-literals`, `check:control-bytes`, and `check:new-line-citations` ("0 new citation(s)"). - Also run, because the diff touches markdown: `check-shell-escape-residue`, `check-doc-links`, and `check-governed-queue-guard --test` over the five paths ("NOT GOVERNED"). Each exits 0. **Round 2 acceptance notes:** - In the three docblocks, a pre-existing line join remains: the paragraph that ends "skipped its declared gate entirely." (details, highlights) or "the wrong question either way." (related list) runs into the next line's ` *`. It is left as is, because it is comment formatting outside this round's wording ask. - Test names in `record-blocks.requiredPermissions-gate.test.tsx` still say the gate "hides the WHOLE block" or "hides the block". Their assertions pin the notice: the refusal text is found and the block body is absent. They are left as is, because this round covers renderer comments and changesets only. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18159
Clause-②: yes
Rewritten short by the
domain:spec#5seat (2026-09-23T19:59Z). The dev reports are on #18159 (5798750532,5800008407,5800978880,5801656924); the earlier long body is in the edit history.requiredPermissionsis now declared onrecord:details,record:highlightsandrecord:related_list, with the shaperecord:quick_actionsalready has (z.array(z.string()).optional()). Ruling: batch #197 item 2, letter A (5749268463), after objectui#10058 became installable at.objectui-sha62597c588072.One describe, four blocks (seat ruling
5798783314)The ruling asks for the same describe as
record:quick_actions. That published describe said "…every named permission on this object", which is false for the renderer at the pin: the check ishasCapabilities, and the renderer's own comment says the capability "is not object-scoped". So all four blocks now share ONE describe, from one constant (RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION). The publishedrecord:quick_actionsdescribe changes; its shape does not. The describe states:The renderer lines behind each clause are in the dev report
5800008407.Other changes
RecordDetailsPropsfamily header, the related-list and highlights pointers, and the test header..shapeenumeration per block, withariaandfieldsas lit controls, plus a pin that the four JSON Schemas of the key are identical. Removing the three declarations turns 11 of 25 pins red.authorable-surface/ui.jsonandcomponent.mdxare regenerated.Check Changesetis red on purposeThe pending note
.changeset/18159-record-block-field-security-pair.md(from PR #19185) said the third key "is deliberately NOT declared". This PR makes that false, so the PR corrects it. That is a DELIBERATE CORRECTION undercheck-empty-changeset.mjs: the check stays red, and the correction awaits the maintainer's written confirmation on this PR. The line to confirm is quoted verbatim in5801679846.Check Changesetis not a required context.Fix round 2026-09-25:
origin/mainmerged, objectui pin re-measuredFix round for
domain:spec#4(sessionsession_019c3Hi6ZMU1p6m6aA6Bz45d), new headb19ac16c77. The merge queue dequeued the PR.Type Check · source gatesfailed atcheck:objectui-pin-citations: #20036 (0bf85eaae6) moved.objectui-shafrom62597c588072tof8a9d0fb0596, and the sharedrequiredPermissionsdocblock incomponent.zod.tsstill asserted the old pin. This round adds three commits and changes nothing else:62decd77ddmergesorigin/mainat66960564d9withscripts/pm/os-regen-merge.sh(merge commit; no rebase or force-push). It had no text conflict.a3075b6aceregeneratespackages/spec/authorable-surface/ui.jsonon the merged tree. In that file the merge driver kept this branch's side and dropped main's 13DocNavItemkeys. The regenerated file has main's keys plus this PR's threerequiredPermissionskeys, and nothing else.b19ac16c77re-measures the docblock at the new pin. It updates the sha and the anchors together (10 lines replaced, 0 added).What was measured
objectui at
f8a9d0fb0596f4521076628e2bbfe27e6ce67d52was read from a scratch fetch of both pins. The seven cited files match the codeload tarball of the same commit byte for byte. All 21 anchors in the docblock were re-read. At the new pin, each cited range holds the same text it held at62597c588072. No read point changed meaning or disappeared.record-details.tsxshifted +1 (one new import line).record-related-list.tsxshifted +34 (an import block and the props-type docblock). Nine anchors get new numbers.record-highlights.tsx,record-quick-actions.tsx,MePermissionsProvider.tsx,PermissionProvider.tsxandusePermissions.tsdid not change between the two pins, so their twelve anchors keep their numbers.f8a9d0fb0596record-details.tsx:234→:235if (required.length > 0 && !perms.hasCapabilities(required)) {record-details.tsx:223→:224* capability is not object-scoped, and the old && objectName conjunct wasrecord-details.tsx:234-242→:235-243if (required.length > 0 && !perms.hasCapabilities(required)) {record-details.tsx:186→:187if (!ctx) {record-related-list.tsx:242→:276if (required.length > 0 && !perms.hasCapabilities(required)) {record-related-list.tsx:229→:263* capability is not object-scoped. This site never carried therecord-related-list.tsx:242-250→:276-284if (required.length > 0 && !perms.hasCapabilities(required)) {record-related-list.tsx:184→:218if (!objectName) {record-related-list.tsx:202→:236if (perms.isLoaded && !perms.can(objectName, 'read')) {record-highlights.tsx:93(unchanged)const highlightsAllowed = required.length === 0 || perms.hasCapabilities(required);record-highlights.tsx:77(unchanged)* capability is not object-scoped, and the old && objectName conjunct wasrecord-highlights.tsx:151-164(unchanged)if (!highlightsAllowed) {record-highlights.tsx:146-149(unchanged)useRegisterHighlightFields(record-quick-actions.tsx:263(unchanged)if (required.length > 0 && !perms.hasCapabilities(required)) {record-quick-actions.tsx:252(unchanged)* capability is not object-scoped, and the old && objectName guard was arecord-quick-actions.tsx:263-271(unchanged)if (required.length > 0 && !perms.hasCapabilities(required)) {MePermissionsProvider.tsx:416, and the later:416(unchanged)return required.every((p) => held.has(p));MePermissionsProvider.tsx:414(unchanged)if (!Array.isArray(perms)) return true;PermissionProvider.tsx:77(unchanged)const ALL_CAPABILITIES: PermissionContextValue['hasCapabilities'] = () => true;usePermissions.ts:45(unchanged)hasCapabilities: () => true,(The inner backticks around
&& objectNamein the three docblock lines are left out of the table.)One observation from the re-read, which changes no anchor. The props-type docblock that
record-related-list.tsxgained (:89-92) says no block the contract maps onto this tag declaresrequiredPermissions, and says not to reopen the type to admit it. This PR makes that sentence stale. That is the objectui half ruled A on objectstack-ai/objectui#10281, and it moves to thedomain:uiseat. The renderer's read of the key (:240-242, through a cast) and its gate (:276) are unchanged.Gates, on head
b19ac16c77check:objectui-pin-citationsexits 0 on--self-test, on the ordinary run and on--verify-anchors, with objectui at the new pin supplied throughOBJECTUI_ROOT. The ordinary run prints: "48 asserting objectui pin citation(s) match .objectui-sha (f8a9d0fb0), 40 historical citation(s) recorded and not checked, across 1555 spec source(s). 7 anchor content assertion(s) verified against objectui at f8a9d0fb0". CI has no objectui checkout, so it verifies the sha label only.pnpm --filter @objectstack/spec build, thencheck:generated: all 15 generated artifacts are up to date.typecheckexits 0. The full spec suite: 535 files, 15727 passed, 2 todo.node scripts/pm/dispatch-gates.mjs --commandsderived 107 commands for this change set. All 107 ran, and--ranreconciles 107 of 107 with recorded exit codes and 0 NOT MEASURED. Seven of them first exited 3 (no build to read). They were re-run green after building the lint closure, the client-react closure and then every package. 106 exit 0. The one exit 1 ischeck-empty-changeset.mjs. That is the deliberate correction described above, which the maintainer confirmed at headaeb6a57456(5823706830).git diff --exit-code aeb6a57456 b19ac16c77 -- .changeset/18159-record-block-field-security-pair.md .changeset/18159-record-block-required-permissions.mdexits 0. The blobs ared502a9a04c6aand80546d851168on both heads. The test file andcomponent.mdxare byte-identical to that head as well.🤖 Generated with Claude Code
https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1