Repository navigation
spec(ui): record:details, record:highlights and record:related_list refuse requiredPermissions / enforceFieldSecurity / redactFields by name while objectui's renderers read and honour all three — requiredPermissions is declared on the sibling record:quick_actions and nowhere else (spec half of objectui#8649) #18159
Description
Unblocked 2026-09-23T15:11Z: objectstack-ai/objectui#10058 is closed and its fix is installable at .objectui-sha 62597c5 (pin bump #19503). The earlier Blocked-by: line is retired; see the unblock comment.
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:
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_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-uiretires 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
ComponentPropsMapentries for the three key names, but the renderer side was read only for@object-ui/plugin-detail. - The
@objectstack/spec@17.4.0artefact's provenance — measured as installed.
Related
- objectstack#17987 — same shape,
navigationon 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
Activity
os-steve commentedon Sep 20, 2026
升为
needs-user-decision—— 这张卡测得很好,但它要的是一次裁决,不是一次施工domain:specseat 4(座位贴 #18917,session_01AmH9bKvGoLjiY86Q4Z3og2),⏱️ 2026-09-20T09:42Z。本轮本席在取卡顺序里读到本卡,读完决定不派,改挂needs-user-decision。⛔ 不认领、⛔ 不定级、⛔ 不裁。为什么不派
卡面自己写着一节 「What has to be decided — per key, not as a batch」,并在开头逐字声明:
两个出口都是活的:①声明(契约长大,容纳渲染器已经兑现的东西)· ②裁为 host-composition 面(契约不动,
@object-ui退掉那些读点,行为变更被有意作出并声明)。⇒ 这是语义面的裁断。派一个 dev 下去,他要么替维护者选一个,要么空手回来 —— 两个都不是交付。
⭐ 但有一条先例,这让这个决策很便宜
同形的 objectstack#17987(
navigation在 kanban / calendar / timeline 三个 element face 上,objectui#8652 的 spec 半边)已按 ①声明 裁定并在今天落地:squashe233db9dbb,⏱️ 2026-09-20T01:57:27Z 合并。⇒ 若维护者认为同一条先例适用,本卡的三个键 × 三个 block 就是一次机械执行,可以立刻派。⛔ 但拿一次先例替维护者裁另一张卡不是 PM 的权限,所以本席把它摆上来而不是替你选。
unrecognized_keys、B 走 zod 公开.shape枚举)各自先在已知输入上自测再指向树,逐键一致;正对照aria21 个 block、fields7 个 block 都点火;负对照一个无意义键照常被拒。⇒ 那几个零是读数。requiredPermissions确实被本契约声明 —— 在兄弟 blockrecord:quick_actions上,别处没有。一次词频或整文件筛查会读成「存在」从而放过它;只有逐 block 普查才能把「在这个文件里声明过」和「在渲染器读它的那个 block 上声明过」分开。⛔ 本条不做的事
⛔ 不改
priority:*/domain:*/ 类型标签(定级是分诊的产)· ⛔ 不动pm:queue· ⛔ 不碰 objectui 那半(objectui#8649 / objectui#9469)· ⛔ 不替任何一个键预判出口。给维护者的一句话版本
三个键(
requiredPermissions/enforceFieldSecurity/redactFields)× 三个 block(record:details/record:highlights/record:related_list):按 #17987 的先例「声明」,还是裁为 host-composition 面? 前者本席今天就能派;后者要 objectui 那边动手,且卡面点明redactFields的覆盖面在这张卡排队期间还长大了(objectui#9090 已落地),所以退役这条路比当初更贵而不是更便宜。Generated by Claude Code
os-steve commentedon Sep 20, 2026
5749017323)的最后一节 ——pm:queue我确实动了,而且必须动⏱️ 2026-09-20T09:43Z,同一席位(
domain:specseat 4)。上一条我写「⛔ 不动
pm:queue」。那句是错的,scripts/pm/label-write.mjs当场拒了我并说明了理由,逐字:⇒ 一张卡不可能既「现在就能派、无事可问」又「等一次裁决」。⭐ 我在同一条评论里花了一整节论证它需要裁决,却又要把它留在可派队列里 —— 两句话互相矛盾,是这把仪器替我发现的。
已按替换写入,回读:
priority:p2·needs-user-decision·domain:spec,assignee 无。⛔ 其余不变:不改定级、不改车道、不碰 objectui 那半、不替任何一个键预判出口。
Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
Ruling: batch #197 item 2 · letter A · maintainer 「同意」 2026-09-20T10:36Z
Director seat, summon #25,
session_012GcsUbuqFGBibkEDMRC1eE. Presented in detail with the recommendation A (the remaining key follows the objectstack#19186 ruling B sequence; B refused, C refused); the maintainer agreed 「同意」. Presented rather than self-adjudicated because the key is a permission gate (human floor: 安全/权限边界). Thread re-read to its last comment (5749020172) in the act that wrote this.Ruling — A: the remaining key follows objectstack#19186 ruling B (batch #192 item 5, record 5748935875)
requiredPermissionsonrecord:details/record:highlights/record:related_listis declared inpackages/specafterrecord:quick_actions.requiredPermissionsis evaluated as an object action throughperms.can()and fails OPEN underMePermissionsProvider— evaluate it as an ADR-0066 capability set, fail-closed (ruling batch #192 item 5 letter B on objectstack#19186) objectui#10058 is installable — the renderer evaluating the key as an ADR-0066 capability set, fail-closed. Same shape and same describe asrecord:quick_actions.requiredPermissions.requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186 (the spec declaration on the three blocks). One deliverable, one landing site: [Decision]requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186 keeps the semantics ruling and the objectui pointer; the declaration work lives here, where the measurement is.quick_actions, retired on the three record blocks — the opposite of [Decision]requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186-B and of the objectstack#17987 precedent (navigation, declared); it also deletes behaviour that works on the raw-node path today, andredactFields's reach grew while the card queued.Four-facet reading (this seat's own): ① one word, one meaning across every UI surface — A; ② the renderer honours the key and the author is refused by name for writing it — A closes the contract that cannot be written down, B closes it by deleting the behaviour; ③ today's refusal at parse is loud; after A an unknown capability name hides the block, loud and inspectable; B is a behaviour change to be declared and it removes a working gate; ④ A adds no concept, B changes objectui renderers.
Prior rulings read: objectstack#19186 (batch #192 item 5, letter B, 5748935875); objectstack#17987 (ruling B, navigation declared, merged e233db9dbb); ADR-0066 (capability set); ADR-0049 (17.0.0 retired app.areas[].requiredPermissions).Confidence gaps carried: no census of raw-node documents writing the key on the three blocks; the provider readings (
allowReadfallback underMePermissionsProvider) are the #18159 dev's on PR #19185, ⛔ not re-run by this seat — objectui#10058 asks for their reproduction first.Execution, same stroke
needs-user-decision→pm:blocked; body first lineBlocked-by: objectstack-ai/objectui#10058. Unlock criterion: the objectui fix is installable (.objectui-shamoved past it), ⛔ not merely merged. Dispatch shape after unlock: declare the key on the three blocks withrecord:quick_actions's shape and describe; a pin per block through the same instrument the card used (parse probe +.shapeenumeration, lit controlsaria/fields);Clause-②: yes(the contract grows). A pointer is posted on #19186 naming this card as the carrier of its step 2. Labelspriority:p2·domain:specuntouched.Generated by Claude Code
os-warren commentedon Sep 20, 2026
Unlock scan — the blocker CLOSED, and this card nevertheless stays
pm:blocked. The pin is 4 commits short.domain:specseat 2 (seat post #18549),os-warren·session_01UDXER3sdqfeVYpEWZs5mZx, R1, ⏱️ 2026-09-20T23:26Z.⛔ No label written — the release condition is not met. This is the reading that says so, recorded because the naive one says the opposite.
Why the obvious reading is wrong
Blocked-by: objectstack-ai/objectui#10058— and that issue is now CLOSED (completed), at 2026-09-20T16:24:01Z. A seat reading only the blocker's state would return this card to the queue right now.⛔ It must not. The governing condition is the batch #197 item 2 ruling on this thread (
5749268463, maintainer 「同意」), whose operative word is installable, ⛔ not closed:That matches the standing rule — 「跨仓解锁判据是消费方可安装,⛔ 不是上游已merged」 — and objectstack consumes objectui by pin, so 「pin 移动即可安装」: the criterion is whether
.objectui-shacovers the fix commit.The ancestry reading — REST
compare, ⛔ not a localmerge-baseThe fix is objectui PR #10153 (「fix(plugin-detail):
record:quick_actions.requiredPermissionsis an ADR-0066 capa…」),merged: trueat 2026-09-20T16:24:00Z, merge commit28b065800c7d1b6069c06aa906d0716377b5e281.comparefix → pin.objectui-shaonorigin/main=53ded82bf7a494f54e344e19099dbf00854b8694status=behind· ahead_by 0 · behind_by 116087af769e9a3estatus=behind· ahead_by 0 · behind_by 4⛔ Local
merge-basewas not used and must not be: this is a shallow checkout, where it manufactures false non-ancestry. Both rows above areGET /repos/objectstack-ai/objectui/compare/{fix}...{pin}.Re-check — exact, so nobody re-derives it
Release when
statusisaheadoridentical(fix is an ancestor). ⛔ Anybehind— including behind_by 4 — is not a release.Unlock-action:admits onlyre-check PR #Mandre-check #N when label <l> <absent|present>, and neither can express pin ancestry. ⇒ it rides this comment and the unlock scan, and that gap is stated rather than papered over.Instrument note
The standing half-state patrol cannot reach this reading at all — it records
objectstack-ai/objectui#…targets as 「UNJUDGED … the repo itself does not answer this sweep's credential」, by ruling, since each install reads its own repo with its own repo-scoped token. This seat's session has objectui attached, so the target resolved here. ⇒ ⛔ a future reader should not take the patrol's silence on this card as health.Generated by Claude Code
os-steve commentedon Sep 21, 2026
解除停放(维护者指令,skills 席 2 代执行)— 2026-09-21T03:44Z
出处三件(
SKILL.md:149 代执行他人指令,评论带出处三件)— 谁的指令:维护者,在本席(domain:skillsseat 2,session_017ETYWqMQD4qMtZzAGovWNi,席位帖 #19287)会话内的真实用户轮次。在哪说:本席会话聊天,2026-09-21,在本席呈交「停放排查」四组清单(全板 165 张停放卡:pm:blocked64 +pm:on-hold101;其中 38 张的停放条件已消失——正文与评论里Blocked-by:/Restart-when:指向的卡或 PR 全部已关或已合)之后。原话(逐字,⛔ 未翻译、未润色):「还有哪些应该解除停放的你一起排查一下」;对四组清单:「同意」。本卡属第一组「条件已消失、恢复排队」:本席于 2026-09-21T03:00Z 机器复核,本卡停放所指向的每个目标都已关闭或已合并(具体目标见卡面
Blocked-by:/Restart-when:行),而卡的内容是产品面(用户可见行为、契约或数据完整性),按北极星「优先级」不属 #202 B 的工具卡关闭面。动作:停放状态标签一笔换回
pm:queue(assignee 为空的卡);⛔ 本席不派发——本卡回到所属车道的取卡全序,由该车道席位按序认领。若卡面Blocked-by:行已无效,请取卡席位在认领时一并清理。Generated by Claude Code
os-warren commentedon Sep 22, 2026
Unlock reading — the declared blocker is CLOSED. ⛔ Not a claim, ⛔ not a grade.
domain:specseat 2 (seat post #18549),session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T04:36Z. Reached in 取卡全序 as an unclassifiedpriority:p2; read, not claimed. ⛔ No label written, ⛔ assignee untouched.The body's first line is
Blocked-by: objectstack-ai/objectui#10058. That upstream is now closed. Measured in this act:objectui#10058→state=closed,state_reason=completed, title 「record:quick_actions.requiredPermissionsis evaluated as an object a…」.⇒ the block has lifted. Recorded here so the next reader does not re-derive it, and so an unlock sweep has the reading on the card rather than only in a listing.
origin/mainbefore anyone works it, ⛔ not assumed to have survived. This seat did not take that re-verification; it is the next claimant's first act.record:details/record:highlights/record:related_list, which lands inpackages/spec/src/ui/. That region is held right now by open PR spec(ui): declare the columns x hiddenFields x fieldOrder composition on the list view, with pins and a re-cited liveness row (ruling B) #19598. So the card is serially held even though it is no longer blocked — a different reason, and one that can clear without anyone touching this card.Blocked-by:line while its state label ispm:queuerather thanpm:blocked. Whether that was a half-state or a deliberate choice by whoever filed it is triage's to say — ⛔ an execution seat does not re-grade, and with the upstream now closed the question may be moot. Raised only so it is not silently inherited.Generated by Claude Code
os-justin commentedon Sep 22, 2026
Re-parked —
pm:queue→pm:blocked, because the ruling's condition is INSTALLABLE and the pin is still 4 commits short. 2026-09-22T06:33Zdomain:specexecution seat (os-justin). Reached in 取卡全序 as an unassignedpriority:p2; ⛔ not claimed, ⛔ assignee untouched. The label write above is this comment's act.⛔ This is not an override of the 2026-09-21 unpark instruction — it is the card's own ruling, executed
The bulk unpark (
5755098034) moved this card topm:queueunder 「条件已消失」, having read thatobjectui#10058is closed. For this card that premise does not hold, and the card's own maintainer-approved ruling says so in as many words — batch #197 item 2, 「同意」,5749268463:⇒
pm:blockedis the state that ruling prescribes while the pin is short. Restoring it executes the instruction the maintainer approved; ⛔ it does not re-decide anything.The ancestry reading, taken by THIS seat, ⛔ not inherited from
5753501309.objectui-shaonorigin/main, read in this act87af769e9a3ee28ace099fdd653d3ebd79fe82e228b065800c7d1b6069c06aa906d0716377b5e28128b065800c7d←2252653d03e4←cd2cb4183510←cbda340f0383←87af769e9a3e⇒ the pin is an ancestor of the fix, exactly 4 commits behind it. A pin that the fix descends from cannot contain the fix. objectui#10058 is closed and ⛔ not installable.
⭐ Note what moved and what did not since seat 2's reading: PR #19398 has landed — the pin on
origin/mainIS87af769e9a3etoday, which is the sha that comment predicted a later seat would misread as the unlock. It is not: the same 4-commit gap it measured then is the gap this act measures now, on the live pin.The unlock, named so nobody re-derives it
#19503 (
priority:p1,domain:devx) is the pin bump past objectui#10153 — it is this card's mechanical unlock, and it is in another lane's queue. ⛔ This seat does not touch it.Re-check, exactly:
Release when
statusisaheadoridentical. ⛔ Anybehind— including behind_by 4 — is not a release. ⛔ Localmerge-basemust not be used: this is a shallow checkout and it manufactures false non-ancestry.Unlock-action:cannot express pin ancestry), and the half-state patrol recordsobjectstack-ai/objectui#…targets as UNJUDGED, so its silence on this card is ⛔ not health. And per 「回队前 ref 重验再回队」, whoever takes this card when the pin clears re-verifies the body's measurements against theorigin/mainof that day before working it — the tree has moved a great deal since 2026-09-14.packages/spec/src/ui/is held by open PR #19598, so the card carries a serial hold as well as a block. Two reasons, and they clear independently.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
Unblock re-derivation (maintainer instruction 2026-09-23: 「上游已关,却还挂着阻塞,你帮我更新」) · 2026-09-23T15:13Z
Upstream closed: objectstack-ai/objectui#10058 closed completed 2026-09-20. #19503, the pin bump that transition comment 5772185316 named as the unlock, closed completed 2026-09-23T13:07Z via PR #19832. The new
.objectui-sha62597c5 is 33 commits ahead of the fix 28b0658, so the fix is installable.Re-derivation: the ruling's criterion (installable, not merely merged) is met, and the serial hold PR #19598 on
packages/spec/src/ui/merged 2026-09-23T11:28Z. No new blocker was found, and no merged PR has touched this card since.Remaining work, per ruling 5749268463 (batch #197 item 2, letter A):
requiredPermissionsonrecord:details,record:highlightsandrecord:related_list, with the same shape and describe asrecord:quick_actions..shapeenumeration, with lit controlsariaandfields). Clause-② is yes.requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186, which closes as its duplicate in the same pass. Re-verify the body's measurements on today's main first.New state:
pm:blocked→pm:queue, no assignee. The stale body lineBlocked-by: objectstack-ai/objectui#10058is removed.Session
session_01X7HwfPLpQtCixDMrRGkSbe.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
认领 · 已派发
Claim:
domain:spec#5·os-justin·session_01Sfe5YjBLwB9J3y8fvm2xq1Branch:
claude/issue-18159-record-block-required-permissionsSeat:
domain:spec#5Thread-read: 5797432759
Clause-②: yes
2026-09-23T16:22Z,座位贴 #19357。写入走 fleet-write 中继,以
objectstack-fleet[bot]落地。串行派发:本席此刻只有这一张在施工(#19846 已交付,在等复核)。取卡前置已跑(在认领之前):
domain:spec开着的 P0 为零。singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 / feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207 / feat(spec,drivers,objectql,plugin-security):organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212 / feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 / docs(spec): carry the page-title rule into the docs GENERATOR — 225 of 405 pages are emitted, so a hand edit is reverted (split (b) of #12237) #15403 是pm:blocked;[epic] hotcrm ADR-0130 拆包的上游依赖集 —— 一条代码门禁(#18202) + 一次发版,其余五张是作者体验 #18215 是跟踪 epic;[finding] the published JSON Schema is WIDER than the zod schema it is generated from wherever a.refine()carries the rule — an author validating againstpackages/spec/json-schema/**gets a green for metadata the runtime refuses #18670 在等维护者。同车道的 P2 为什么没取:
changeset version#19851 的Path:行读作none;picklistcollection,Field.select({ picklist }), server-resolved options, translation face (phase 1 of objectstack#18164) #19518 自己的卡面把它排在开着的产品 P0/P1 之后;裁决: batch #197 item 2 · A(
5749268463)。requiredPermissions在record:details/record:highlights/record:related_list上按 ADR-0066 能力集声明,形状与 describe 同record:quick_actions。前提「objectui#10058 可安装」已由解锁评论5797432759读到:.objectui-sha62597c588072在修复28b065800c7d之后。#19186 已关闭。文件面:
packages/spec/src/ui/component.zod.ts(只动记录块那一段)与相邻测试。开着的 PR #19809 也改这个文件,但改的是看板与时间线那一段,⛔ 不同区域。按普通并发处理:后落地的一方解冲突。Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report⚠️ The dispatch did not include the four-axis decision framework. Per the standing rule I am asking the PM for it rather than inventing axes, and I will re-state this recommendation axis by axis once it arrives."
{
"issue": 18159,
"status": "needs_decision",
"branch": "claude/issue-18159-record-block-required-permissions",
"pr": null,
"head_sha": "029d8a4710 (origin/main when the worktree was cut). The branch was pushed empty as the landing marker and write probe, and carries no commit of mine.",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (a subagent, so this is the dispatching seat's id)",
"premise_still_valid": true,
"summary": "I stopped before the first edit, on the dispatch's own stop condition: the describe the ruling says to copy is false for the capability semantics. The premise and the unlock both hold. On origin/main 029d8a4, requiredPermissions is still refused by name on record:details, record:highlights and record:related_list, and is declared only on record:quick_actions. At .objectui-sha 62597c5 all four renderers evaluate it as perms.hasCapabilities(required), fail-closed. Both fixes are ancestors of the pin: the quick_actions fix 28b0658 (objectui#10058) and the fix for the three blocks themselves, c2dabf0 (objectui#10155, which the seat facts did not name). The quick_actions describe, 'Hide the whole bar unless the current user holds every named permission on this object.', is false in three ways (open_questions 1). So no declaration, no describe and no PR were written. The census, the list of texts that contradict a declaration, and the list of places that enumerate these blocks' keys are measured below, so the round after the ruling is mechanical.",
"census": {
"tree": "objectstack-ai/objectstack at 029d8a4",
"method": "Find files that contain both record:(details|highlights|related_list) and the whole word requiredPermissions, per area. A node-level write needs both in one file, so this is a necessary condition. Each hit was then read to see whether the key sits on the node.",
"result": "No authored raw-node document writes requiredPermissions on any of the three blocks: examples 0, packages/platform-objects pages 0, content/docs 0, skills 0, docs 0. Every file-level hit is prose, not a node: an action label string in examples/app-showcase/src/system/translations/index.ts; the platform-objects CHANGELOG; the auto-generated component.mdx (its quick_actions row); releases/v17/17-1.mdx; skills/objectstack-ui SKILL.md and its evals (app navigation); docs/audits and the protocol upgrade guide (action, app.areas).",
"test_fixtures": "One file writes the key at node level: packages/spec/src/ui/component-record-block-field-security.test.ts. It has two refusal fixtures per block across the three blocks (requiredPermissions ['crm.manage'] and ['read'], both expecting unrecognized_keys). That is the absence pin, and it flips when the key is declared. packages/lint/src/lint-liveness-properties.test.ts matches only because of app.areas.",
"controls": "The positive control fired: the same method finds hideFields written at node level on record:details in packages/platform-objects/src/pages/sys-user.page.ts. It also finds hideFields in 3 test files and inlineEdit in 1 example. A further finding: requiredPermissions has no node-level author on record:quick_actions in this repo either. Every file where it co-occurs is prose about action gating, for example examples/app-showcase/src/ui/actions/index.ts and content/docs/ui/actions.mdx.",
"supplementary_objectui_at_pin": "objectui at 62597c5, read only through git show and git grep on that ref: 27 files co-occur. The node-level writers are its own renderer tests: record-blocks.requiredPermissions-gate.test.tsx, detailRendererUndeclaredKeys-8649.test.ts and record-details.test.tsx. No authored example or guide writes the key on the three blocks.",
"not_measured": "hotcrm, cloud and external apps; runtime traffic on the raw-node path."
},
"opposing_texts": {
"method": "A script over git ls-files. It strips leading * // # markers, drops backticks and joins lines and ' + ' seams. It then reads two-sentence windows that carry (requiredPermissions | third key | forked key | the fork), a negation marker and a block word. It was self-tested on 4 known sentences first. I read all 43 candidate sentences by hand; the 5 sites below are the true positives.",
"sites": [
"packages/spec/src/ui/component.zod.ts:1121-1132. The family header on RecordDetailsProps says: 'The THIRD key objectui reads on these three blocks — requiredPermissions — is deliberately NOT declared here. Its read is perms.can(objectName, name) … The exit is the spec seat's to rule.' It has been stale since pin bump 48c91e9, whether or not the key is declared, because the renderer no longer reads perms.can.",
"packages/spec/src/ui/component.zod.ts:1254-1257. The RecordRelatedListProps pointer: '… and why the third key objectui reads on this block is not declared.'",
"packages/spec/src/ui/component.zod.ts:1355-1358. The RecordHighlightsProps pointer, same sentence.",
"packages/spec/src/ui/component-record-block-field-security.test.ts, four places. Header :3-4: 'the key that is deliberately NOT beside it'. :13-39: 'This card declares TWO of the three and forks the third', plus the ABSENCE pin paragraph that cites perms.can, which is stale at the pin. Describe block :171-202: the refusal pins, including :192 'names a vocabulary the renderer's evaluator cannot express', which is false at the pin. :223-224: declaring('requiredPermissions') toEqual ['record:quick_actions']. The file itself says, at :36-39, that the ruling updates it in the same PR.",
".changeset/18159-record-block-field-security-pair.md:15. A pending, unreleased @objectstack/spec minor changeset: 'The third key … requiredPermissions — is deliberately NOT declared, and stays refused by name on all three. Its read is perms.can(objectName, name) …'. The next release will copy it word for word into packages/spec/CHANGELOG.md unless the declaring PR amends it first. After release, only a docs-only PR can correct it."
],
"checked_not_opposing": "skills/** (no line about requiredPermissions on the record blocks); the hand-written content/docs trees (none); packages/spec/liveness/.json (no ComponentPropsMap rows at all); the docs/audits strictness ledger (:473 is about actions); migration entry 18.ui-record-blocks-unknown-keys-refused.ts (quick_actions family only)."
},
"key_enumerating_surfaces": {
"note": "Read statically. I did not run the generators, because the stop came before any edit. The only measured comparison is PR #19185's sibling pair on the same three blocks.",
"rows": [
"react-blocks.ts and the generated react-blocks contract: unaffected. The three blocks are withdrawn from the react tier (REACT_RECORD_BLOCK_ALTERNATIVES :200-205), so none of their props are in the contract.",
"packages/spec/authorable-surface/ui.json: AFFECTED. gen:schema adds 3 rows, ui/Record{Details,Highlights,RelatedList}Props:requiredPermissions. The sibling pair moved this file by exactly 6 lines in #19185.",
"content/docs/references/ui/component.mdx: AFFECTED. gen:docs adds 3 table rows (the sibling pair added 6).",
"json-schema.manifest/ui.json: unaffected; it lists schema ids, not keys. authorable-defaults/ui.json: unaffected; it records only keys with defaults, and this key has none.",
"sdui.manifest.json: a spec edit does not change it. It is built from the objectui registry and regenerated only on a pin bump. At 62597c5 the registry declares requiredPermissions as an input only on record:quick_actions (plugin-detail/src/index.tsx:807). On the three blocks it declares neither the sibling pair nor this key.",
"liveness: packages/spec/liveness/.json has no ComponentPropsMap rows.",
"metadata-form catalogs: none of the 17 packages/spec/src/**/*.form.ts files lists component props (page.form.ts covers page metadata only).",
"lint validate-page-field-bindings.ts BINDINGS (:194, :200): lists only props that hold field names, so it is unaffected.",
"Hypothetical dispatch-gates run for component.zod.ts plus the test and the changeset: exit 0, 83 command lines. They include check:authorable-surface, check:docs, check:objectui-pin-citations and check:react-declaration-parity."
]
},
"tests": "(1) Instruments A (parse probe) and B (.shape enumeration), a scratch probe that passed its 5 self-tests before running on the tree, on origin/main 029d8a4. Output: 'ComponentPropsMap entries: 49 (strict 48, loose 1)'. requiredPermissions A=1 B=1 agree=true ['record:quick_actions']. enforceFieldSecurity 3/3 and redactFields 3/3. Lit controls: aria A=21 B=21, fields A=7 B=7. record:details, record:highlights and record:related_list each give '+requiredPermissions unrecognized=true' and '+zzqx unrecognized=true'; record:quick_actions gives '+requiredPermissions unrecognized=false'. (2) The existing absence pin on 029d8a4, run under the verify lock with 'pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/ui/component-record-block-field-security.test.ts': 'Test Files 1 passed (1) / Tests 18 passed (18)', VERDICT command-exit 0. (3) Unlock: GET objectui compare 28b0658...62597c5 gives ahead 33 / behind 0, and the reversed compare gives behind 0 / ahead 33. compare c2dabf0...62597c5 gives ahead 21 / behind 0. Local merge-base --is-ancestor c2dabf0 62597c5 exits 0; exit 0 proves itself even on a shallow checkout. (4) dispatch-gates --commands --repo objectstack-ai/objectstack exits 2 with 'this branch changes nothing against origin/main', so no families are owed and there is nothing to reconcile with --ran. NOT MEASURED, because I stopped before the edit: the spec suites (--project local and repo), typecheck, build, check:generated, the live citation gate.",
"ablations": "None run, because there is no declaration to ablate. The absence pin is green on main (18/18). After the ruling, describe block :171-202 becomes accept pins, and the ablation is owed then: remove the three declarations, rebuild, run ablation-dist-preflight --absent, expect red, then restore.",
"ci": "There is no PR, so no CI ran at any head of mine.",
"mcp_calls": "0. No MCP GitHub tool was called, read or write. All reads went through REST via the proxy.",
"api_writes": "1: POST /repos//issues/18159/comments (this report), sent with scripts/pm/post-stamped.mjs over the fleet-write relay (transport dispatch). The git push of the empty branch is not a REST write. No label writes and no PR writes.",
"open_questions": [
{
"question": "Which describe should the three new declarations carry? Ruling 5749268463 says 'same shape and same describe as record:quick_actions'. The dispatch says to use that describe unless it is false for the capability semantics, and to stop if it is. Measured at .objectui-sha 62597c5, it is false in three ways. (1) 'on this object': all four renderers say 'A system capability is not object-scoped' and no longer pass the object name to the check (record-details.tsx:223, record-related-list.tsx:229, record-highlights.tsx:77, record-quick-actions.tsx:252). The phrase describes the object-action reading that ruling B refused. It also leads an author to write read or update. The capability path treats those as capabilities nobody holds, so on a provider that reports capabilities the block is hidden for everyone (objectui PR 10153, DISCRIMINATOR 3: 'manage' is gated like any other capability name). (2) 'the whole bar' is the wrong noun on details, highlights and related_list. (3) The hide claim has no conditions, but hasCapabilities returns true when the provider reports no systemPermissions (MePermissionsProvider.tsx:410-416; PermissionProvider.tsx:77 always returns true). The gate is also presentation only, because the data API still serves the data. The sibling enforceFieldSecurity and redactFields describes on the same three blocks were required to say this (Prime Directive #10), and bulk-action.zod.ts:374 says it for its own requiredPermissions. The published quick_actions describe is false today for the same reasons.",
"options": [
"A: write ONE corrected describe and use it word for word on all four blocks, in the same declaring PR. The published record:quick_actions row changes its describe only, not its shape. The seat approves the wording. Here is a candidate for the seat to accept or rewrite; I have not written it into code: '[ADR-0066] Capabilities (system permissions: names a permission set grants in systemPermissions, e.g. crm.manage) that the current user must ALL hold; otherwise this block shows an insufficient-permissions notice instead. They are not object-scoped and not object actions: read or update is just another capability name, and a name nobody holds hides the block for everyone. Presentation only: it authorises nothing and the data API still serves the record. A client that cannot resolve the caller's capabilities renders the block (fails open).' COST: one more published describe changes, so gen:schema and gen:docs regenerate one extra row. The text must use a neutral noun ('this block') to stay identical across the four blocks. objectui's own registry description for quick_actions (plugin-detail index.tsx:807) is objectui's to mirror.",
"B: copy the quick_actions describe word for word onto the three blocks (at most changing bar to block), keeping 'on this object'. File the correction for all four as a separate class-b card. COST: publishes text known to be false on three more surfaces, and all four rows change again later. The object-scoped wording keeps inviting read and update, which now hide the block for everyone.",
"C: write a separate describe for each of the three new rows and leave the published quick_actions text as it is. COST: one word with four texts, which goes against ruling B's 'one word, one meaning', and the published half stays false."
],
"recommendation": "A. It is the only option where 'same describe as record:quick_actions' and 'the describe is true' are both satisfied. It also fixes the published half in the same landing, instead of spreading its current wording to three more blocks first.
}
],
"out_of_scope_findings": [
"class: b · The published record:quick_actions.requiredPermissions describe (component.zod.ts:1786) contradicts its shipped renderer at .objectui-sha 62597c5. On object scoping: record-quick-actions.tsx:252 says 'A system capability is not object-scoped', and the gate at :263 is perms.hasCapabilities(required). On the unreported-provider fail-open: MePermissionsProvider.tsx:410-416. Seam: spec:RecordQuickActionsProps.requiredPermissions → renderer:plugin-detail record-quick-actions.tsx. This falls inside #18159: if open question 1 is answered A, the declaring PR fixes it, so it needs no card of its own. On B, the seat files it as a sub-issue of #18159. Dedupe words: quick_actions requiredPermissions describe object-scoped · requiredPermissions capability describe fail-open unreported · record block requiredPermissions describe.",
"noted, not filed · packages/lint/src/validate-capability-references.ts walks only objects, fields, actions and apps (:133-181), never page components. A mistyped capability on record:quick_actions today, and on the three blocks after the declaration, gets no ADR-0066 ⑨ authoring warning. At runtime it fails closed with a visible insufficient-permissions notice, so it is not class c. carrier: whoever next edits validate-capability-references.ts. 承接者:无.",
"noted, not filed · Three texts cite 'Its read is perms.can(objectName, name)': the RecordDetailsProps family header, the absence-pin test header and the pending changeset. All three have been stale on main since pin bump 48c91e9, whether or not this card lands. carrier: this card's round after the ruling, which has to rewrite all three anyway (listed under opposing_texts)."
],
"cleanup": "Right after this comment is posted: rm -rf node_modules in /home/user/objectstack-issue-18159, then git worktree remove without --force. The hand-back message confirms the result. No dev server, background job or monitor was started. The remote branch stays at 029d8a4, with no commit, for the round after the ruling to reuse."
}
Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
Seat ruling on the dev's open question (report
5798750532): A, one true describe, word for word on all four blocksdomain:spec#5seat, 2026-09-23T16:39Z. Checked before ruling:packages/spec/src/ui/component.zod.ts:1786onorigin/mainreads 「Hide the whole bar unless the current user holds every named permission on this object.」At
.objectui-sha62597c588072,packages/plugin-detail/src/renderers/record-quick-actions.tsx:252says the capability 「is not object-scoped」, and the gate ishasCapabilities(:235).Ruling
5749268463asks for 「same shape and same describe asrecord:quick_actions」 so that one word keeps one meaning ([Decision]requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186 ruling B). The published describe is false against that ruling's own semantics. So the declaring PR writes one describe that is true, and uses it word for word onrecord:quick_actionsand on the three record blocks. The shape (z.array(z.string()).optional()) does not change anywhere. The only thing that moves onquick_actionsis a published describe.The seat does not approve a wording in advance. The describe must hold these properties, each backed by the renderer lines at the pin, cited in the PR:
If one sentence cannot be true for all four renderers, stop and report. Don't split the text.
Consequence to plan for: the pending note
.changeset/18159-record-block-field-security-pair.md(from PR spec(ui):record:details/record:highlights/record:related_listdeclareenforceFieldSecurityandredactFields—requiredPermissionsforks on a measured fail-open gate #19185) says the third key 「is deliberately NOT declared」. This PR makes that false, so the PR corrects that note. That is a DELIBERATE CORRECTION undercheck-empty-changeset.mjs:Check Changesetstays red, and the maintainer confirms in writing on the PR.The dev's other two notes stay noted. Neither is filed or taken in this PR:
validate-capability-references.tsdoes not walk page components;perms.can. This PR rewrites them anyway, as listed under opposing texts.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{
"issue": 18159,
"status": "done",
"branch": "claude/issue-18159-record-block-required-permissions",
"pr": "#19913",
"head_sha": "dcef292d4e. Branch is origin/main 44ce049 (fast-forwarded from the earlier empty push at 029d8a4, no force) plus two commits: 254cdcf (source, test, changesets) and dcef292 (regenerated artifacts). origin/main is now d1ca874, 2 commits ahead. A driver-free merge-tree probe (bare --shared clone) exits 0, and neither commit touches the files this PR changes, so no merge was needed.",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (a subagent, so this is the dispatching seat's id)",
"premise_still_valid": true,
"summary": "Ruling A is carried out as the seat ruled it (comment 5798783314). requiredPermissions is now declared on record:details, record:highlights and record:related_list with shape z.array(z.string()).optional(). record:quick_actions keeps that shape. All four blocks use ONE describe, taken from a single non-exported constant, RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION, so they cannot drift. A test checks that the four JSON Schemas of the key are identical, describe included. One text holds all five properties on all four renderers, so nothing was split. The published quick_actions describe changes, and the new changeset says so. I rewrote the five places that said the key was not declared, turned the absence pin into accept pins (instruments A and B, lit controls aria and fields), and corrected the pending pair changeset. That correction leaves Check Changeset red on purpose (DELIBERATE CORRECTION), with no skip-changeset. The describe text: '[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through
systemPermissions, not object actions:readorupdatehere is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not reportsystemPermissions) renders this block as if they were held — it fails open; a resolved empty set gates like any other.'","describe_citations_at_objectui_pin_62597c588072": [
"1 capabilities, not object actions: the gate is perms.hasCapabilities(required) at record-details.tsx:234, record-highlights.tsx:93, record-related-list.tsx:242 and record-quick-actions.tsx:263. 'is not object-scoped' appears at record-details.tsx:223, record-highlights.tsx:77, record-related-list.tsx:229 and record-quick-actions.tsx:252.",
"2 all of them: permissions/src/MePermissionsProvider.tsx:416, return required.every(...) over the held set. The providers are MePermissionsProvider:410, PermissionProvider:184 and the no-provider constant usePermissions:45; git grep 'hasCapabilities:' at the pin finds no others.",
"3 outcome: a role=status 'Insufficient permissions to view …' notice replaces the content at record-details.tsx:234-242, record-highlights.tsx:151-164, record-related-list.tsx:242-250 and record-quick-actions.tsx:263-271. Earlier checks can withhold the content first: record-details.tsx:186 (no record), record-related-list.tsx:184 (no object) and :202 (read gate on the related object, which returns null). Hence the wording 'wherever it would otherwise render'.",
"4 presentation only: git grep requiredPermissions over the server source (packages/rest, runtime, services, metadata*, plugins) finds nothing that reads a page component's key. Also, a gated highlights block registers [] (record-highlights.tsx:146-149), so record:details no longer de-duplicates those fields.",
"5 unresolved capabilities fail open: MePermissionsProvider.tsx:414 'if (!Array.isArray(perms)) return true;', PermissionProvider.tsx:77 'ALL_CAPABILITIES … () =] true' (bracket spelled out), and usePermissions.ts:45 'hasCapabilities: () =] true'. A reported [] reaches :416 and gates.",
"Discrepancy, noted only: the seat ruling's subscript gives the quick_actions gate as :235. At the pin, :235 is inside the docblock; the gate is at :263."
],
"opposing_texts_rewritten": [
"component.zod.ts, RecordDetailsProps family header: the 'deliberately NOT declared … perms.can … The exit is the spec seat's to rule' paragraph now says the key is declared below as the block-level ADR-0066 capability gate, that it is presentation only, and where the read points are.",
"component.zod.ts, the RecordRelatedListProps and RecordHighlightsProps pointers: 'why the third key … is not declared' becomes 'how they differ from the block-level requiredPermissions gate declared below'.",
"component-record-block-field-security.test.ts: the header is rewritten. The absence pin (old :171-202) becomes accept pins, the stale PermissionActionSchema pin is removed along with its import, and the old :223-224 census now expects the three blocks plus record:quick_actions.",
".changeset/18159-record-block-field-security-pair.md:15: 'deliberately NOT declared … Its read is perms.can' becomes 'declared in the same release, by its own entry … the block-level ADR-0066 capability gate, not a member of this pair'.",
"Check after the edits: grep over the two source files finds 'deliberately NOT declared' only in six unrelated places (the page:card body, a section title, and four view-config blocks further down the file). 'on this object' now appears only in the actionName and actionNames describes, where it is true."
],
"census": "Unchanged from report 5798750532, taken on 029d8a4. No authored raw-node document in examples, platform-objects, docs or skills writes the key on the three blocks. The only node-level writers were the refusal fixtures of the absence pin, and they are now accept fixtures.",
"tests": "All at head dcef292 except where a commit is named. (1) The pin file on its own: 'Test Files 1 passed (1) / Tests 25 passed (25)'. (2) vitest run --project local: 'Test Files 526 passed (526) / Tests 15508 passed | 1 todo (15509)', VERDICT command-exit 0. Run at 254cdcf; dcef292 adds only generated json and mdx. (3) vitest run --project repo: 'Test Files 35 passed (35) / Tests 602 passed (602)', VERDICT 0. (4) pnpm --filter @objectstack/spec typecheck exits 0 ('check:test-typecheck: OK — 53 file(s) / 255 error(s) / 142 pinned signature(s)'); tsc --listFilesOnly shows the test file is in the tsconfig.test.json program. (5) build exits 0, 34/34 d.ts. (6) gen:schema changed authorable-surface/ui.json by +3 rows; authorable-surface.base.json is untouched. gen:docs changed component.mdx by +3 rows and 1 changed row. check:generated: 'All 15 generated artifacts are up to date'. (7) dispatch-gates --commands --repo objectstack-ai/objectstack derived 108 commands, all re-run after the last commit. --ran reports 'Run reconciliation — 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN'. 107 exit 0; the only non-zero is check-empty-changeset.mjs --base origin/main, exit 1, the DELIBERATE CORRECTION by design. Seven gates first exited 3 on missing prerequisites. They went green after I built the lint, client-react and objectql closures and then the whole packages set (72 tasks, 34 from cache), so none is counted as a measured failure. (8) Live citation gate: check:objectui-pin-citations exits 0; with --verify-anchors against ../objectui (read-only git show) it prints '48 asserting objectui pin citation(s) match .objectui-sha (62597c5)' and '7 anchor content assertion(s) verified'. (9) MANIFEST=sdui.manifest.json check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict exits 0: 'no new DECLARATION divergence'. The record blocks are outside this gate's population. (10) eslint --no-inline-config --format json on the two touched TS files: 2 files, 0 errors, 0 warnings. The narrowing loses nothing: the repo config enables no type-aware linting (eslint.config.mjs:324-332, no parserOptions.project), so an untouched file's verdict cannot move. The json and mdx files are generated, not linted.",
"ablations": "Committed first, then scripts/ablation-replace.mjs --expect 3 replaced the three block declarations (the anchor includes the preceding TSDoc tail, so quick_actions is not touched) with a marker comment. Its output: 'anchor hits 3 time(s), as declared', 'anchor x3 -> x0', 'replace x0 -> x3', 'blob 6b1263a24511 -> ae4de3107167'. On-disk grep during the run: declaration lines 4 to 1, marker 0 to 3. Mutated run: 'Tests 11 failed | 14 passed (25)'. All 11 failures are requiredPermissions pins (A x3, B x3, the census, the value arm x3 and the identity pin); the 14 that stayed green are the pins for the field-security pair and the strictness controls. The tool restored the file itself ('blob after restore 6b1263a2… = blob at HEAD'). My own check with an absolute-path trap: git hash-object equals the HEAD blob and is non-empty, git diff HEAD is empty, git status is clean. Restored run: 'Tests 25 passed (25)'. No build or dist step was needed: the test imports ./component.zod by a relative path, so the subject resolves to src.",
"ci": "Read at head dcef292 right after the PR opened, without waiting or polling. 32 check-runs: 9 completed (success: the claim, single-writer-path, Part-of and branch-claim guards, Check Documentation Links, Check PR Size, filter; skipped: Console Pin Gate and the opt-in tarball smoke), 22 in_progress, 1 queued. Still in progress: Lint & Repo Gates, all Type Check jobs, Spec property liveness, Governed Surface Queue Guard and Check Changeset. Check Changeset is expected to go red (DELIBERATE CORRECTION). The seat posts the gate comment and the maintainer confirms. in_progress is the honest value.",
"mcp_calls": "0. No MCP GitHub tool was called. All reads went through REST via the proxy, and all writes through the fleet-write relay.",
"api_writes": "2 this round: POST /repos/objectstack-ai/objectstack/pulls (pr_create op via the relay, draft forced, run 35898046389 success, PR 19913; I read the body back and it matches what I sent, with one footer), and POST /repos//issues/18159/comments (this report, via post-stamped.mjs over the relay from a detached worktree at origin/main). The earlier round wrote 1: comment 5798750532. No label, ready, auto-merge or merge writes. Git pushes are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed · objectui's registry declares a requiredPermissions input only on record:quick_actions (plugin-detail/src/index.tsx:807, description 'Hide the whole bar unless the user holds these permissions'), not on the three blocks. The ruling expects objectui to mirror the declaration. carrier: objectui's next mirror or console pin round; react-declaration-parity cannot see it, because these blocks are outside its population.",
"noted, not filed (carried from the earlier report, as the seat ruled) · validate-capability-references.ts does not walk page components, so a mistyped capability on any of the four blocks gets no ADR-0066 ⑨ warning. carrier: none."
],
"cleanup": "After the report is read back: remove the detached posting worktree, then rm -rf node_modules and git worktree remove /home/user/objectstack-issue-18159 without --force. The scratch probe clone PROBE.git is deleted. No dev server, background job or monitor is still running: the one command the harness moved to the background (the repo suite) finished with exit 0 and was read. /home/user/objectstack and /home/user/objectui were only read. The hand-back message confirms the outcome."
}
Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
PR #19913:达档复核 FAIL(只错在文字),第二轮已下令 —— 只删不加
domain:specseat 5(session_01Sfe5YjBLwB9J3y8fvm2xq1),2026-09-23T18:28Z。5800509073(在 PR feat(spec): declare requiredPermissions on record:details / highlights / related_list with one true describe shared with record:quick_actions #19913 上,头dcef292d4e)。四个块共用的那段描述,逐条对照四个渲染器都成立。唯一的阻塞项是被更正的待发布说明.changeset/18159-record-block-field-security-pair.md:15写着「hides the whole block」。本席在 objectui62597c588072上亲自读过:record-details.tsx:234-242渲染的是一条role="status"的「权限不足」提示,不是把块藏起来。needs:contract-review。requiredPermissionsis an input onrecord:quick_actionsonly, and its description says "hide the whole bar" while the renderer shows a notice — the objectui half of objectstack#18159 objectui#10224。objectui 的 registry 只在record:quick_actions上声明了这个键,说明文字写的是「Hide the whole bar」。5798783314: 小字里 quick_actions 门禁的行号:235引错了,应为:263(:235落在 docblock 里)。Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report⚠️ bullet",
{
"issue": 18159,
"round": "2 (contract review 5800509073 FAIL at dcef292). This round only deletes words.",
"status": "rework",
"branch": "claude/issue-18159-record-block-required-permissions",
"pr": "#19913",
"head_sha": "30fc769272. dcef292 plus 7c519e7 (the cuts) and 30fc769 (gen:docs output). Pushed as a fast-forward: no rebase, no force.",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (a subagent, so this is the dispatching seat's id)",
"premise_still_valid": true,
"summary": "I made every ordered cut except one, and that one is stopped as the order requires. Cuts 1 (the blocking one), 2a, 3 and 4 are done, plus one more claim found by the enumeration. Cut 2b, deleting 'it hides the whole block and' from the RecordDetailsProps family header, is held: the literal cut leaves 'Like this pair it is presentation only: authorises nothing.', which has no subject after the colon. I wrote no new text and did not choose a different span; see open_questions 1. Hence status 'rework'. The source diff this round is 5 lines modified, 0 added and 0 net; a word diff shows only deletions plus the unchanged word that now ends each sentence. gen:docs changed only the four requiredPermissions rows in component.mdx.",
"cuts": [
{
"item": "1, BLOCKING · .changeset/18159-record-block-field-security-pair.md:15",
"before": "… It is the block-level ADR-0066 capability gate, not a member of this pair: a capability set read through the permission context's capability path, which hides the whole block and, like this pair, authorises nothing.",
"after": "… It is the block-level ADR-0066 capability gate, not a member of this pair.",
"note": "The first bold sentence is unchanged."
},
{
"item": "2a · component.zod.ts, RecordDetailsProps family header (:1177-1178)",
"before": "It is the block-level ADR-0066 capability gate, read through the capability set and fail-closed, with the one describe it shares word for word with record:quick_actions …",
"after": "It is the block-level ADR-0066 capability gate, read through the capability set, with the one describe it shares word for word with record:quick_actions …"
},
{
"item": "2b, HELD (STOP) · component.zod.ts, same header (:1180-1181)",
"before": "Like this pair it is presentation only: it hides the whole block and authorises nothing.",
"after": "unchanged. The literal cut would read 'Like this pair it is presentation only: authorises nothing.', which is ungrammatical (no subject after the colon). See open_questions 1."
},
{
"item": "3 · component.zod.ts, the constant's docblock (:897-899)",
"before": "Unlike the field's own requiredPermissions (ADR-0066 D3, enforced by the server before the payload leaves it), this key hides a block and authorises nothing.",
"after": "Unlike the field's own requiredPermissions (ADR-0066 D3, enforced by the server before the payload leaves it), this key authorises nothing."
},
{
"item": "4 · the shared describe RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION (final sentence)",
"before": "A client that cannot resolve the user's capabilities (no permission provider, or one that does not report systemPermissions) renders this block as if they were held — it fails open; a resolved empty set gates like any other.",
"after": "A client that cannot resolve the user's capabilities (no permission provider, or one that does not report systemPermissions) renders this block as if they were held — it fails open.",
"note": "gen:docs carried the same cut into the four component.mdx rows, and nothing else changed there."
},
{
"item": "5, found by the enumeration · .changeset/18159-record-block-required-permissions.md, the
"before": "If a page writes object actions there (read, update), the console reads them as capability names: a name nobody holds hides the bar for every user whose capabilities are reported.",
"after": "If a page writes object actions there (read, update), the console reads them as capability names."
}
],
"enumeration_item_5": {
"method": "A script over the ADDED lines of 44ce049..HEAD (git diff --unified=0), grouped per hunk. It strips leading * and // markers, drops backticks, joins ' + ' seams and whitespace, splits into sentences, and matches hid(e|es|den|ing), remov(e…), vanish, disappear, withh(e|o)ld, fail-?closed and 'drops the block/bar'. It was self-tested on 4 known sentences first. Scope: both changesets, component.zod.ts, the test file, component.mdx and authorable-surface/ui.json. Before the cuts: 31 hunks, 7 matching sentences. After: 3.",
"claims_deleted": [
"pair changeset :15, 'which hides the whole block' (item 1)",
"constant docblock, 'this key hides a block' (item 3)",
"family header, 'fail-closed' (item 2a)",
"new changeset, 'a name nobody holds hides the bar for every user whose capabilities are reported' (cut as the enumeration asked)"
],
"claims_held": [
"family header, 'it hides the whole block and authorises nothing' (item 2b; open_questions 1)"
],
"matches_kept_because_not_a_claim_about_this_gate": [
"new changeset: 'It read "Hide the whole bar unless the current user holds every named permission on this object."' This quotes the replaced describe, not a claim.",
"constant docblock, clause 3: 'Checks that already withhold the content run first on two of them (no record bound, record-details.tsx:186; no object, record-related-list.tsx:184; the related object's read gate, record-related-list.tsx:202)'. It is about those other checks, and true at the pin: a placeholder, a placeholder, and return null."
],
"test_comments": "none",
"docs": "none besides the describe rows gen:docs regenerated",
"pr_body_seat_owned_read_only": "one match, 'Removing the three declarations turns 11 of 25 pins red'. It is about the ablation, not the gate. Not touched."
},
"tests": "At head 30fc769 (spec source identical to 7c519e7). (1) build, whose gen:schema step left authorable-surface unchanged, then gen:docs: exit 0, 34/34 d.ts, 'Generated 225 files'; only component.mdx changed. (2) check:generated: 'All 15 generated artifacts are up to date'. (3) vitest --project local: 'Test Files 526 passed (526) / Tests 15508 passed | 1 todo (15509)', VERDICT command-exit 0. (4) vitest --project repo: 'Test Files 35 passed (35) / Tests 602 passed (602)', VERDICT 0. (5) pnpm --filter @objectstack/spec typecheck exits 0 ('check:test-typecheck: OK — 53 file(s) / 255 error(s) / 142 pinned signature(s)'). (6) The pin file with --reporter=verbose: 25/25, including '✓ … one key, one text: the four record blocks declare it IDENTICALLY', so the identity pin stays green. (7) Live citation gate: check:objectui-pin-citations exits 0; --verify-anchors reports '48 asserting objectui pin citation(s) match .objectui-sha (62597c5)'. (8) Also run: eslint --no-inline-config on component.zod.ts, 1 file, 0 errors, 0 warnings; check-changeset-no-major exits 0; check-adr-0087-registration exits 0; check:nul-bytes exits 0; check:doc-authoring exits 0. check-empty-changeset exits 1, which is the same DELIBERATE CORRECTION as before and names only the pair changeset. (9) origin/main a34c27c touches none of the files this PR changes; a driver-free merge-tree probe from a bare --shared clone exits 0.",
"ablation": "The count did not move. Same anchor and --expect 3 as round 1, on HEAD blob c7c139bd76. The tool reported 'anchor hits 3 time(s), as declared', 'ok mutation landed: anchor 3 -> 0, blob c7c139bd7682 -> 4b040f75f8d6'. On disk during the run: declaration lines 4 to 1, markers 0 to 3. Result: 'Tests 11 failed | 14 passed (25)', the same 11 and 14 as round 1. Restore: the tool's 'blob after restore' equals 'blob at HEAD' (c7c139bd76). My check with an absolute-path trap: git hash-object equals the HEAD blob and is non-empty, git diff HEAD is empty, git status is clean.",
"ci": "Read at head 30fc769 without waiting or polling. 34 check-runs: 30 success, 2 skipped, 1 failure and 1 in_progress. The failure is Check Changeset, red by design (DELIBERATE CORRECTION; the gate comment should quote the pair-changeset line as it now ships: 'It is the block-level ADR-0066 capability gate, not a member of this pair.'). The one in_progress run is Test Core (1/6).",
"mcp_calls": "0",
"api_writes": "1 this round: POST /repos//issues/18159/comments (this report), via post-stamped.mjs over the fleet-write relay from a detached worktree at origin/main. The PR body was not touched. No label, ready, auto-merge or merge writes. The two git pushes are not REST writes.",
"open_questions": [
{
"question": "Cut 2b, STOPPED. The order says to delete 「it hides the whole block and」 from 'Like this pair it is presentation only: it hides the whole block and authorises nothing.' (component.zod.ts:1180-1181). The literal cut leaves 'Like this pair it is presentation only: authorises nothing.', a clause with no subject after the colon. The order says to stop on an ungrammatical cut and write no new text, so the sentence is unchanged and the false 'hides the whole block' is still in this docblock. The review listed it as non-blocking (a).",
"options": [
"A: delete only 'hides the whole block and ', keeping 'it'. Result: 'Like this pair it is presentation only: it authorises nothing.' A pure cut: it adds no word and is grammatical and true.",
"B: make the literal cut and accept the fragment 'presentation only: authorises nothing.'",
"C: delete the whole sentence. A pure cut, but it drops 'presentation only' from the header; the describe and the constant's docblock still say it."
],
"recommendation": "A. It is the smallest deletion that removes the false claim and leaves a grammatical sentence. It needs a one-line go-ahead because it is not the span the order named."
},
{
"question": "The new changeset still says '… renders the block as if they were held (fails open); a resolved empty set gates.' This clause has the same misreading that cut 4 removed from the describe: it can be read as 'an empty requiredPermissions list gates', which the renderers' required.length check makes false. It is not a hide claim, so item 5 does not cover it, and I did not cut it.",
"options": [
"A: delete '; a resolved empty set gates' so the sentence ends '(fails open).' That matches the describe.",
"B: keep it. Read as the user's capability set, it is true."
],
"recommendation": "A, for consistency with cut 4. Waiting for the word before cutting, because the order did not name it."
}
],
"out_of_scope_findings": [
"noted, not filed (unchanged) · objectui's registry declares a requiredPermissions input only on record:quick_actions (plugin-detail/src/index.tsx:807). The review asks the seat to name a carrier for the objectui mirror; that is the seat's call, not the dev's."
],
"cleanup": "After this comment is posted: remove the detached posting tree, then rm -rf node_modules and git worktree remove /home/user/objectstack-issue-18159, both without --force. The repo suite ran under nohup with its PID recorded; I waited for it with tail --pid and it has exited. Nothing else was started. /home/user/objectstack was only fetched into and used to add and remove worktrees."
}
Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{
"issue": 18159,
"round": "3. Carries out the seat's ruling on the two round-2 questions: Q1 A and Q2 A, both pure deletions.",
"status": "done",
"branch": "claude/issue-18159-record-block-required-permissions",
"pr": "#19913",
"head_sha": "aeb6a57456. It is 30fc769 plus one commit, pushed as a fast-forward with no rebase and no force. The PR reads head aeb6a57, still draft. The PR body was not touched.",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (a subagent, so this is the dispatching seat's id)",
"premise_still_valid": true,
"summary": "Both ruled cuts are made. Nothing else changed: 2 lines modified, 0 added. The word diff shows only the two deleted spans and '{+open).+}', the word that now ends the changeset sentence. Neither text feeds a generator (one is a comment on a non-exported constant, the other a changeset), and check:generated confirms all 15 artifacts are current. Re-running the hide-claim scan over the PR's added text leaves 2 matches, and neither is a claim about the gate: the quote of the replaced quick_actions describe, and the sentence about the OTHER checks that withhold content.",
"cuts": [
{
"item": "Q1 A · packages/spec/src/ui/component.zod.ts:1180-1181, RecordDetailsProps family header",
"before": "Like this pair it is presentation only: it hides the whole block and authorises nothing.",
"after": "Like this pair it is presentation only: it authorises nothing."
},
{
"item": "Q2 A · .changeset/18159-record-block-required-permissions.md:10",
"before": "A client that cannot resolve the user's capabilities renders the block as if they were held (fails open); a resolved empty set gates.",
"after": "A client that cannot resolve the user's capabilities renders the block as if they were held (fails open)."
}
],
"tests": "At head aeb6a57. (1) vitest --project repo: 'Test Files 35 passed (35) / Tests 602 passed (602)', VERDICT command-exit 0. Two earlier attempts ended in queue-timeout (exit 99, never acquired, NOT MEASURED) while a sibling seat's issue-15293 heavy batch held the lock. I read that batch's logs without writing to it: it was progressing and reached its last step, so I took my kept slot again and the third attempt acquired the lock. (2) One locked batch: build exits 0; check:generated exits 0 with 'All 15 generated artifacts are up to date'; vitest --project local gives 'Test Files 526 passed (526) / Tests 15508 passed | 1 todo (15509)'; pnpm --filter @objectstack/spec typecheck exits 0 ('check:test-typecheck: OK … 53 file(s) / 255 error(s) / 142 pinned signature(s)'); the pin file with --reporter=verbose gives 'Tests 25 passed (25)', including '✓ … one key, one text: the four record blocks declare it IDENTICALLY', so the identity pin is green. Batch VERDICT command-exit 0. (3) Unlocked extras: check:objectui-pin-citations exits 0; eslint --no-inline-config on component.zod.ts exits 0; check-changeset-no-major exits 0; check-adr-0087-registration exits 0; check-empty-changeset exits 1, the same DELIBERATE CORRECTION as before, naming only the pair changeset. (4) origin/main touches none of this PR's files since the merge base. The ablation was not re-run: the declarations are unchanged, and the round-2 result (11 failed / 14 passed) still applies.",
"ci": "Read at head aeb6a57: 35 check-runs, 32 success, 2 skipped and 1 failure, with none pending. The failure is Check Changeset, red by design (DELIBERATE CORRECTION on .changeset/18159-record-block-field-security-pair.md). Its line 15 is unchanged since round 2: 'It is the block-level ADR-0066 capability gate, not a member of this pair.' The PR's labels (documentation, size/m, tests, tooling, protocol:ui) come from the repo's automation; I wrote none.",
"mcp_calls": "0",
"api_writes": "1 this round: POST /repos//issues/18159/comments (this report), via post-stamped.mjs over the fleet-write relay from a detached worktree at origin/main. No PR-body, label, ready, auto-merge or merge writes. The git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [],
"cleanup": "After this comment is posted: remove the detached posting tree, then rm -rf node_modules and git worktree remove /home/user/objectstack-issue-18159, both without --force. The four nohup'd lock calls (three repo-suite attempts, two of which timed out in the queue, and the batch) had their PIDs recorded, I waited on each with tail --pid, and all have exited. The two queue-timeouts released their lock tickets. Nothing else was started."
}
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Cross-seat note from the objectui
domain:ui#4execution seat (session_01BP8CMtACxTdLjqR6rhd33C), 2026-09-24T15:26Z. There is no request to act yet; this is so the spec seat knows before it lands #19913.objectstack-ai/objectui#10200 carries a maintainer ruling from 2026-09-24T13:37Z, comment
5815200174. It orders the objectui renderer to stop readingrequiredPermissionsonrecord:details, because the spec deliberately does not declare it there. This card's ruling (batch #197 item 2 A) declares it on all three record blocks. The two point opposite ways, and the objectui ruling does not mention this one.It is filed for the maintainer as objectstack-ai/objectui#10281 (the seat recommends A: this card's declaration wins, and the objectui item is withdrawn). The objectui PR that would remove the read, objectstack-ai/objectui#10279, is held with no auto-merge until that answer. Nothing is asked of this card now; if the answer is B, the director seat carries the re-ruling here.
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Release:
5798529253(session_01Sfe5YjBLwB9J3y8fvm2xq1, seatdomain:spec#5), taken over bydomain:specseat 4,session_019c3Hi6ZMU1p6m6aA6Bz45d. Cause: PR #19913 is delivered and at-tier PASSed (5801959316); it has waited since 2026-09-23 on a changeset-correction confirmation, and the maintainer has directed this seat to handle it. Destination: theClaim:below.谁的指令: the maintainer
原话 (their reply quotes this seat's question, then answers it):
Claim: PM loop — land PR #19913 (takeover, seat
domain:spec#4)Session:
session_019c3Hi6ZMU1p6m6aA6Bz45dBranch:
claude/issue-18159-record-block-required-permissionsWorktree: none — landing only
Domain:
domain:specSeat:
domain:spec#4File surface: the PR's existing files, unchanged by this act (landing only)
Container & model: landing only, no dev dispatched
Clause-②: yes
Thread-read: 5817071526
Serial constraints cleared: open-PR census 2026-09-24T17:55Z:
git merge-treeagainst today'sorigin/mainis clean; no other open PR edits this PR's changeset files except the Version Packages PR #17076, which touches every pending changeset.The four-part takeover, in one comment
① The
Release:line above names the holder's claim5798529253and its session, with the three provenance fields.② Assignee:
os-justin→os-litant, written in the same act.③ The
Claim:above continues branchclaude/issue-18159-record-block-required-permissionsat remoteaeb6a57456(PR #19913). No new branch.④ Handover record: the holder's last pushed sha is
aeb6a57456. Status: draft · CI on that head all success or skipped exceptCheck Changeset(red by design: it corrects the pending.changeset/18159-record-block-field-security-pair.mdand adds its own) ·git merge-treewith today'sorigin/mainis clean.Plan
requiredPermissionsfor the record blocks: the spec declares it (objectstack#18159) vs. the renderer stops reading it (objectui#10200) objectui#10281 (cross-seat note5817071526above). Under A, this card's declaration stands and the PR lands. Under B, this card is re-ruled and the PR closes. This seat has put the question to the maintainer. ⛔ Landing before the letter would choose A on the maintainer's behalf.⛔ No liveness judgement is made about seat 5. The maintainer's word is the permission, copied with its provenance.
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
objectstack-ai/objectui#10281 is ruled A, recorded there as
5823922380(maintainer 「19913 同意A」, carried from this seat's chat 2026-09-24T23:25Z). The protocol declaration wins: this card's ruling5749268463stands, PR #19913 lands, and the objectui half (closing objectui#10279, withdrawing item 1 of objectui#10200) moves to thedomain:uiseat through that card'spm:queue.PR #19913 state at this act:
aeb6a57456;5801959316on that head;5823706830).It waits only on the landing channel: the allow-listed ccr
ready_for_reviewis denied by the session classifier (5819399538), and that waits on the maintainer.Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
os-dev-report
{
"issue": 18159,
"round": "Fix round after the merge-queue dequeue (CI_FAILURE at check:objectui-pin-citations in Type Check - source gates): merge origin/main, re-measure the one stale pin citation.",
"status": "done",
"branch": "claude/issue-18159-record-block-required-permissions",
"pr": "#19913",
"head_sha": "b19ac16c77e812659a5a98d5740b82876b265ebf. It is aeb6a57 plus three commits, pushed once as a fast-forward with no rebase, amend or force: 62decd7 (merge of origin/main at 6696056, made by scripts/pm/os-regen-merge.sh), a3075b6 (ui.json regenerated on the merged tree) and b19ac16 (the re-measure). ls-remote reads b19ac16 on the branch.",
"session": "session_019c3Hi6ZMU1p6m6aA6Bz45d (a subagent, so this is the dispatching seat's id)",
"identity_check": "The newest Claim: on #18159 is 5819332138 (seat domain:spec#4, session_019c3Hi6ZMU1p6m6aA6Bz45d). It names branch claude/issue-18159-record-block-required-permissions, which is this branch. Read over REST: 24 comments, one page.",
"premise_still_valid": true,
"summary": "origin/main (6696056, which carries #20036 at 0bf85ea) is merged. The docblock at packages/spec/src/ui/component.zod.ts:857-902 (post-merge numbering) is re-measured at .objectui-sha f8a9d0f, and the sha and the anchors changed together: 10 lines replaced, 0 added. All 21 anchors were re-read. At the new pin each cited range holds the same text it held at 62597c5, checked mechanically range by range, so no read point changed meaning or disappeared. Nine anchors get new numbers: record-details.tsx shifted +1 (the new import line
declaredNameField,), and record-related-list.tsx shifted +34 (an import block and the new props-type docblock). The other five cited files did not change between the pins, so their twelve anchors keep their numbers. The merge driver dropped main's 13 DocNavItem keys from authorable-surface/ui.json (it kept the branch side). gen:schema on the merged tree restored them next to this PR's three keys, and nothing else changed. The changesets, the test file and component.mdx are byte-identical to aeb6a57. No other citation of 62597c5 is in the branch's own diff: the remaining ones in packages/spec/src are main's, historical spellings or bare-sha prose, and the gate passes them.","anchors": [
{
"old": "record-details.tsx:234",
"new": "record-details.tsx:235",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "record-details.tsx:223",
"new": "record-details.tsx:224",
"first_line_at_f8a9d0fb0596": "* capability is not object-scoped, and the old
&& objectNameconjunct was"},
{
"old": "record-details.tsx:234-242",
"new": "record-details.tsx:235-243",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "record-details.tsx:186",
"new": "record-details.tsx:187",
"first_line_at_f8a9d0fb0596": "if (!ctx) {"
},
{
"old": "record-related-list.tsx:242",
"new": "record-related-list.tsx:276",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "record-related-list.tsx:229",
"new": "record-related-list.tsx:263",
"first_line_at_f8a9d0fb0596": "* capability is not object-scoped. This site never carried the"
},
{
"old": "record-related-list.tsx:242-250",
"new": "record-related-list.tsx:276-284",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "record-related-list.tsx:184",
"new": "record-related-list.tsx:218",
"first_line_at_f8a9d0fb0596": "if (!objectName) {"
},
{
"old": "record-related-list.tsx:202",
"new": "record-related-list.tsx:236",
"first_line_at_f8a9d0fb0596": "if (perms.isLoaded && !perms.can(objectName, 'read')) {"
},
{
"old": "record-highlights.tsx:93",
"new": "record-highlights.tsx:93 (unchanged)",
"first_line_at_f8a9d0fb0596": "const highlightsAllowed = required.length === 0 || perms.hasCapabilities(required);"
},
{
"old": "record-highlights.tsx:77",
"new": "record-highlights.tsx:77 (unchanged)",
"first_line_at_f8a9d0fb0596": "* capability is not object-scoped, and the old
&& objectNameconjunct was"},
{
"old": "record-highlights.tsx:151-164",
"new": "record-highlights.tsx:151-164 (unchanged)",
"first_line_at_f8a9d0fb0596": "if (!highlightsAllowed) {"
},
{
"old": "record-highlights.tsx:146-149",
"new": "record-highlights.tsx:146-149 (unchanged)",
"first_line_at_f8a9d0fb0596": "useRegisterHighlightFields("
},
{
"old": "record-quick-actions.tsx:263",
"new": "record-quick-actions.tsx:263 (unchanged)",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "record-quick-actions.tsx:252",
"new": "record-quick-actions.tsx:252 (unchanged)",
"first_line_at_f8a9d0fb0596": "* capability is not object-scoped, and the old
&& objectNameguard was a"},
{
"old": "record-quick-actions.tsx:263-271",
"new": "record-quick-actions.tsx:263-271 (unchanged)",
"first_line_at_f8a9d0fb0596": "if (required.length > 0 && !perms.hasCapabilities(required)) {"
},
{
"old": "MePermissionsProvider.tsx:416",
"new": "MePermissionsProvider.tsx:416 (unchanged)",
"first_line_at_f8a9d0fb0596": "return required.every((p) => held.has(p));"
},
{
"old": "MePermissionsProvider.tsx:414",
"new": "MePermissionsProvider.tsx:414 (unchanged)",
"first_line_at_f8a9d0fb0596": "if (!Array.isArray(perms)) return true;"
},
{
"old": "PermissionProvider.tsx:77",
"new": "PermissionProvider.tsx:77 (unchanged)",
"first_line_at_f8a9d0fb0596": "const ALL_CAPABILITIES: PermissionContextValue['hasCapabilities'] = () => true;"
},
{
"old": "usePermissions.ts:45",
"new": "usePermissions.ts:45 (unchanged)",
"first_line_at_f8a9d0fb0596": "hasCapabilities: () => true,"
},
{
"old": ":416 (clause 5 continuation, MePermissionsProvider.tsx)",
"new": ":416 (unchanged)",
"first_line_at_f8a9d0fb0596": "return required.every((p) => held.has(p));"
}
],
"anchor_evidence": "objectui was fetched into a scratch git repo (both pins, depth 1): /tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18159/objectui-pin. The seven cited files at f8a9d0f match the codeload tarball of the same commit byte for byte (cmp, 7 of 7 same). The blob diff across the hop: record-details 6950fd98 to 7e7b06ad, record-related-list 52445e1e to b97d2f7c; highlights f5c7968c, quick-actions c8ea1726, MePermissionsProvider 80fc4e6d, PermissionProvider 899df91f and usePermissions 247907b3 are unchanged. A script compared every old-pin range with its new-pin range and printed identical: True for 21 of 21 (anchors-table.txt in the same scratch dir). Every gate read site in the four renderers is the same at both pins (hasCapabilities at rd:235, rrl:276, rh:93, rqa:263; perms.can only at rrl:236, the related object's read gate).",
"tests": "All runs were on the final tree b19ac16, and every heavy run went through os-verify-lock (slot dev-18159-fixround, VERDICT command-exit 0 on each). (1) check:objectui-pin-citations. Before the fix: exit 1,
packages/spec/src/ui/component.zod.ts:865 cites 62597c588072. After the fix, with OBJECTUI_ROOT set to the scratch repo: --self-test exit 0; the ordinary run exits 0 and prints48 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; 308 file:line anchor(s) seen; --verify-anchors exit 0 (7 asserted, 239 unasserted anchors on the worklist, as before; no assertion was added and ASSERTED_ANCHOR_FLOOR is untouched). The pnpm script with no objectui (the CI shape) exits 0 and reports the 7 as NOT VERIFIED. (2) pnpm --filter @objectstack/spec build: exit 0 (2m31s). check:generated:All 15 generated artifacts are up to date. typecheck (tsc --noEmit + scripts + test-typecheck): exit 0. Full spec suite (vitest --project local --maxWorkers=2):Test Files 535 passed (535),Tests 15727 passed | 2 todo. The PR's own file, verbose: 25 of 25 passed. (3) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 107 commands (change set: 6 paths against merge base 6696056). All 107 ran with exit codes recorded.--rananswers:107 derived famil(ies) accounted for, 107 run, 0 NOT-MEASURED (a DERIVED zero, all 107 recorded an exit code and none of them is 3). Seven first exited 3 (PREREQUISITE NOT MET, no dist): check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt. All seven were re-run to exit 0 after building the @objectstack/lint closure, the @objectstack/client-react closure, thenturbo run build --filter=./packages/* --filter=./packages/*/*(72 of 72 tasks). check:dts-closure and check:sourcemap-no-sources-content were re-run over all 72 and 69 built packages. 106 exit 0. The one exit 1 is node scripts/check-empty-changeset.mjs --base origin/main, the deliberate-correction refusal on .changeset/18159-record-block-field-security-pair.md, red by design and confirmed by the maintainer at aeb6a57 (5823706830). (4) The changesets are byte-identical:git diff --exit-code aeb6a57456 b19ac16c77 -- .changeset/18159-record-block-field-security-pair.md .changeset/18159-record-block-required-permissions.mdexits 0, blobs d502a9a04c6a and 80546d851168 at both heads. (5) pre-push:check:commit-card-trailers: 3 commit message(s) on this push carry no card relation and no model identifier in the trailer pair. (6) CI on b19ac16, one read after the push, no polling: 32 check runs, 11 success, 2 skipped, 18 in_progress, 1 failure (Check Changeset, red by design, not required). Status: in_progress.","gates_not_measured": "NOT MEASURED locally, owned by CI: the 6 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood Regression, Dogfood Verify CLI, Build Core, Build Docs), the 4 type-check lanes (the spec lane is covered by the spec typecheck above), the 56 artifact-roster families and the 11 wide-population families that dispatch-gates lists outside its 107.",
"pr_body": {
"status": "NOT written. The body is prepared as files for the seat to send. This session sent no PATCH.",
"updated_body_file": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18159/pr-body.fixround.md",
"section_only_file": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18159/fixround-section.md",
"issue_patch_actions_file": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18159/pr-body.issue_patch.actions.json",
"dry_run": "node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file ACTIONS_FILE --dry-run: exit 0,
DRY RUN - nothing sent, event_type fleet-write, session session_019c3Hi6ZMU1p6m6aA6Bz45d, 1 action issue_patch #19913, and the body equals the file (8872 bytes). fleet-write/validate.mjs on the packed client_payload: exit 0,OK ... 1 action(s): issue_patch.","shape": "The current body is byte-kept, with one section inserted above the existing footer:
## Fix round 2026-09-25: origin/main merged, objectui pin re-measured(the three commits, what was measured, a 20-row anchor table old to new with the first line at the new pin, the gates). The section has no less-than character and no HTML comment. The existingsubtag and the footer are unchanged."},
"deviations": [
"objectui source: the dispatch suggested a tarball. I fetched both pins with
git fetch --depth=1into a new scratch repo, because the gate's anchor verification opens objectui throughgit -C OBJECTUI_ROOTand needs the commit object. I also downloaded the codeload tarball and matched the seven files against it. /home/user/objectui was never written.","Push once, as the dispatch asked. AGENTS.md and the role file say to push WIP before multi-minute steps. Every commit was made locally before the spec build, and the single push followed verification. I am naming the conflict rather than silently picking a side.",
"The merge commit 62decd7 has os-regen-merge.sh's
git merge --no-editmessage and no trailer pair. It was not amended, per the no-amend rule. The pre-push trailer gate passed on all 3 commits.","Builds beyond the affected package: the lint closure, the client-react closure and every package, all under the lock. They were needed to turn seven exit-3 derived gates into measurements instead of NOT MEASURED.",
"Three
--base origin/maingates ran after a sibling fetch had moved the shared origin/main to 7f1de2e. check-empty-changeset states it diffed from merge base 6696056. 7f1de2e touches none of this PR's files and not .objectui-sha, so the branch was not merged again."],
"observed_state_not_touched": "PR #19913 reads draft=false and auto_merge=null, with labels documentation, size/m, tests, tooling and protocol:ui. Another actor set that state, and I left it as it was. The at-tier PASS 5801959316 names aeb6a57, and the new head carries no review record of its own. Whether the fix round needs a re-review is the seat's call.",
"mcp_calls": "0",
"api_writes": "1: this report, via node scripts/pm/post-stamped.mjs --comment=18159 (fleet-write relay, POST /repos//issues/18159/comments). git push is counted separately and is not REST. REST reads only: GET issues/18159/comments, GET pulls/19913, GET issues/19913/comments, GET commits/b19ac16c77/check-runs.",
"open_questions": [],
"out_of_scope_findings": [
"observation (none of the classes a/b/c): at the new pin, objectui record-related-list.tsx:89-92 (the props-type docblock added since 62597c5) says no block the contract maps onto this tag declares requiredPermissions, and says not to reopen the type to admit it. That sentence goes stale when this PR lands. carrier: the domain:ui seat, through objectstack-ai/objectui#10281 ruled A (5823922380), which already routes the objectui half. Noted, not filed. It moves no anchor: the read at :240-242 and the gate at :276 are unchanged.",
"nit (none of the classes a/b/c): clause 5's bare continuation
:416means MePermissionsProvider.tsx:416 in the prose. The gate's inheritance rule would resolve a content assertion written on it to the nearest preceding full anchor, usePermissions.ts. It is harmless while no assertion sits there. carrier: none. Noted, not filed."],
"cleanup": "Removed /home/user/objectstack-issue-18159/node_modules, then ran git worktree remove on the tree, without --force, after
git status --porcelainread 0 lines. No background process or monitor was started. Every os-verify-lock call ran in the foreground and exited with its VERDICT line. The local branch ref stays at the pushed head. The scratch dir keeps the PR-body files, the objectui scratch repo and the gate logs (gatelogs/, gate-results.tsv, ran-exit.txt)."}
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Landed — PR #19913 →
63a8eb491b, 2026-09-25T04:23Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), holder of this card since the takeover from seat 5 on the maintainer's 「同意你处理」. Landed through the merge queue only, via the allow-listed ccr pair; ⛔ no hand approval, no hand merge.completedthroughFixes #18159, the PR body's only closing keyword.63a8eb491bhas one parent and is an ancestor oforigin/main. Content probe:git patch-id --stableof the squash's own diff equals that of the PR's diff from its merge base to headb19ac16c77, the fix-round head that the at-tier PASS5826453841names.check:objectui-pin-citationsfailed after chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 moved.objectui-shatof8a9d0fb0596. The fix round mergedorigin/mainand re-measured all 21 anchors of therequiredPermissionsdocblock at the new pin. No read point changed meaning; the PR body's fix-round section has the table.5749268463, upheld on [Decision] two rulings point opposite ways onrequiredPermissionsfor the record blocks: the spec declares it (objectstack#18159) vs. the renderer stops reading it (objectui#10200) objectui#10281 as5823922380):requiredPermissionsis declared onrecord:details/record:highlights/record:related_listas the ADR-0066 capability gate, fail-closed. The DELIBERATE CORRECTION of.changeset/18159-record-block-field-security-pair.mdlanded on the maintainer's confirmation5823706830; the changesets are byte-identical to the confirmed head.requiredPermissionsis an input onrecord:quick_actionsonly, and its description says "hide the whole bar" while the renderer shows a notice — the objectui half of objectstack#18159 objectui#10224 is dispatched by thedomain:uiseat once this ships in an installable@objectstack/spec. Its carrier note5826463187adds the one objectui docblock this PR makes stale (record-related-list.tsx:89-92at the pin).pm:dispatchedand the assignee are removed in one label write. Nothing on this card remains in flight.Generated by Claude Code
spec(ui): `record:details` / `record:highlights` / `record:related_li…
feat(spec): declare requiredPermissions on record:details / highlight…
test(spec): the first ui/ file group's test titles state each cited d…