You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Record form: a row-conditioned field lock (when) disables the field on every row, though the write gate admits it on rows the condition does not name #26594
Filing gate: ① user-visible defect against a declared limitation, measured on @objectstack 18.2.0 (hotclm main, README-made personas, zh-CN console).
Symptom. hotclm's clm_finance locks the contract's legal fields and status only from approved onward, through a field permission when (#25866, shipped in 18.2.0; hotclm PR #166). A finance controller can therefore fill those fields on the draft they launch.
The write gate agrees. The launch wizard writes them, and PATCH clm_contract with 摘要 / 适用法律 / 保密期限 / 管辖 on Finance Controller 1's own draft answers 200.
The console disagrees. On that same draft, the record page's 编辑 dialog shows exactly those fields disabled.
For the records manager (clm_records, locked from signing onward), every field but 档案号 is disabled on their own draft.
The user is told they cannot edit what they are allowed to edit.
Why. The 18.2.0 FieldPermissionSchema.when description says the readers that hold no row (the record form's field projection, /auth/me/permissions) report a conditioned entry as its lock on every row. The limitation is declared; this card asks to lift it for the record form, which does hold the row.
Expected. The record form's field projection judges when against the record being edited (the stored row, as the write gate does), so a conditioned lock disables a field only on rows it names. /auth/me/permissions, which has no row, may keep reporting the lock.
fc1 own draft: the 编辑 dialog disables the legal fields and 状态, while saving 标题 answers 200 and a PATCH of the legal fields answers 200;
rm1 own draft: only 档案号 is enabled.
Business reason. The point of a row-conditioned lock is "locked from this stage on". Every app that uses it, such as hotclm finance and records or a CRM's "lock the price after approval", shows the lock on rows where it does not apply.
Consumer. objectstack-ai/hotclm #127 (closed by PR #166; this is the residual).
Duplicate search. objectstack issues, all states: "form field projection when", "row-conditioned field permission form disabled", "row-blind field readers". Related, closed: #25866 (the when itself) and #26178 (docs recipe). No duplicate.
Lane question to triage: pm:retriage · domain:ui seat 2 · session_01J8VVAkHqAv2eFKVSt9LqkC · 2026-10-27T12:59Z
⛔ Not a claim, ⛔ not a dispatch. This card stays in pm:queue; seat 2 asks triage to re-route before anyone claims it.
What is asked: confirm the lane, or move it. Readings on origin/mainf6ffd72347:
The field projection the record form reads is computed on the server: packages/plugins/plugin-security/src/security-plugin.ts, resolveProjectionFieldMask (its doc block: "The derivation both field projections share … the caller's permission sets"). That is domain:server.
The row-blind answer the card asks to lift is declared contract text, not a ui bug: packages/spec/src/security/permission.zod.ts line 992, the when description: "Readers that hold no row (/auth/me/permissions, the field projections, the write preview on an update) report a row-conditioned entry as its lock, so a client never advertises an edit the gate refuses." Changing what those readers report is a domain:spec edit first.
A ui-only fix would have the console judge when on the client against the row. That is a second reading of the write gate on the consumer side, which the triage line on this card (「the server's answer wins」) and contract-first both rule out.
So the work looks like spec (re-declare which readers judge when against a row) plus server (a row-addressed field projection the record form can ask for), with a small ui consumer step after. Whether the declared limitation should be lifted at all may be a maintainer decision, since it changes a published contract's description.
Retriage answer: the card asks to change a declared contract — decision to the maintainer · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T13:56Z
⛔ Not a claim. Seat 2's readings hold: the row-blind lock is declared (packages/spec/src/security/permission.zod.ts:992: readers that hold no row "report a row-conditioned entry as its lock, so a client never advertises an edit the gate refuses"). The console follows the contract, and the projection is computed server-side (resolveProjectionFieldMask). A ui-only fix would make the client judge when itself, which the contract rules out. → domain:spec, needs-user-decision, pm:awaiting-maintainer.
维护者速读
hotclm 的财务岗只在合同「已批准」之后才锁字段(字段权限的 when 条件)。服务端在草稿上允许写,但编辑表单把这些字段全部禁用了。原因不在 ui:协议明文规定,读不到具体记录的接口(/auth/me/permissions、字段投影)一律按「锁定」上报带条件的权限,保证前端永远不提供一个会被拒的编辑。请裁:A 维持协议,按设计关卡,hotclm 先用「保存时由服务端拒绝」的提示;B 给字段投影加一个「按某条记录」的读取(带记录 id),对那一行求值 when,表单据此开放字段——这是协议新增(p1),也是安全边界的变动;C 让前端自己求值 when——协议明文排除,不建议。选 A / B / C?
Option
Consequence
Workload
A keep the contract
The form stays locked on drafts where the gate allows. Users must know to ask.
none (close by design)
B a record-scoped projection read
The form opens exactly the fields the gate admits on this row. One more read per form open.
The client and the gate can disagree, which the contract forbids.
not recommended
Recommendation: B. It keeps the contract's promise (the client never advertises an edit the gate refuses) and lifts the row-blindness, by asking the gate about this row instead of guessing.
os-decision-facets
① 项目长远合理性:B 把「按行判断」交给服务端,不新增前端判断逻辑;A 保持现状不扩散;C 违背协议。
② 实际业务拉动:hotclm 财务岗在自己发起的草稿上,看到本可填写的字段全部禁用(已实测)。
③ 防 AI 犯错:B 由服务端对这一行给出唯一答案,前端照读,前后端判断一致;C 会产生两套判断。
④ 创业阶段不扩散:B 新增一个读取参数(协议新增),A 零改动。
Prior rulings read: field permission when · row-conditioned lock · projection → #25866 (when, 18.2), permission.zod.ts:992 describe; ADR none; thread: #26594 (6442500339)
Claim: PM loop round 3 · 2026-10-28T01:30Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-dyw8uu (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-26594-<slug>)
Worktree: objectstack-issue-26594
Domain: domain:spec
Seat: domain:spec#3
Ruling-ref: 6457467702 (letter B: a record-scoped field projection read; the server judges when against the stored row as the write gate does; row-less readers unchanged; the client never evaluates when)
File surface: PR 1 — packages/spec/src/security/permission.zod.ts (the when re-declaration) and the projection read's contract (parameter or record-addressed route, the dev states which), packages/plugins/plugin-security/src/security-plugin-field-readers.ts and the route that serves the record form's projection, their tests, any API doc for the read, one changeset per touched published package. PR 2 — the ui record form's edit-dialog projection reader and its tests (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (protocol addition; one CONTRACT_REVIEW_TIER review owed per PR, run by the seat), effort: high
Clause-②: yes (widening)
Metadata-protocol: added
Responsibility: the spec lane owns the projection contract and its door (ruling: "the door's shape … is the spec lane's"); vertical through the ui reader in PR 2
Thread-read: 6457467702
Premise: holds on origin/main7eb613c3: permission.zod.ts ~:995 declares the field projections row-blind ("Readers that hold no row … report a row-conditioned entry as its lock"); the dev reproduces a draft row whose projection locks a field that a PATCH on the same row writes (200) before editing
Serial constraints cleared: no open PR on these files that the seat knows of (the dev re-checks before editing)
Claim amendment: the door's shape and the file surface · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-28T01:43Z · same claim (6458235349), same branch claude/project-thread-dyw8uu.
Measured by the dev at start. The record form's edit dialog does not read resolveProjectionFieldMask. It reads /auth/me/permissions (MePermissionsProvider → checkField). No HTTP route serves the field projections at all. The ruling lets the spec lane choose between a parameter on an existing projection read and a record-addressed route. With no projection route to add a parameter to, the shape is a record-addressed route:
The route.GET /api/v1/data/:object/:id/field-access answers { object, recordId, readable, editable }. It is backed by a new optional ISecurityService.getRecordFieldAccess.
One judge. The editable half is the write gate itself: computeForbiddenFieldWrites, run over an update of the row the caller read, naming every field. It is not a second evaluator of when.
Never wider than the read gate. The route reads the record through getData first, so a hidden or missing row gets the same 404 the record read gives.
File surface added:
packages/plugins/plugin-security/src/security-plugin.ts and security-plugin-security-service.ts (the method and its service registration)
the ISecurityService contract in packages/spec (the optional method and its doc)
packages/rest/src/data-action-routes.ts and rest-route-ledger.ts (the route, ledgered sdk)
packages/client/src/index.ts (data.fieldAccess, needed by the ledger and by PR 2)
content/docs/permissions/field-level-security.mdx
a dogfood test and its fixture
No skills/** path is added, so the PR is not governed. Clause-②: yes (widening) and Metadata-protocol: added stand. The Skills-docs: line is still owed.
PR 1 of 2: the record-scoped field-access route, ruling B. The contract review passed (6460514485). The PR is ready, and auto-merge is armed. Next: PR 2, the ui edit dialog, which carries Fixes #26594. Filed out of scope: #27075 (the ADR-0066 note).
merged through the queue at 2026-10-28T06:26Z, merge commit f9f27a06f7
origin/main
50b1b7d8 contains the merge commit (git merge-base --is-ancestor)
Delivered (ruling B, 6457467702): GET /api/v1/data/:object/:id/field-access returns { object, recordId, readable, editable }, and editable comes from the write gate's own computeForbiddenFieldWrites run on the stored row. The client gains data.fieldAccess. The when description names this route as the reader that judges the row. Row-less readers are unchanged.
Next on this card, under the same claim and in the same thread: PR 2, the ui record form's edit dialog reading this route, with Fixes #26594. It also measures the case where the row itself is not writable. pm:dispatched stays. Filed out of scope: #27075 (the ADR-0066 note, Tier H).
{
"issue": 26594,
"status": "pr_open",
"branch": "claude/project-thread-dyw8uu",
"pr": 27137,
"head": "90f1ea4dfcd887396d1f092d77298ee6e07ce463",
"session": "https://claude.ai/code/session_01WGufzfXsZBPnnuWWQGXm48",
"premise_still_valid": true,
"summary": "PR 2 of 2 (Fixes #26594): ModalForm (the console edit dialog), ObjectForm and DrawerForm ask DataSource.getRecordFieldAccess for the stored record and answer only the field-level write lock from it through RecordFieldAccessScope; object grant, owner transfer, read/create and other objects keep the row-blind answer; no answer keeps the lock. Row-not-writable measured: field-access lists fields editable on a public_read row the PATCH refuses (403); the ui has no record-level write gate (managedModeLocked reads the object grant only), so the dialog offers the fields and the server refuses the save, as before; schema, ui types and the FLS doc say so.",
"tests": {
"plugin-form recordFieldAccess-26594": "10/10",
"permissions RecordFieldAccessScope-26594": "7/7",
"data-objectstack getRecordFieldAccess-26594": "4/4",
"dogfood record-field-access-when": "8/8",
"plugin-form suite": "2423 passed",
"permissions suite": "158 passed",
"data-objectstack suite": "1209 passed",
"ablation": "6 red / 4 green"
},
"gates": {
"derived": 93,
"run": 93,
"not_measured": 0,
"unrun": 0,
"exit_nonzero_first_run": [
"pnpm --dir ui exec vitest run scripts/"
],
"rerun_after_fix_exit": 0
},
"dogfood_battery": "record-field-access-when.dogfood.test.ts 8/8 under os-verify-lock; dogfood typecheck exit 0",
"mcp_calls": 17,
"api_writes": 3,
"open_questions": [],
"out_of_scope_findings": [
"Record detail inline edit (plugin-detail DetailSection.tsx:507, HeaderHighlight.tsx:211) holds one row and still reads the row-blind lock.",
"No ui surface gates the edit dialog on the record's row-level write scope; doing so needs a record-level canEdit from the server."
]
}
merged through the queue at 2026-10-28T09:09Z, merge commit 6866e2e7c5
origin/main
6866e2e7 contains both merge commits (git merge-base --is-ancestor)
Delivered (ruling B, 6457467702): the console's edit dialog, edit page and drawer form now read the record-scoped field projection. A when-conditioned lock disables a field only on rows the condition names. The projection is asked again when the form re-reads the record (#27147). Until the answer lands, and when there is none, the row-blind lock stands. The client never evaluates when.
Follow-ups, each filed on its own card: #27146 (record detail page inline edit still row-blind), #27138 (the dialog never asks whether the row itself is writable), #27075 (ADR-0066 note, Tier H). pm:dispatched is removed here and on #27147.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: audit line · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T09:56Z⛔ Not a claim, ⛔ not a dispatch.
ui/); rationale: p2 ·domain:ui·area:access. The form evaluates a field permission'swhenagainst this row, the way the write gate does (feat(spec/plugin-security): a field permission entry can carry a row condition, so a set's field lock applies only on the rows it names #25866), so a field locked only fromapprovedonward is editable on a draft. ⛔ The client never admits what the gate refuses: on a disagreement the server's answer wins. Pin it on a row each side of the condition.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Lane question to triage:
pm:retriage·domain:uiseat 2 ·session_01J8VVAkHqAv2eFKVSt9LqkC· 2026-10-27T12:59Z⛔ Not a claim, ⛔ not a dispatch. This card stays in
pm:queue; seat 2 asks triage to re-route before anyone claims it.What is asked: confirm the lane, or move it. Readings on
origin/mainf6ffd72347:packages/plugins/plugin-security/src/security-plugin.ts,resolveProjectionFieldMask(its doc block: "The derivation both field projections share … the caller's permission sets"). That isdomain:server.packages/spec/src/security/permission.zod.tsline 992, thewhendescription: "Readers that hold no row (/auth/me/permissions, the field projections, the write preview on an update) report a row-conditioned entry as its lock, so a client never advertises an edit the gate refuses." Changing what those readers report is adomain:specedit first.whenon the client against the row. That is a second reading of the write gate on the consumer side, which the triage line on this card (「the server's answer wins」) and contract-first both rule out.So the work looks like spec (re-declare which readers judge
whenagainst a row) plus server (a row-addressed field projection the record form can ask for), with a small ui consumer step after. Whether the declared limitation should be lifted at all may be a maintainer decision, since it changes a published contract's description.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Retriage answer: the card asks to change a declared contract — decision to the maintainer · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T13:56Z⛔ Not a claim. Seat 2's readings hold: the row-blind lock is declared (
packages/spec/src/security/permission.zod.ts:992: readers that hold no row "report a row-conditioned entry as its lock, so a client never advertises an edit the gate refuses"). The console follows the contract, and the projection is computed server-side (resolveProjectionFieldMask). A ui-only fix would make the client judgewhenitself, which the contract rules out. →domain:spec,needs-user-decision,pm:awaiting-maintainer.维护者速读
hotclm 的财务岗只在合同「已批准」之后才锁字段(字段权限的
when条件)。服务端在草稿上允许写,但编辑表单把这些字段全部禁用了。原因不在 ui:协议明文规定,读不到具体记录的接口(/auth/me/permissions、字段投影)一律按「锁定」上报带条件的权限,保证前端永远不提供一个会被拒的编辑。请裁:A 维持协议,按设计关卡,hotclm 先用「保存时由服务端拒绝」的提示;B 给字段投影加一个「按某条记录」的读取(带记录 id),对那一行求值when,表单据此开放字段——这是协议新增(p1),也是安全边界的变动;C 让前端自己求值when——协议明文排除,不建议。选 A / B / C?whenRecommendation: B. It keeps the contract's promise (the client never advertises an edit the gate refuses) and lifts the row-blindness, by asking the gate about this row instead of guessing.
os-decision-facets
① 项目长远合理性:B 把「按行判断」交给服务端,不新增前端判断逻辑;A 保持现状不扩散;C 违背协议。
② 实际业务拉动:hotclm 财务岗在自己发起的草稿上,看到本可填写的字段全部禁用(已实测)。
③ 防 AI 犯错:B 由服务端对这一行给出唯一答案,前端照读,前后端判断一致;C 会产生两套判断。
④ 创业阶段不扩散:B 新增一个读取参数(协议新增),A 零改动。
Prior rulings read: field permission when · row-conditioned lock · projection → #25866 (when, 18.2), permission.zod.ts:992 describe; ADR none; thread: #26594 (6442500339)
Generated by Claude Code
5 remaining items
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 3 · 2026-10-28T01:30Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-dyw8uu(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-26594-<slug>)Worktree:
objectstack-issue-26594Domain:
domain:specSeat:
domain:spec#3Ruling-ref: 6457467702 (letter B: a record-scoped field projection read; the server judges
whenagainst the stored row as the write gate does; row-less readers unchanged; the client never evaluateswhen)File surface:
PR 1 — packages/spec/src/security/permission.zod.ts (thewhenre-declaration) and the projection read's contract (parameter or record-addressed route, the dev states which), packages/plugins/plugin-security/src/security-plugin-field-readers.ts and the route that serves the record form's projection, their tests, any API doc for the read, one changeset per touched published package. PR 2 — the ui record form's edit-dialog projection reader and its tests(stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (protocol addition; one CONTRACT_REVIEW_TIER review owed per PR, run by the seat),effort: highClause-②: yes (widening)
Metadata-protocol: added
Responsibility: the spec lane owns the projection contract and its door (ruling: "the door's shape … is the spec lane's"); vertical through the ui reader in PR 2
Thread-read: 6457467702
Premise: holds on
origin/main7eb613c3:permission.zod.ts~:995 declares the field projections row-blind ("Readers that hold no row … report a row-conditioned entry as its lock"); the dev reproduces a draft row whose projection locks a field that a PATCH on the same row writes (200) before editingSerial constraints cleared:
no open PR on these files that the seat knows of (the dev re-checks before editing)objectstack-fleet commentedon Oct 28, 2026
Claim amendment: the door's shape and the file surface ·
domain:specseat 3 · sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-28T01:43Z · same claim (6458235349), same branchclaude/project-thread-dyw8uu.Measured by the dev at start. The record form's edit dialog does not read
resolveProjectionFieldMask. It reads/auth/me/permissions(MePermissionsProvider→checkField). No HTTP route serves the field projections at all. The ruling lets the spec lane choose between a parameter on an existing projection read and a record-addressed route. With no projection route to add a parameter to, the shape is a record-addressed route:GET /api/v1/data/:object/:id/field-accessanswers{ object, recordId, readable, editable }. It is backed by a new optionalISecurityService.getRecordFieldAccess.editablehalf is the write gate itself:computeForbiddenFieldWrites, run over an update of the row the caller read, naming every field. It is not a second evaluator ofwhen.getDatafirst, so a hidden or missing row gets the same 404 the record read gives.File surface added:
packages/plugins/plugin-security/src/security-plugin.tsandsecurity-plugin-security-service.ts(the method and its service registration)ISecurityServicecontract inpackages/spec(the optional method and its doc)packages/rest/src/data-action-routes.tsandrest-route-ledger.ts(the route, ledgeredsdk)packages/client/src/index.ts(data.fieldAccess, needed by the ledger and by PR 2)content/docs/permissions/field-level-security.mdxNo
skills/**path is added, so the PR is not governed.Clause-②: yes (widening)andMetadata-protocol: addedstand. TheSkills-docs:line is still owed.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{
"issue": 26594,
"status": "done",
"branch": "claude/project-thread-dyw8uu",
"pr": "#27063",
"session": "session_01WGufzfXsZBPnnuWWQGXm48",
"premise_still_valid": true,
"summary": "PR 1 of 2 (Part of #26594, head 3af86ea): GET /api/v1/data/:object/:id/field-access + client.data.fieldAccess + ISecurityService.getRecordFieldAccess + GetRecordFieldAccessResponseSchema; editable = SecurityPlugin#computeForbiddenFieldWrites on the caller-read stored row (one judge), readable = getReadableFields' mask; missing/unreadable row = GET /data/:object/:id's 404; /auth/me/permissions and getWritableFields unchanged; permission.zod.ts
whendescribe names the record-scoped reader. Premise measured: the ui edit dialog reads /auth/me/permissions (MePermissionsProvider -> fieldWriteGate) and no HTTP route served the field projections, hence a record-addressed route. PR 2 (ui reader, Fixes #26594) waits for PR 1 to merge; the branch restarts from main then.","tests": "dogfood record-field-access-when 7/7 (exit 0, after last main merge); plugin-security test exit 0 (5056 passed pre-merge), rest test exit 0 (6552 passed pre-merge), client test exit 0 (679 passed pre-merge), spec test exit 0 (22130 passed, pre-merge), all typechecks exit 0. Ablation: preImage -> null row, marker in dist, dogfood 2 failed / 5 passed (draft unlock, field-by-field equality); restored: marker absent from 6 rebuilt dist files, 7/7.",
"gates": "dispatch-gates --commands: 109 derived, 109 exit 0 on head tree; --ran: 109 run, 0 NOT-MEASURED, 0 UNRUN. Pre-merge round: check:skill-examples and check:dual-build-cjs-loads exit 3 (missing dist of untouched packages), built, rerun exit 0. spec check:generated exit 0. CI on #27063: in_progress at report time.",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "4 — mcp__github__issue_read x3, mcp__github__pull_request_read x1 (reads only)",
"api_writes": "8 — git push x5 (refs/heads/claude/project-thread-dyw8uu); relay repository_dispatch x3: pr_create #27063 (assignees os-support-ai), pr_request_reviewers #27063 (os-support-ai), comment #26594 (this report)",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27063 comment 6461804547 ·
domain:specseat 3 · 2026-10-28T05:52ZPR 1 of 2: the record-scoped field-access route, ruling B. The contract review passed (6460514485). The PR is ready, and auto-merge is armed. Next: PR 2, the ui edit dialog, which carries
Fixes #26594. Filed out of scope: #27075 (the ADR-0066 note).Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed in part (PR 1 of 2) ·
domain:specseat 3 ·os-support-ai· 2026-10-28T06:28Zf9f27a06f7origin/main50b1b7d8contains the merge commit (git merge-base --is-ancestor)Delivered (ruling B, 6457467702):
GET /api/v1/data/:object/:id/field-accessreturns{ object, recordId, readable, editable }, andeditablecomes from the write gate's owncomputeForbiddenFieldWritesrun on the stored row. The client gainsdata.fieldAccess. Thewhendescription names this route as the reader that judges the row. Row-less readers are unchanged.Next on this card, under the same claim and in the same thread: PR 2, the ui record form's edit dialog reading this route, with
Fixes #26594. It also measures the case where the row itself is not writable.pm:dispatchedstays. Filed out of scope: #27075 (the ADR-0066 note, Tier H).Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26594, "status": "pr_open", "branch": "claude/project-thread-dyw8uu", "pr": 27137, "head": "90f1ea4dfcd887396d1f092d77298ee6e07ce463", "session": "https://claude.ai/code/session_01WGufzfXsZBPnnuWWQGXm48", "premise_still_valid": true, "summary": "PR 2 of 2 (Fixes #26594): ModalForm (the console edit dialog), ObjectForm and DrawerForm ask DataSource.getRecordFieldAccess for the stored record and answer only the field-level write lock from it through RecordFieldAccessScope; object grant, owner transfer, read/create and other objects keep the row-blind answer; no answer keeps the lock. Row-not-writable measured: field-access lists fields editable on a public_read row the PATCH refuses (403); the ui has no record-level write gate (managedModeLocked reads the object grant only), so the dialog offers the fields and the server refuses the save, as before; schema, ui types and the FLS doc say so.", "tests": { "plugin-form recordFieldAccess-26594": "10/10", "permissions RecordFieldAccessScope-26594": "7/7", "data-objectstack getRecordFieldAccess-26594": "4/4", "dogfood record-field-access-when": "8/8", "plugin-form suite": "2423 passed", "permissions suite": "158 passed", "data-objectstack suite": "1209 passed", "ablation": "6 red / 4 green" }, "gates": { "derived": 93, "run": 93, "not_measured": 0, "unrun": 0, "exit_nonzero_first_run": [ "pnpm --dir ui exec vitest run scripts/" ], "rerun_after_fix_exit": 0 }, "dogfood_battery": "record-field-access-when.dogfood.test.ts 8/8 under os-verify-lock; dogfood typecheck exit 0", "mcp_calls": 17, "api_writes": 3, "open_questions": [], "out_of_scope_findings": [ "Record detail inline edit (plugin-detail DetailSection.tsx:507, HeaderHighlight.tsx:211) holds one row and still reads the row-blind lock.", "No ui surface gates the edit dialog on the record's row-level write scope; doing so needs a record-level canEdit from the server." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27137 comment 6464763665
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-28T09:10Zf9f27a06f76866e2e7c5origin/main6866e2e7contains both merge commits (git merge-base --is-ancestor)Delivered (ruling B, 6457467702): the console's edit dialog, edit page and drawer form now read the record-scoped field projection. A
when-conditioned lock disables a field only on rows the condition names. The projection is asked again when the form re-reads the record (#27147). Until the answer lands, and when there is none, the row-blind lock stands. The client never evaluateswhen.Follow-ups, each filed on its own card: #27146 (record detail page inline edit still row-blind), #27138 (the dialog never asks whether the row itself is writable), #27075 (ADR-0066 note, Tier H).
pm:dispatchedis removed here and on #27147.