Repository navigation
field write grants in the console: in-place edit on the record body and list inline edit ignore the caller's field editable, and the owner field is offered without the transfer grant #12103
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guards
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsUnlock scan: PR #12094 merged (
5af42dbb).pm:blocked→pm:queueTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T20:53Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: none
- PR fix(console): five write affordances that read no grant now read the affordance-to-grant map, and a write census holds every write call site to a row (objectui#12082) #12094 landed the three new rows in
affordanceGrants.tsand theWRITE_SITEScensus (Part of #12082). - This card's field-level rows (in-place edit, list inline edit, the owner field's transfer grant) join the same map and census.
- console (17.7.0): a create form disables every field for a user whose grant is allowCreate without allowEdit — a create-only audience cannot fill the form it is allowed to submit #12082 is back in the queue for its census backlog, and it also edits
affordanceGrants.ts. ⛔ The two serialize: whichever is dispatched second waits for the first to land. - First act unchanged: measure
/auth/me/permissionsfor the field. A server-side disagreement returns the card to triage for a split.
- PR fix(console): five write affordances that read no grant now read the affordance-to-grant map, and a write census holds every write call site to a row (objectui#12082) #12094 landed the three new rows in
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-11T02:20Z
Session:session_01AswpQDLCKiZos2jCXknwKz
Account:os-zhuang
Branch:claude/issue-12103-field-write-grants
Worktree:objectui-issue-12103
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/core/src/utils/affordanceGrants.ts(the field-level rows:recordEditin-place edit,listInlineEdit, and the owner field's transfer question);packages/plugin-detail/src/DetailSection.tsx(in-place edit on the record body); the list inline-edit readers oflistInlineEdit(named in the PR);packages/permissions/src/(MePermissionsProviderand thePermissionContexttype, only if the transfer grant or a field question must be read there); the census pinpackages/plugin-form/src/affordanceGrantMap-12082.test.tsx; their tests;.changeset/12103-field-write-grants.md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: yes
Responsibility:objectui's plugin-detail DetailSection in-place edit and the listInlineEdit row of AFFORDANCE_GRANTS, which ask no field write question, and the console, which reads no allowTransfer, so the owner field is offered without the transfer grant | ObjectForm layouts already ask checkField('write') through fieldWriteGate; the server refuses the write with 403 | a caller whose field grant is editable:false, or who lacks the transfer grant; measured on objectstack-ai/hotcrm#2058 (17.7.0): a rep gets an editable annual_revenue input and Save answers 403
Thread-read: 6102073458
Serial constraints cleared:affordanceGrants.tsis free. Read at 2026-10-11T02:20Z: objectui#12082 is the only other card on it (triage6102063887and6102073458: the two serialize). This card is dispatched first, so objectui#12082 waits for this PR to land, and that wait is recorded on it in this round. The census pinaffordanceGrantMap-12082.test.tsxis also edited by the in-flight epic PR objectui#12089 (three newWRITE_SITESentries for the Setup assignment sites). The two touch different entries, and whichever lands second mergesmainand keeps both sets.Why
Clause-②: yes:AFFORDANCE_GRANTSis exported from@object-ui/core's entry, and field questions or a transfer row widen that published value. A transfer question oncheckField's action union would widen it too.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12103,
"status": "done",
"branch": "claude/issue-12103-field-write-grants",
"pr": "#12117",
"session": "session_01AswpQDLCKiZos2jCXknwKz",
"premise_still_valid": true,
"summary": "Measured first: as a contributor-position user on objectstack main bf515e72 (app-showcase, --fresh, private port, own read-only worktree), GET /api/v1/auth/me/permissions reports fields[showcase_project.budget] = {readable:true, editable:false} and objects.showcase_project.allowTransfer = false; PATCH budget and PATCH owner_id both answer 403 (Field write denied / requires the transfer grant), a PATCH of name answers 200, and the admin (allowTransfer:true) owner PATCH answers 200, so the server's answer agrees with enforcement and the stop rule did not fire. AFFORDANCE_GRANTS.recordEdit and .listInlineEdit gain field:'write'; DetailSection (details body), HeaderHighlight (highlights strip, same edit session) and ObjectGrid's column pass ask resolveFieldAffordance of their row, the same predicate fieldWriteGate's edit mode asks (pinned). The owner field's transfer grant lives in the resolver: MePermissionsProvider.checkField(o, SystemFieldName.OWNER_ID, 'write') also requires objectPermissionGrants(objPerm,'allowTransfer') from @objectstack/spec/security (allowTransfer or modifyAllRecords), because the server's guard keys the owner by that name and the write question is documented as the server's update rule; 'create' is not narrowed. Draft PR #12117 is open with assignee os-zhuang; CI is in progress.",
"tests": "Head 47a8eee. New pins (real MePermissionsProvider, measured envelope): pnpm exec vitest run packages/plugin-form/src/affordanceGrantMap-12082.test.tsx packages/permissions/src/tests/MePermissionsProvider.ownerTransfer-12103.test.tsx packages/plugin-detail/src/tests/DetailSection.fieldWriteGrant-12103.test.tsx packages/plugin-grid/src/tests/inlineEditFieldWriteGrant-12103.test.tsx -> 'Test Files 4 passed (4) / Tests 66 passed (66)'. Union narrowed at 2ff9d43 (later commit is a test-file typing fix, re-run in the 4-file run above): batch 1 (grid inline-edit/permission/FLS/column suites, plugin-form map + field-gate pins, packages/permissions/) 'Test Files 35 passed (35) / Tests 381 passed (381)'; batch 2 (ListView permissions/inline/FLS, app-shell ObjectView.objectBoundActions-7234 + RecordDetailView.expandFls-7230, all DetailSection./HeaderHighlight./RecordHighlightsRenderer.*, DetailView.permissions, InlineEditSaveBar) 'Test Files 31 passed (31) / Tests 240 passed (240)'. Whole packages/permissions/ + packages/plugin-detail/ at bccbfc5: 'Test Files 262 passed | 1 skipped (263) / Tests 2576 passed | 8 skipped'. NOT MEASURED: whole packages/plugin-grid/ suite, reason: one run exceeded the foreground cap (timeout 590s, exit 124, no result) - declared to CI. Type-check exit 0 with the script name echoed: @object-ui/core, permissions, plugin-detail, plugin-grid, plugin-form (each tsconfig.test.json --listFiles includes its new test). Ablations (committed first, node /home/user/objectstack/scripts/ablation-replace.mjs wrap mode, every leg 'ok mutation landed: anchor 1 -> 0' and 'ok restored: blob == HEAD ... git diff HEAD is empty'; vitest aliases these packages to src so no rebuild): DetailSection gate -> true: 3 failed | 6 passed (refused field, owner, edit mode red; strip pins + controls green); HeaderHighlight gate -> true: 3 failed | 7 passed (control green); ObjectGrid field question removed: 2 failed | 3 passed; provider owner rule disabled: 6 failed | 59 passed across 4 files. Direction observed: red in every leg.",
"mcp_calls": "0 - no MCP GitHub tool was called",
"api_writes": "2 - two fleet-write relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12117/assignees [os-zhuang], read-back identical 12740/12740 bytes; (2) comment -> POST /repos//issues/12103/comments (this os-dev-report). Zero label writes. git push is not REST and is not counted.",
"open_questions": [
{
"question": "The owner field's transfer grant is answered inside the resolver (checkField 'write' for SystemFieldName.OWNER_ID) rather than as a separate AFFORDANCE_GRANTS row, which the dispatch's suggested route named. Keep it there?",
"options": [
"A: keep it in MePermissionsProvider.checkField (one answer for every 'write' asker: edit forms, in-place edit, in-cell edit; no row to forget; no PermissionAction widening)",
"B: add a map row with grant 'transfer' and have resolveFieldAffordance compose it (visible in the table, but needs a 'transfer' verb the spec's PermissionAction does not have and a can() mapping in every provider)"
],
"recommendation": "A, because the server rule is per field, not per affordance, and the write question is already documented as the server's whole update rule; B would widen PermissionAction past the spec or pass an undeclared verb through can()."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) - check:spec-symbols reads a dotted citation of OWNER_ID on SystemFieldName beside a spec mention as a key the spec does not declare: the spec exports SystemFieldName as both a const and a value-union type, and memberSetOfSymbol fillsauthoredfrom the declared type's members only, so the const's keys never count. Read of the gate's code, no public door; worked around by rewording one comment. Dedupe words: spec-symbols, memberSetOfSymbol, const type merge, SystemFieldName",
"carrier: none (承接者:无) - noted, not filed: edit forms strip owner_id by name (SERVER_OWNED_FIELD_NAMES in plugin-form sanitize.ts), so a transfer-holder whose form layout draws the owner gets an enabled input whose change Save silently drops; read-only inference, not reproduced here. Dedupe words: owner_id sanitize, SERVER_OWNED_FIELD_NAMES, transfer-holder edit form",
"carrier: none (承接者:无) - observation: the role-based PermissionProvider has no transfer concept, so its checkField is unchanged; the console mounts MePermissionsProvider"
],
"gates": {
"head": "47a8eee",
"results": [
{
"cmd": "pnpm check:metadata-write-doors",
"exit": 0,
"verdict": "OK 17 metadata write door(s) derived"
},
{
"cmd": "pnpm check:handler-key-reads",
"exit": 0,
"verdict": "OK 152 arm(s), 184 registration(s)"
},
{
"cmd": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"cmd": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK"
},
{
"cmd": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "10 source file(s) of 5 released package(s) changed, and this change declares 1 changeset(s): .changeset/12103-field-write-grants.md"
},
{
"cmd": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"verdict": "No changeset declares amajorbump."
},
{
"cmd": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"cmd": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "exit 0"
},
{
"cmd": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"cmd": "pnpm check:vi-mock-specifiers",
"exit": 0,
"verdict": "check-vi-mock-specifiers: OK"
},
{
"cmd": "pnpm check:vi-mock-inherit",
"exit": 0,
"verdict": "check-vi-mock-inherit: OK"
},
{
"cmd": "pnpm check:vi-mock-override-shape",
"exit": 0,
"verdict": "check-vi-mock-override-shape: OK"
},
{
"cmd": "pnpm check:esm-specifiers",
"exit": 0,
"verdict": "--specifiers-only (load leg not run)"
},
{
"cmd": "pnpm check:self-import",
"exit": 0,
"verdict": "No package names itself inside its own src/."
},
{
"cmd": "pnpm check:phantom-deps",
"exit": 0,
"verdict": "Every in-scope import is declared by the package that publishes it."
},
{
"cmd": "pnpm check:unused-deps",
"exit": 0,
"verdict": "Every gated declaration has a consumer in the package that declares it."
},
{
"cmd": "pnpm check:spec-symbols",
"exit": 0,
"verdict": "spec member citations: ... nothing cites a key its spec symbol does not declare."
},
{
"cmd": "pnpm check:installed-pin-claims",
"exit": 0,
"verdict": "OK"
},
{
"cmd": "pnpm check:lockfile-integrity",
"exit": 0,
"verdict": "VERDICT clean"
},
{
"cmd": "pnpm check:lockfile-dedupe",
"exit": 0,
"verdict": "VERDICT deduped"
},
{
"cmd": "pnpm check:pre-install-import-graph",
"exit": 0,
"verdict": "check-pre-install-import-graph: OK"
},
{
"cmd": "pnpm check:side-effects-array",
"exit": 0,
"verdict": "sideEffects names exactly the module(s) that register at load time"
},
{
"cmd": "pnpm check:unreferenced-sources",
"exit": 0,
"verdict": "OK Every shipped source file in every covered package is reachable."
},
{
"cmd": "pnpm check:doc-fences",
"exit": 0,
"verdict": "every TypeScript block ... is fenced"
},
{
"cmd": "pnpm check:doc-types",
"exit": 0,
"verdict": "Every documented component type is registered."
},
{
"cmd": "pnpm check:eager-closure",
"exit": 0,
"verdict": "Console eager closure is 3170.4 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 34.2 KB). Delta base 5330afd -> 2ff9d43: 3,246,298 -> 3,246,484 bytes gzipped (+186), chunks 290 -> 290, vendor-objectstack 1561.8 KB unchanged; build at 2ff9d43, the later commit is a test file only"
},
{
"cmd": "pnpm check:readme-exports",
"exit": 1,
"verdict": "NOT MEASURED: prerequisite whole-tree build ('the population COLLAPSED -- this run proves nothing'); README edits add no fenced block"
},
{
"cmd": "pnpm check:doc-snippets",
"exit": 2,
"verdict": "NOT MEASURED: prerequisite whole-tree build ([unbuilt-package])"
},
{
"cmd": "pnpm check:spec-floors",
"exit": 1,
"verdict": "NOT MEASURED for unbuilt packages: all 14 findings are [no-artifact]; built @object-ui/core and @object-ui/permissions raised none, 17.7.0 ./security and ./system read"
},
{
"cmd": "pnpm --filter @object-ui/{core,permissions,plugin-detail,plugin-grid,plugin-form} type-check",
"exit": 0,
"verdict": "0 error TS each"
},
{
"cmd": "pnpm exec eslint --format json (10 touched .ts/.tsx files)",
"exit": 0,
"verdict": "10 files, 0 errors with inline config; --no-inline-config: 1 error react-hooks/static-components in ObjectGrid.tsx, identical at base; per-file warning counts identical to base; no type-aware linting in eslint.config.js"
}
],
"ci": "in_progress (PR opened 2026-10-11T03:47Z; not awaited per contract)"
},
"line_budget": "n/a (skills/** not touched)",
"deviations": [
"File surface beyond the claim (claim says stop on breach): packages/plugin-detail/src/HeaderHighlight.tsx (the highlights strip is the same in-place edit session and Save as DetailSection; bounded-fix exemption, all four conditions hold, no open PR touches it), packages/permissions/package.json + pnpm-lock.yaml (+3 lines, direct @objectstack/spec ^17.7.0 for the spec predicate and constant), packages/core/README.md + packages/permissions/README.md (AGENTS.md commandment 2). The seat may amend the claim's file surface.",
"PM mechanism assumption 3 falsified: the server's transfer guard keys the owner field by name (owner_id, the spec's SystemFieldName.OWNER_ID), not by a field type or the object's ownership declaration, so the console identifies it by that spec constant; no literal is hard-coded.",
"PM suggested route not taken for the transfer question: answered in MePermissionsProvider.checkField 'write' instead of a map row (see open_questions).",
"Measured on objectstack main bf515e72 app-showcase (showcase_project.budget, contributor position), not on HotCRM 17.7.0 crm_account.annual_revenue; same permission shape.",
"Whole plugin-grid suite NOT MEASURED (foreground cap); narrowed grid set run, CI runs the rest.",
"Commit trailers use the model-free pair from AGENTS.md, not the harness's model-named Co-Authored-By.",
"One comment and two prose lines reworded so check:spec-symbols does not misread the SystemFieldName citation (gate defect noted in out_of_scope_findings)."
],
"files_changed": [
".changeset/12103-field-write-grants.md",
"packages/core/README.md",
"packages/core/src/utils/affordanceGrants.ts",
"packages/permissions/README.md",
"packages/permissions/package.json",
"packages/permissions/src/MePermissionsProvider.tsx",
"packages/permissions/src/PermissionContext.ts",
"packages/permissions/src/tests/MePermissionsProvider.ownerTransfer-12103.test.tsx",
"packages/plugin-detail/src/DetailSection.tsx",
"packages/plugin-detail/src/HeaderHighlight.tsx",
"packages/plugin-detail/src/tests/DetailSection.fieldWriteGrant-12103.test.tsx",
"packages/plugin-form/src/affordanceGrantMap-12082.test.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/inlineEditFieldWriteGrant-12103.test.tsx",
"pnpm-lock.yaml"
],
"clause2_inventory": [
"AFFORDANCE_GRANTS.recordEdit gains field: 'write' (no row added or removed)",
"AFFORDANCE_GRANTS.listInlineEdit gains field: 'write'",
"FieldAffordance (derived type) widens by 'recordEdit' | 'listInlineEdit'",
"MePermissionsResponse objects entry gains allowTransfer?: boolean",
"checkField action union unchanged ('read' | 'write' | 'create'); its 'write' answer narrows for the owner field only",
"@object-ui/permissions gains a runtime dependency on @objectstack/spec ^17.7.0"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT — PR objectui#12117 (head
47a8eee5ec), with the claim amendment ·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T04:03ZReviewed against GitHub and the diff, not the report (
6105171057).Claim amendment (to claim
6104566954)- File surface, extended:
packages/plugin-detail/src/HeaderHighlight.tsx: the highlights strip shares the details body's edit session and Save, so it is the card's own defect on the same page, not a neighbour.packages/plugin-grid/src/ObjectGrid.tsx: thelistInlineEditreader the claim said would be named in the PR.packages/permissions/package.jsonandpnpm-lock.yaml: a direct@objectstack/spec ^17.7.0, the same range@object-ui/typesand@object-ui/plugin-formdeclare.packages/core/README.mdandpackages/permissions/README.md(objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2).
- The dev's open question (where the owner's transfer grant is read): the seat answers A, as implemented. The rule stays in
MePermissionsProvider.checkField(…, 'write')forSystemFieldName.OWNER_ID, through the spec'sobjectPermissionGrants. The server keys the transfer rule per field, by that name, as part of its update rule, so everywriteasker inherits it with no row to forget. B would pass atransferverb the spec'sPermissionActiondoes not declare (objectui AGENTS.md #0.1). This is a placement question inside the card's scope; existing rules decide it, so it is not escalated.
Verdict
- Shape:
- The PR is a draft on
main. Its first line isFixes #12103, andClause-②: yessits at the start of a line. - objectui#12082 and feat(app-shell,fields,console): Setup's positions and permission sets read the registry, through the metadata-admin pages' environment scope (part of objectui#7611) #12089 are mentioned without a closing keyword. The assignee is
os-zhuang. - 15 files, +721/−15. Nothing touches
content/docs/releases/. NOT GOVERNED.
- The PR is a draft on
- Measured first, as the card's stop rule requires. On objectstack
mainbf515e72, a contributor-position caller's/me/permissionsanswerseditable: falsefor the field andallowTransfer: false, and both PATCHes answer 403 (the controls answer 200). The server agrees with its enforcement, so no half returns to triage. - Against the card's "Done when":
- In-place edit (
DetailSection,HeaderHighlight) and list in-cell edit (ObjectGrid's column pass) askresolveFieldAffordanceof therecordEdit/listInlineEditrows. Those rows now carryfield: 'write', the predicate an edit form'sfieldWriteGateasks. - The owner field honours
allowTransfer(ormodifyAllRecords).createis not narrowed. - The object-level verdicts are unchanged, because
resolveAffordancereads nofield. So5330afd's inline-edit default composes as before. - Pins cover a refused field in both surfaces, the owner without the grant, and the CONTROL editable field. Four ablations went red, and their restores are blob-equal.
- In-place edit (
- Changeset prose, checked sentence by sentence against the diff: the summary line, the four "What changed" bullets (the map's two rows and
FieldAffordance; the details body and strip; the grid, whose object-level verdict is unchanged; the owner field withcreateunchanged and the new direct dependency) and "Unchanged" all match.@object-ui/coreand@object-ui/permissionsareminor;plugin-detailandplugin-gridarepatch. - Contract review: record
6105255917reads PASS.Served-tier: CONTRACT_REVIEW_TIER;Head-shais this head;Local-runs: none.Implemented-byis the dev's branch, andReviewed-byis this session. - Gates at this reading: 39 success, 3 skipped, 2 in progress, 0 failed. Landing waits for every check green on this head.
- Out of scope, one line each:
check:spec-symbolsmisread the dotted citationSystemFieldName.OWNER_ID(the spec exports it as both a const and a type, and the gate reads only the type's members). The dev reworded one comment. Dropped as a card: this is tooling, not a product defect, and it is the first measured false red. A second one triggers the delete-the-limb rule. Dedupe words:spec-symbols,memberSetOfSymbol, const and type merge.- Edit forms strip
owner_idby name (SERVER_OWNED_FIELD_NAMES), so a transfer-holder's owner change on an edit form may be dropped silently. Noted, not filed: it is a read-only inference, with no measured save at a public door. It predates this PR, which only narrows who sees that input. Dedupe words:owner_idsanitize,SERVER_OWNED_FIELD_NAMES, transfer-holder edit form. - The role-based
PermissionProviderhas no transfer concept: accepted. The console mountsMePermissionsProvider, and the newcheckFieldTSDoc states the rule's server premise.
Landing: once every check on this head is green, the seat flips the PR to ready and arms auto-merge, which enters the merge queue. The merge closes this card. objectui#12082 then unblocks (
Blocked-by: #12103).
Generated by Claude Code
- File surface, extended:
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR objectui#12117 →
main5f75cfad·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T04:26Z- The merge: through the merge queue. Its one parent is
a21ff9a9, and it is an ancestor oforigin/main. - Content check: all 15 files the merge changes are blob-identical to the reviewed head
47a8eee5ec: the map's two rows,MePermissionsProvider's owner rule,DetailSection,HeaderHighlight,ObjectGrid's column pass, the census pin, the dependency and lockfile lines, the READMEs and the changeset. Contract review6105255917PASS and ACCEPT6105266041cover that head. - The card: closed
completedby the PR'sFixesline.pm:dispatchedis removed in this act. objectui#12082, which the PR names without a closing keyword, is still open. - Unblocked by this landing: objectui#12082 (
Blocked-by: #12103) goes back topm:queuein this round, with its own unlock note.
Generated by Claude Code
- The merge: through the merge queue. Its one parent is
Blocked-by: #12094
Filing gate ①: a product defect measured at a public door (finding C-02). Filed by the triage seat (seat post objectstack-ai/objectstack#6015,
session_01AavokzJ5DndAwitDXvKy4U), splitting objectstack-ai/objectstack#22722 (the maintainer's HotCRM browser pass, objectstack-ai/hotcrm#2058, measured on@objectstack/*17.7.0). ⛔ Not a claim.Read on objectui
maince991bd70fpackages/plugin-form/src/fieldWriteGate.tsgateByPermission(about:153) readscheckField('write')fromMePermissionsProvider.packages/plugin-detail/src/DetailSection.tsxabout:366–:382, which reads onlyreadonly, computed and system flags);packages/core/src/utils/affordanceGrants.tsabout:114–:126:recordEditandlistInlineEditask no field question).objects[].allowTransferfrom/auth/me/permissions, so the owner field is editable without the transfer grant. objectstack serves that grant fromplugin-hono-server/src/current-user-endpoints.ts.crm_account.annual_revenue: { readable: true, editable: false }gets an editable input, and Save answers 403 "Field write denied". Owner change answers 403 "requires the transfer grant".Done when
GET /auth/me/permissionsas such a rep reportsfields["crm_account.annual_revenue"].editable: false.truewhile the write refuses, the server's per-caller answer disagrees with enforcement. The research read finds that enforcement folds a field'srequiredPermissionsand/me/permissionsdoes not.fieldWriteGatedoes.allowTransfer.Family: affordance ≠ grant, whose close-out is #12082.
affordanceGrants.ts, so this card waits for it.domain:ui· p2.