Skip to content

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

Description

@objectstack-fleet

Filing class: ① product defect (user-visible) — reach: browser, measured on @objectstack/* 17.7.0 (Console as pinned in 17.7.0). Maintainer instruction for platform problems found in testing, verbatim, 2026-10-10: 「你遇到的平台问题应该提交issue」.

Reader: objectui triage → the form renderer (plugin-form) / the Console's permission check.

Symptom

Measured in objectstack-ai/hotclm while fixing objectstack-ai/hotclm#90 (draft PR objectstack-ai/hotclm#91; report on #90). The app's intake screen flow embeds an object form in create mode for clm_contract_version. The requester's permission set grants that object allowCreate: true, allowEdit: false (the app's design: requesters create versions, never edit them). Once GET /api/v1/auth/me/permissions has loaded:

  • every field of the create form renders disabled — "You do not have edit access to this field.";
  • Save & Continue then posts an empty body: POST /api/v1/data/clm_contract_version → 400 VALIDATION_FAILED "Contract is required; Version No. is required; File is required".

So every user of a create-only grant is blocked at that step. (The server accepts the create: with the client's permission copy patched to allowEdit: true, the same create lands 201 under the user's real allowCreate.)

Cause (dev's source read of the 17.7.0 bundle — for triage to confirm)

checkField(object, field, 'write') reads objects[o].allowEdit, and plugin-form disables every field failing it whenever mode !== 'view' — create included. A create form should check allowCreate (field-level create/insert permission), not allowEdit.

Minimal repro

Any permission set with allowCreate: true, allowEdit: false on an object; open that object's create form (or a flow object-form screen in create mode) after /auth/me/permissions has loaded.

Dedupe

MCP search_issues on this repo, create form fields disabled when allowEdit false allowCreate true checkField write create mode → 2 hits: #11000 (closed — every field disabled when the create affordance is closed, the opposite grant) and #3402 (closed — unrelated). Neither covers a create-allowed, edit-denied grant.


Filed by the repo:hotclm PM seat from a measured dev finding.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · domain:ui · area:access · pm:queue. Accepted as the close-out of the "affordance ≠ grant" family; #12081 items 1, 5 and 6 fold in here

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T03:53Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 10, 2026
  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-12082-affordance-grant-map
    Worktree: objectui-issue-12082
    Domain: domain:ui
    Seat: domain:ui#3
    Scope: this card plus objectui#12081 items 1, 5 and 6, which triage folded in here (6093495104, 6093502833). Items 2, 3, 4, 7 and 8 stay on objectui#12081.
    File surface (line numbers on main 1b2d0160):

    • The map, one module: each console affordance → the grant it reads. Rows: create-form fields → create; edit-form fields → edit; record-header Edit → update; list New / Import → create; lookup "Create new" → create on the target object; plus every other row the census finds. Where it lives is the dev's measured choice, either beside resolveEffectiveCrudAffordances (packages/core/src/utils/managedBy.ts:177) or in packages/permissions/src/. ⛔ One copy, ⛔ no per-component permission logic.
    • Create-mode fields: packages/plugin-form/src/fieldWriteGate.ts gateByPermission (:138) asks checkField(…, 'write') whenever mode !== 'view'. A create form asks the create question instead. The resolver side lives in packages/permissions/src/ (MePermissionsProvider.tsx checkField :302; PermissionProvider.tsx; the PermissionContext.ts type). The create answer follows what the server enforces on insert, measured first. ⛔ No client rule the server does not have.
    • The readers:
      • record-header Edit: packages/app-shell/src/views/RecordDetailView.tsx resolveRecordHeaderActionGates (:316);
      • list New / Import: ObjectView.tsx (:2062) and ObjectDataPage.tsx (:432);
      • lookup "Create new": packages/fields/src/widgets/LookupField.tsx canCreate (:1160);
      • any further reader the census finds, for example RelatedRecordActionsBridge.tsx:375, plugin-list ListView.tsx:1883 and plugin-grid rowCrudAffordances.ts:238. Each is named in the PR.
    • Tests: the card's enumeration pin. Every row of the map runs against four grant shapes (create-only, edit-only, read-only, full), and each affordance shows exactly when its grant allows it. A reader that bypasses the map, or an affordance with no row, turns it red. Plus the card's repro as a form-level pin: a create-only grant gets enabled create-form fields.
    • One .changeset/12082-*.md per touched package, or one file naming all of them.

    ⛔ Not on it:

    • objectui#12081 items 2, 3, 4, 7 and 8;
    • any server, spec or /me/permissions envelope change (objectstack's lane);
    • the fail-open contract with no permission provider mounted (fieldWriteGate.ts docblock), which stays as is;
    • packages/components/src/ui/**.

    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier). The contract review runs at the ceiling, CONTRACT_REVIEW_TIER, because of the line below.
    Clause-②: yes
    Why Clause-②: yes: the map is a new exported surface read across packages (core or permissions, then app-shell, fields, plugin-form), and asking the create question may widen checkField's published action union. Changesets are minor where a published surface widens and patch where only behaviour moves. ⛔ Never major (objectui AGENTS.md §9).
    Responsibility: objectui console: each affordance decides on its own which grant it reads, so a create form gates its fields on edit and a create-only role cannot create, the header hides Edit from a caller whose update is allowed, and New / Import / lookup "Create new" show to a caller who cannot create | the platform path: one affordance-to-grant map that every affordance reads, with create-mode fields following the server's insert rule | every user of a create-only, edit-only or read-only grant in any metadata app
    Thread-read: 6093495104
    Serial constraints cleared: none blocking.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12082,
      "status": "done",
      "branch": "claude/issue-12082-affordance-grant-map",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12084",
      "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent seat's id)",
      "premise_still_valid": true,
      "summary": "One affordance-to-grant map now lives in @object-ui/core (AFFORDANCE_GRANTS + resolveAffordance / resolveFieldAffordance / formFieldsAffordance): 18 rows, each naming the CRUD-affordance bit, the object grant and (for field rows) the field question; the verdict is managed-object policy AND effective API operations (getObjectApiOperations, kept inside the map) AND the caller's grant, predicates only when allowed. Core was chosen over permissions because every reader already depends on both and permissions depends on types alone (homing it there adds a permissions-to-core edge; in core the principal is structural, no edge either). The p1 card: checkField gained 'create' (explicit entry, else allowCreate) = the server's insert rule read in objectstack main 76bc1e03 (plugin-security step '2.5. Field-Level Security write enforcement' via computeForbiddenFieldWrites -> FieldMasker.detectForbiddenWrites/getNonEditableFields on insert and update alike; PermissionEvaluator.getFieldPermissions builds entries only for named fields; OPERATION_TO_PERMISSION insert -> allowCreate); every ObjectForm layout's render gate, outbound filter and form-wide lock read the form's row, and a create-mode MasterDetailForm's line cells ask the create question of the child. Measured on main 1b2d0160f: the p1 pin was red on all 9 layouts with body {} (24 failed / 9 passed), now 35/35. Item 1 does not reproduce from objectui's side: for hotclm's clm_payment_plan (controlled_by_parent, no managedBy/userActions/apiMethods) the only principal-dependent header input is the explain record verdict; objectstack-ai/objectstack#22529 (merged 2026-10-09, not an ancestor of the 17.7.0 tag commit 4e4e8814, control leg exit 0) made explain's update verdict on controlled_by_parent records come from the master-detail write check - its own table shows allowed:true + record.visible:false + PATCH 200, the hotclm shape; the header now also reads the update grant (pinned). Item 5 does not reproduce: both list surfaces already ANDed can(create) on main and in 17.7.0, and hotclm binds every position to clm_requester (src/security/bind-position-sets.ts), which grants allowCreate on clm_contract, so the records manager's effective grant allows create; pinned through the map anyway. Item 6 reproduced on main (3 red / 2 green against main's LookupField) and is fixed: Create new reads the target's lookupCreateNew row. Census readers converted: RecordDetailView, ObjectView, ObjectDataPage, importTargetFields (now the create question), RelatedRecordActionsBridge, RecordAttachmentsPanel, ListView, ObjectGrid + rowCrudAffordances (now takes the map's verdicts), ImportWizard, DetailView (operation set + grant; the object's own policy stays the host's channel per objectui#4419), record:details (now reads the update grant), console ProfilePage. Out of family: managedByEmptyState (empty-state copy from the bucket, shows/hides nothing), useFieldPermissions canWrite/writableFields (the resolver's own API), read gates. Known gap, outside the brief's grep population because they read NO grant: plugin-view ObjectView create button, plugin-calendar quick-create and drag-to-reschedule, plugin-kanban card move, LineItemsPanel add/remove lines - see out_of_scope_findings and open_questions.",
      "tests": "All at branch head 62413c986 unless noted (package suites ran at 9104511cc; later commits touched only docs, the changeset and 3 pin files, re-run green at fc8e0193f and 62413c986). Package suites (vitest from repo root, under os-verify-lock): core+permissions 209 files / 4075 passed 27 skipped; fields 244 passed 1 skipped / 3973 passed; plugin-list 125 / 1384; plugin-grid 43+154 = 197 files / 373+1478 passed; plugin-detail 156+94 files (1 skipped) / 1397+1036 passed; plugin-form 171 / 2069 passed 1 skipped; app-shell scoped to the touched views (59+60+85 = 204 files, 416+560+964 passed); console scoped ProfilePage 3 files / 29 passed; scripts/__tests__ 179 passed 2 skipped / 5452 passed. Pins at 62413c986: createFormGrant-12082 35/35, affordanceGrantMap-12082 27/27 (+ check-hand-rolled-comment-mask 22/22), LookupField.createGrant-12082 5/5; family pins green: closedAffordanceNotice-11000, RecordAttachmentsPanel.affordanceGrant-12047, useRecordEditable.principalScope (10107), rowRecordCrudVerdict + rowCrudAffordances + rowCrudEffectiveOps (4296) - 9-file final pin run 161/161. Measured on main first: p1 pin vs main 24 failed / 9 passed (every field locked, create body {}); item-6 pin vs main's LookupField 3 failed / 2 passed (controls green), restored blob-equal. Ablations (scripts/ablation-replace.mjs, WRAP mode, predictions first, every restore blob == HEAD and git diff HEAD empty; vitest aliases @object-ui/core to src, so no dist leg): A1 createFormFields field 'create' -> 'write' (put 'write' back): predicted 16+3 red, observed 19 failed / 43 passed; A3 plant can(referenceTo, 'create') in LookupField: census red naming packages/fields/src/widgets/LookupField.tsx, 1 failed / 26 passed as predicted; A4 recordEdit grant 'update' -> 'create': 4 failed / 38 passed as predicted (row table, create-only and edit-only shapes, header create-only row); A5 MasterDetailForm formMode forced 'edit': 1 failed / 34 passed as predicted. Reverse type check against REBUILT .d.ts: a throwaway probe in plugin-form refused resolveAffordance('noSuchAffordance') with TS2345 listing the map's rows, accepted checkField(o, f, 'create') and refused 'bogus' with TS2345 naming 'read' | 'write' | 'create'; probe deleted. Eager closure: base 1b2d0160f 3,244,032 B gz vs head 3,244,687 B gz (+655 B gz, +1,711 B raw, 290/290 eager chunks), check:eager-closure exit 0 at head (3168.6 KB, budget 3204.6 KB).",
      "mcp_calls": "0 GitHub MCP calls. 1 session tool call: mcp__claude-code-remote__add_repo (objectstack-ai/hotclm, access read) - answered read_available, attached nothing; used for an anonymous shallow clone into the scratchpad to read hotclm's metadata for items 1 and 5.",
      "api_writes": "3 REST writes, each one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create -> POST /repos/objectstack-ai/objectui/pulls (#12084, draft; read-back 7694 B identical); label-write --assign os-support-ai -> POST /repos/objectstack-ai/objectui/issues/12084/assignees (read-back matches); this os-dev-report -> POST /repos/objectstack-ai/objectui/issues/12082/comments. Plus git push to claude/issue-12082-affordance-grant-map (not REST).",
      "open_questions": [
        {
          "question": "Five write affordances read NO grant at all, so the brief's grep census (grant reads) cannot see them: plugin-view ObjectView renderToolbar showCreateButton (create), plugin-calendar ObjectCalendar handleDateClickDefault / handleTimeRangeSelectDefault quick-create (create) and handleEventDropDefault drag-to-reschedule (update), plugin-kanban ObjectKanban handleCardMove (update), plugin-form LineItemsPanel add / remove lines (create / delete on the child; only schema.readonly). They are this family's members; how are they closed?",
          "options": [
            "A - a patch round on this claim: give each a map row (or reuse listNew / rowEdit / rowDelete) and read it; claim's file surface amended to plugin-view, plugin-calendar, plugin-kanban, LineItemsPanel",
            "B - a follow-up dispatch on this card after #12084 lands, keeping this PR's size as is",
            "C - leave them: the server refuses the write anyway"
          ],
          "recommendation": "B, because each surface needs its own runtime pin (calendar and kanban are heavy suites) and #12084 already spans 9 packages; C contradicts the Zone 1 ruling that every affordance reads the map. Note the census pin cannot catch this shape by construction - it refuses grant reads outside the map, not affordances with no grant read - which the follow-up should state rather than imply."
        }
      ],
      "out_of_scope_findings": [
        "class: a (same family, folds into this close-out card - no separate card) - reach: NOT MEASURED at a public door, source read only on objectui main 1b2d0160f - evidence: plugin-view ObjectView renderToolbar `showCreateButton = schema.showCreate !== false && operations.create !== false`; plugin-calendar ObjectCalendar handleDateClickDefault / handleTimeRangeSelectDefault open quick-create on calendarConfig + objectName + dataSource.create only, handleEventDropDefault persists via dataSource.update; plugin-kanban ObjectKanban handleCardMove writes the group field with no grant read; LineItemsPanel add / remove lines gate on schema.readonly only - dedupe words: quick-create grant, drag reschedule permission, kanban card move permission, object-view create button permission, line items add remove grant",
        "carrier: hotclm seat (repo:hotclm), noted not filed - objectui#12081 item 1's cause is the explain record verdict for controlled_by_parent rows, fixed upstream by objectstack-ai/objectstack#22529 after the 17.7.0 tag; hotclm's finance header Edit should be re-measured once its @objectstack pin includes that merge",
        "carrier: hotclm seat, noted not filed - objectui#12081 item 5 reads one permission set; the records manager's effective grant on clm_contract includes clm_requester's allowCreate (every position binds clm_requester), so New / Import are correct for that user",
        "carrier: 承接者:无 - noted not filed - MePermissionsProvider.check keys objects by the name as given while checkField and getObjectApiOperations lowercase it; every reader passes the object's own lowercase name, no divergence measured",
        "carrier: 承接者:无 - noted not filed - LookupField reads `allow_create ?? allowCreate` (two spellings of one key), a consumer-side alias outside this card",
        "carrier: 承接者:无 - noted not filed - ObjectGrid's comment above rowActionDefsList carries a pre-existing cross-file line address (index.tsx plus a line number); not swept here per AGENTS.md #11"
      ],
      "gates": [
        {
          "command": "turbo run build --filter=@object-ui/app-shell... (and console^..., and cli/plugin-ai/gantt/map/markdown/timeline closures) --concurrency=2",
          "exit": 0,
          "verdict": "Tasks: 29/29, 34/34, 20/20 successful"
        },
        {
          "command": "pnpm --filter @object-ui/{core,permissions,plugin-form,fields,plugin-list,plugin-grid,plugin-detail,app-shell} type-check",
          "exit": 0,
          "verdict": "each echoed its own type-check script, 0 error TS (tsc --noEmit && tsc -p tsconfig.test.json)"
        },
        {
          "command": "pnpm --filter @object-ui/console type-check",
          "exit": 0,
          "verdict": "> @object-ui/console@17.7.0 type-check, no error"
        },
        {
          "command": "pnpm exec vitest run (package suites listed under tests)",
          "exit": 0,
          "verdict": "all Test Files passed (counts in tests)"
        },
        {
          "command": "pnpm exec eslint (39 touched .ts/.tsx)",
          "exit": 0,
          "verdict": "0 errors; warnings on added lines: no-explicit-any in new test files and a pre-existing react-hooks/preserve-manual-memoization that moved with LookupField's deps line (9 before, 9 after)"
        },
        {
          "command": "pnpm check:control-bytes",
          "exit": 0,
          "verdict": "check-control-bytes: OK (scanned 8277 tracked text file(s))"
        },
        {
          "command": "pnpm check:test-path-roots",
          "exit": 0,
          "verdict": "check-test-path-roots: OK"
        },
        {
          "command": "pnpm check:changeset-claims",
          "exit": 0,
          "verdict": "No pending changeset names a file this change touches."
        },
        {
          "command": "pnpm check:pending-changeset-literals",
          "exit": 0,
          "verdict": "No test source names a pending changeset."
        },
        {
          "command": "node scripts/check-changeset-presence.mjs",
          "exit": 0,
          "verdict": "39 source file(s) of 9 released package(s) changed, and this change declares 1 changeset(s)"
        },
        {
          "command": "pnpm check:new-line-citations",
          "exit": 0,
          "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
        },
        {
          "command": "pnpm check:readme-exports",
          "exit": 0,
          "verdict": "check-readme-exports: OK (585 of 870 import bindings judged, 0 fabricated); first run exit 1 = 6 unbuilt packages, rebuilt and re-run"
        },
        {
          "command": "pnpm check:doc-fences / doc-types / doc-snippets / doc-examples",
          "exit": 0,
          "verdict": "each passed"
        },
        {
          "command": "pnpm check:vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape",
          "exit": 0,
          "verdict": "each passed"
        },
        {
          "command": "pnpm check:phantom-deps / unused-deps / self-import / esm-specifiers / unreferenced-sources",
          "exit": 0,
          "verdict": "Every in-scope import is declared by the package that publishes it (phantom-deps); the rest passed"
        },
        {
          "command": "pnpm check:comment-mask-corpus",
          "exit": 0,
          "verdict": "1 file(s) disagree, within the residue objectui#7882 is holding open"
        },
        {
          "command": "pnpm check:eager-closure (after apps/console vite build)",
          "exit": 0,
          "verdict": "Console eager closure is 3168.6 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 35.9 KB)"
        },
        {
          "command": "pnpm exec vitest run scripts/__tests__/",
          "exit": 0,
          "verdict": "179 passed, 2 skipped"
        },
        {
          "command": "CI on #12084",
          "exit": null,
          "verdict": "in_progress at 62413c986; at fc8e0193f Test (shard 4/8) failed on check-hand-rolled-comment-mask (census used a private comment regex) - fixed in 62413c986, the other shards were cancelled by that push"
        },
        {
          "command": "i18n gates (check:i18n-keys / drift / dead-keys / designer-parity)",
          "exit": null,
          "verdict": "not run: no locale pack changed (no new keys; the notice copy already existed)"
        }
      ],
      "deviations": [
        "The PR body was written once, when the draft opened after the first commit (os-dev rule: no PATCH by dev); it predates the gate readout, the ablations, the eager delta, the final reader list and the fix for check-hand-rolled-comment-mask. Requested amendment for the seat: replace the Readers section with the converted list in summary plus the out-of-family list (managedByEmptyState, useFieldPermissions, read gates), add the MasterDetailForm line-cell change, the tests/ablation/eager readings in tests, and the no-grant family members from open_questions under Acceptance notes.",
        "Files beyond the claim's named surface, each a census reader the claim says to name in the PR: plugin-detail DetailView + record-details, plugin-grid ImportWizard + rowCrudAffordances, apps/console ProfilePage (+ its 3 test stubs), the core/permissions READMEs and content/docs/plugins/plugin-form.mdx (AGENTS.md #2). MasterDetailForm (create-mode line cells ask the create question) was taken under the bounded in-place rule (same defect class, mechanical, pinned by createFormGrant-12082 + ablation A5, no other claim on the file); the claim's file surface needs that addition.",
        "Behaviour moves beyond the card's rows, all toward the server's refusal: the grid add-record row now also honours managed-object policy and the effective create operation (gridAddRow row); DetailView's object gate adds the effective operation set; record:details in-place editing now reads the update grant; the form-wide lock and its notice now also engage on a denied grant for the form's mode.",
        "The census pin is a test-level guard the card and the claim rule for (an affordance or reader outside the map turns it red); no new check:* gate or workflow was added.",
        "My hand-derived gate list missed scripts/__tests__/check-hand-rolled-comment-mask.test.ts (a root-level test that scans package tests); CI shard 4 caught it and it is fixed. The root scripts suite was then run locally in full.",
        "Read access to objectstack-ai/hotclm via add_repo (read_available, nothing attached) and an anonymous shallow clone in the scratchpad, to measure items 1 and 5 against hotclm's metadata. A temporary ref refs/os-dev-12082/main was written to the shared .git to read origin/main's delta and deleted after.",
        "Commit trailers are the model-free pair per objectui AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness's model-named form."
      ],
      "files_changed": [
        ".changeset/12082-affordance-grant-map.md",
        "apps/console/src/pages/system/ProfilePage.tsx",
        "apps/console/src/pages/system/__tests__/ProfilePage.access.test.tsx",
        "apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx",
        "apps/console/src/pages/system/__tests__/ProfilePage.sharedSelect-11865.test.tsx",
        "content/docs/plugins/plugin-form.mdx",
        "packages/app-shell/src/views/ObjectDataPage.tsx",
        "packages/app-shell/src/views/ObjectView.tsx",
        "packages/app-shell/src/views/RecordAttachmentsPanel.tsx",
        "packages/app-shell/src/views/RecordDetailView.headerActionGates.test.tsx",
        "packages/app-shell/src/views/RecordDetailView.tsx",
        "packages/app-shell/src/views/RelatedRecordActionsBridge.tsx",
        "packages/app-shell/src/views/importTargetFields.test.ts",
        "packages/app-shell/src/views/importTargetFields.ts",
        "packages/core/README.md",
        "packages/core/src/index.ts",
        "packages/core/src/utils/affordanceGrants.ts",
        "packages/fields/src/widgets/LookupField.createGrant-12082.test.tsx",
        "packages/fields/src/widgets/LookupField.tsx",
        "packages/permissions/README.md",
        "packages/permissions/src/MePermissionsProvider.tsx",
        "packages/permissions/src/PermissionContext.ts",
        "packages/permissions/src/PermissionProvider.tsx",
        "packages/plugin-detail/src/DetailView.tsx",
        "packages/plugin-detail/src/renderers/record-details.tsx",
        "packages/plugin-form/src/DrawerForm.tsx",
        "packages/plugin-form/src/MasterDetailForm.tsx",
        "packages/plugin-form/src/ModalForm.tsx",
        "packages/plugin-form/src/ObjectForm.effectiveOps.test.tsx",
        "packages/plugin-form/src/ObjectForm.tsx",
        "packages/plugin-form/src/SplitForm.tsx",
        "packages/plugin-form/src/TabbedForm.tsx",
        "packages/plugin-form/src/WizardForm.tsx",
        "packages/plugin-form/src/affordanceGrantMap-12082.test.tsx",
        "packages/plugin-form/src/createFormGrant-12082.test.tsx",
        "packages/plugin-form/src/fieldWriteGate.ts",
        "packages/plugin-form/src/formChrome.i18n-11071.test.tsx",
        "packages/plugin-form/src/sanitize.ts",
        "packages/plugin-grid/src/ImportWizard.tsx",
        "packages/plugin-grid/src/ObjectGrid.tsx",
        "packages/plugin-grid/src/__tests__/rowCrudAffordances.test.ts",
        "packages/plugin-grid/src/rowCrudAffordances.ts",
        "packages/plugin-list/src/ListView.tsx"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment and seat answer — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-10T06:43Z

    This amends claim 6093729821 after the dev's report 6094735932 (status: done, PR objectui#12084 at 62413c98).

    1. The dev's open question: five write affordances read no grant at all

    These five sit outside the brief's grep census, which looked for grant reads:

    • plugin-view ObjectView create button;
    • plugin-calendar quick-create;
    • plugin-calendar drag-to-reschedule;
    • plugin-kanban card move;
    • plugin-form LineItemsPanel add / remove lines.

    Seat answer: B. They are closed by a follow-up dispatch on this card, after objectui#12084 lands. This is a sequencing call, so it is in the class the seat answers without escalating.

    • Why not A: each surface needs its own runtime pin (calendar and kanban are heavy suites), and objectui#12084 already spans nine packages.
    • Why not C: "the server refuses the write anyway" contradicts this card's ruling that every affordance reads the map.

    Consequences:

    • objectui#12084 stops closing this card. The seat rewrites its first line from Fixes #12082 to Part of #12082, so the card stays open for the remainder.
    • After the landing: the claim is released to pm:queue with pm:retriage, and the remaining scope written on the card. The p1 defect is closed by objectui#12084, so triage re-grades what is left.
    • For the follow-up: each of the five gets a row in the map (or reuses an existing one, for example listNew, rowEdit or rowDelete), and its reach is measured first. The census pin as it stands refuses grant reads outside the map; it cannot see an affordance that reads no grant. The follow-up must state that limit or close it. It must not imply the pin covers that shape.

    2. File surface: added, as census readers the claim said to name

    • packages/plugin-detail/src/DetailView.tsx and src/renderers/record-details.tsx;
    • packages/plugin-grid/src/ImportWizard.tsx and src/rowCrudAffordances.ts;
    • apps/console/src/pages/system/ProfilePage.tsx and its three test stubs;
    • packages/plugin-form/src/MasterDetailForm.tsx: a create-mode form's line cells ask the create question of the child. Same defect class, pinned by createFormGrant-12082 and ablation A5;
    • the core and permissions READMEs and content/docs/plugins/plugin-form.mdx (objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2).

    Clause-②: yes stands. @object-ui/core (the map) and @object-ui/permissions (checkField accepts 'create') are minor. The rest are patch.

    3. objectui#12081 items 1, 5 and 6

    The seat records these readings on objectui#12081 at landing.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-10T07:01Z. PR objectui#12084, head 62413c98.

    • PR shape:

      • Draft against main, based on 1b2d0160. git merge-tree against main 12ff2563 (objectui#6349 batch 10 landed since) is clean.
      • The body was brought to round 2 by the seat. First line Part of #12082, per the seat's answer B (6094765692): no closing keyword in the body or in any of the ten commits, so this card stays open for its remainder. Clause-②: yes at line start.
      • The commits carry only the model-free trailer pair. Assignee os-support-ai, set with no refusal.
    • Scope: 43 files, +1714/−284, on the claim 6093729821 as amended in 6094765692 (census readers in plugin-detail, plugin-grid, the console ProfilePage, and MasterDetailForm's create-mode line cells). The path fork reads no governed path, and the change is under the 5,000-line human-merge threshold.

    • What lands:

      • The p1 defect: a create form asks its fields the create question. checkField(object, field, 'create') answers from the explicit field entry when there is one, and from allowCreate otherwise. That follows the server's insert rule, read at objectstack 76bc1e03: field step 2.5 refuses only an explicit editable: false, and object admission maps insert to allowCreate. Measured on main first: 24 red / 9 green across all nine layouts, with the create body {}. At the head: 35 / 35.
      • The map: AFFORDANCE_GRANTS in @object-ui/core, with 18 rows. resolveAffordance returns managed-object policy ∧ effective API operation set ∧ the caller's grant, and surfaces predicates only when allowed. resolveFieldAffordance and formFieldsAffordance cover the form rows. Every census reader now reads it, and the PR names the out-of-family readers with their reasons.
      • objectui#12081 item 6 (lookup "Create new"): reproduced on main (3 red) and fixed through the lookupCreateNew row, asked of the target object.
      • Items 1 and 5: do not reproduce from objectui's side (readings in the PR). Both rows are pinned through the map anyway.
      • Behaviour moves beyond the card's rows all point toward the server's refusal, and the PR lists each one.
      • Changesets: @object-ui/core and @object-ui/permissions minor, seven packages patch. Fail-open with no provider is unchanged.
    • Contract review: 6094907310 on the PR, rendered at CONTRACT_REVIEW_TIER by an isolated, read-only reviewer on this head, reads PASS. The seat checked the record's lines:

      • Served-tier: CONTRACT_REVIEW_TIER;
      • Head-sha equal to the head;
      • Local-runs: none;
      • Implemented-by and Reviewed-by signed;
      • VERDICT: PASS;
      • no model name.

      It judged:

      • ① the 18 rows name the right grants, and the create question matches the server's insert rule; the re-signatured helpers and PermissionContextValue are not exported from their package entries, so the widened checkField union is purely additive; every console object form sets mode, so mode === 'create' reaches every create surface;
      • ② minor / patch matching what the diff publishes;
      • ③ the open question answered by the seat (B), and every deviation closed on this head.
    • Gates:

      • The dev's local suites all pass (counts in 6094735932). The four ablations went red as predicted, with restores blob-equal.
      • The Console's eager closure adds 655 B gzipped; check:eager-closure passes.
      • CI on 62413c98: 43 runs, 40 success, 3 skipped (the expected three), 0 failure; mergeable_state clean.
    • Process note, acknowledged here: the dev wrote a temporary ref, refs/os-dev-12082/main, into the shared checkout's .git to read origin/main's delta, then deleted it. That is a write to a shared checkout, which the brief forbids. It has no effect on the diff or the head. The reviewer raised it, and the seat records it rather than letting it pass silently.

    • Out-of-scope findings, one line each:

      • five write affordances that read no grant: this card's follow-up (answer B), not a new card;
      • objectui#12081 items 1 and 5, carried to the hotclm seat: noted in the PR, to be re-measured there;
      • check's object-name casing: Acceptance notes;
      • LookupField's allow_create ?? allowCreate: Acceptance notes;
      • ObjectGrid's old cross-file line address: dropped (pre-existing, AGENTS.md [WIP] Update documentation for project #11).

    Landing: ready + auto-merge through the queue, now. Its merge does not close objectui#12082. At landing, the seat releases the claim to pm:queue with pm:retriage, writes the remaining five affordances on the card, and records items 1, 5 and 6 on objectui#12081.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#12084 → main 023f00d4 · the p1 defect is fixed; the card stays open for its remainder and goes back to triage

    domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-10T07:21Z.

    • The merge: through the queue at 2026-10-10T07:19Z.
      • One parent, 12ff2563, an ancestor of origin/main.
      • The landed tree equals git merge-tree of that parent and the accepted head 62413c98 (tree bc141bfd on both).
      • 43 files, +1714/−284, all forty-three blobs equal to the accepted head's.
    • Content check:
      • AFFORDANCE_GRANTS is in packages/core/src/utils/affordanceGrants.ts:110 and exported from @object-ui/core's entry.
      • MePermissionsProvider's checkField answers 'create' from allowCreate when no field entry exists (:335).
      • LookupField reads resolveAffordance('lookupCreateNew', …) (:1170).
      • Control: at 12ff2563 the map file is absent and checkField's union has no 'create'.
    • Contract review 6094907310 PASS; ACCEPT 6094918906.

    Release: session_01CGZy1BGCjdN5cXqL9cnvB8 (account os-support-ai) releases claim 6093729821, as amended in 6094765692.

    • Reason: the dispatched round has landed. The card stays open on purpose: the PR said Part of #12082, per the seat's answer B. The maintainer has also told this seat to clock out once this merge lands, verbatim, 2026-10-10: 「当前任务处理完,合并后就下班。」 So no follow-up is dispatched from this seat.
    • Next: pm:dispatched and the assignee are removed in this act. The card goes back to pm:queue, and pm:retriage is added for the question below.

    What remains on this card (the follow-up): five write affordances read no grant at all, so the first round's census (grant reads) could not see them:

    1. plugin-view ObjectView's create button (showCreateButton): create;
    2. plugin-calendar quick-create (handleDateClickDefault, handleTimeRangeSelectDefault): create;
    3. plugin-calendar drag-to-reschedule (handleEventDropDefault): update;
    4. plugin-kanban card move (handleCardMove): update;
    5. plugin-form LineItemsPanel add / remove lines: create / delete on the child.

    For each: measure reach on main first, then give it a row in the map (or reuse listNew, rowEdit or rowDelete) and read it through resolveAffordance. The census pin as it stands refuses grant reads outside the map; it cannot see an affordance that reads no grant. The follow-up states that limit or closes it.

    pm:retriage — what this seat asks of triage: re-grade the remainder. priority:p1 was for the create form, which a create-only role could not use at all, and that is fixed on main. The five above show an affordance whose write the server then refuses, which is the family's p2 shape (objectui#12081's triage). Say whether the remainder stays on this card or moves to a successor card.


    Generated by Claude Code

  8. 34 remaining items

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 (the backlog freeze and leg B's non-Setup sites; triage 6102063887)
    Session: session_01TYgwmFK1q4KJ6Qq2WRLzsD
    Account: zhuangjianguo
    Branch: claude/issue-12082-census-freeze-leg-b
    Worktree: objectui-issue-12082
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: the census pin packages/plugin-form/src/affordanceGrantMap-12082.test.tsx (the freeze, and the WRITE_SITES entries for the nine sites below); packages/core/src/utils/affordanceGrants.ts (a row only where none fits); the record feed's writes in packages/app-shell/src/views/RecordDetailView.tsx (handleAddComment, handleAddReply, handleToggleReaction); the dashboard designer's Save in packages/plugin-designer/src/pages/DashboardDesignPage.tsx (saveSchema); the related list's Add existing and fallback remove in packages/plugin-detail/src/RelatedList.tsx (handleAddRecords) and packages/plugin-detail/src/renderers/record-related-list.tsx; their tests; the docs pages these change; .changeset/12082-census-freeze-leg-b.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-②: no
    Responsibility: objectui's record feed (sys_comment and sys_comment_reaction writes), dashboard designer Save and related list Add existing and fallback remove, which each offer a write that reads no grant | AFFORDANCE_GRANTS and resolveAffordance in @object-ui/core, and the WRITE_SITES census (objectui#12084, #12094, #12117, #12132) | a user whose permission set lacks the grant on those objects; reach is measured per site first, and a site no such user can reach becomes outOfFamily with the reading
    Thread-read: 6107939662
    Serial constraints cleared: none blocking. Read at 2026-10-11T10:25Z:

    • No open objectui PR touches any file on this surface.
    • This seat's in-flight objectui#12106 edits plugin-detail's recordActivityFeed.ts, and objectui#12126 edits metadata-admin's widgets.tsx and ResourceEditPage.tsx. Neither is on this surface.
    • Deferred: leg B's four Setup entries (AssignedUsersSection, PositionHoldersSection). They sit in the objectui#7611 epic's C9 readers, and the epic's C9 part 2 is still to come (6106811169). This seat posts a cross-lane declaration on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194 naming the freeze and the deferral.

    This round, in triage's order:

    1. The freeze: a NEW unmapped entry turns the census red until it is graded. Leg A's hand-written HOST_GATED limit is stated or closed in the same step.
    2. Leg B's nine non-Setup entries, with reach measured per site: each becomes mapped to a row (new or reused) with its pin, or outOfFamily with the measured reason.

    The four Setup entries stay unmapped; the freeze grandfathers existing entries. The PR says Part of #12082.

    Clause-②: no is a prediction, not a measurement. If a row is added to AFFORDANCE_GRANTS, the published value widens, and the report says so. The seat then corrects this line and runs the review at CONTRACT_REVIEW_TIER.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12082,
      "status": "done",
      "branch": "claude/issue-12082-census-freeze-leg-b",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12140",
      "session": "session_01TYgwmFK1q4KJ6Qq2WRLzsD (subagent mode: the dispatching seat's id)",
      "premise_still_valid": true,
      "summary": "The census backlog is frozen and leg B's nine non-Setup sites are graded with reach measured per site. Freeze: UNMAPPED_BACKLOG in the census pin is a closed list held equal to the ledger's unmapped keys both ways (a NEW unmapped entry is red; a key graded out must leave the list), frozen at the 13 BASE keys and shrunk by each commit; at the head it holds only the four Setup keys. Leg A's hand-written HOST_GATED is CLOSED rather than stated: a derivation (hostGatedSites) finds a mapped site host-gated when a readBy file JSX-mounts the component the write is written in, and then every mount must be in readBy; on this tree it finds exactly the import wizard, as the list held; its residual blind spot (a host reaching the write through a hook, function, alias or wrapper) is written beside it. Leg B: the feed's five sites are reachable (the everyone baseline names neither sys_comment nor sys_comment_reaction; read on objectstack main 67b669e6: member_default has no entry, viewer_readonly has '*' read without create, the showcase app's sets name sys_comment 0 times, and the security middleware's CRUD step maps insert to allowCreate and refuses 403 PERMISSION_DENIED), so four rows join AFFORDANCE_GRANTS (feedComment create, feedReactionAdd create, feedReactionRemove delete, feedReactionColumn update), each asked of the object it writes, and RecordDetailView hands each handler over only where its row allows (Clause-2 yes, core minor). The related list's Add existing maps to relatedNew (linkField: a link row of the list's own object) or relatedRowEdit (re-parent of the picked record), both reused; its fallback remove maps to relatedRowDelete and no longer puts back the delete the record page's bridge withheld. The dashboard designer's Save is outOfFamily, measured: it writes sys_dashboard, a per-type table objectui retired and no objectstack server registers (absent from the spec's platform and cloud object registries), so it fails for every caller whatever the grant. H3's premise (reachable only behind Studio's entry gate) did not hold: the route sits among ordinary app routes with no gate and no in-app link.",
      "tests": "MEASURED ON BASE c9fc10b1 (restore-trapped script): the 4 source files checked out at BASE (4/4 blobs == BASE on disk; fix anchors feedComment / resolveFeedWriteGates / addGranted / removeFallbackGranted each grep -c 0), HEAD pins + census: Test Files 4 failed (4), Tests 20 failed | 64 passed (84). Red per pin, every red the defect direction (affordance offered under a grant that does not hold it): feed 9/18, Add existing 5/9, fallback remove 4/8, census 2/49 (row table, mapped-site check); controls green (no provider, full shape, host delete). Restore: 4/4 blobs == HEAD bc8f5d5, git diff HEAD empty, status clean. ABLATIONS (objectstack scripts/ablation-replace.mjs WRAP, predictions written first, every restore 'blob == HEAD and git diff HEAD is empty'): A1 grade LookupField's mapped entry unmapped -> census 1 failed / 48 passed (freeze, added); A2 put handleAddComment back in UNMAPPED_BACKLOG -> 1 failed / 48 (freeze, left); A3 plant a JSX ImportWizard mount in RecordDetailView.tsx -> 1 failed / 48 (host-gated mount test); A4 hand onAddComment over unconditionally -> feed pin 4 failed / 14 passed, census green (the file still reads feedComment); A5 junction Add reads relatedRowEdit -> 3 failed (census 'no file it names reads relatedNew' + Add pin create-only and edit-only). Each as predicted; vitest aliases @object-ui/core and plugin-detail to src, so no dist leg. AT FINAL HEAD 6dea96f (main ce9811c merged): pins + census + 12106's recordActivityFeed.milestoneKind test + rowCrudAffordances + column-identity ratchet + one-authority-per-exported-name-6273 + check-doc-json-fences test: Test Files 9 passed (9), Tests 199 passed (199); root suite scripts/__tests__/ in 3 shards: 60 passed | 1 skipped, 60 passed | 1 skipped, 60 passed (Tests 1880+1999+1610 passed, 2 skipped). AT 07b86fe (main 4997995 merged): turbo build of app-shell^... plugin-detail^... plugin-form^... Tasks 28 successful (6 cached); type-check core / plugin-detail / plugin-form / app-shell each echoed its own '> @object-ui/PKG@17.7.0 type-check', exit 0, 0 error TS (tsconfig.test.json includes src/**/*.test.tsx in app-shell, plugin-detail, plugin-form); plugin-detail shards 2-4/4: 64/557, 64/579, 63/620 passed; shard 1/4 on the tree that became 376ac2e: 63 passed | 1 skipped / 713 passed | 8 skipped. AT 8c85182 tree: all 48 RecordDetailView* files, Test Files 48 passed, Tests 312 passed. CI on 6dea96f (read once, not awaited): 43 check runs, 40 success, 3 skipped.",
      "mcp_calls": "0 - no MCP tool called. Card, comments, PR and objectstack main files read by REST GET (gh api) through the proxy; one gh api search/code attempt was refused by the proxy (403, repository-scoped sessions) and replaced by a contents/tree read.",
      "api_writes": "2 REST writes, each one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (#12140, draft) + its assignee leg POST /repos/objectstack-ai/objectui/issues/12140/assignees (zhuangjianguo); relay read-back 10062 bytes sent = stored, identical, re-read independently; (2) this os-dev-report -> POST /repos/objectstack-ai/objectui/issues/12082/comments. Plus git push of claude/issue-12082-census-freeze-leg-b (not REST). Zero label writes, no PR-body PATCH.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 - noted in the PR's Acceptance notes, not filed - reach: NOT MEASURED at a door (source read + the runtime harness only) - the record feed's reply composer cannot be reached from a read row and a reply read back is not shown: RecordActivityTimeline draws ThreadedReplies (and the reply input) only when replyCount is above 0, and nothing derives replyCount on read (RecordDetailView sets it only after a reply is posted in-session), while sys_comment's own docblock prescribes counting parent_id children at read time - dedupe words: replyCount never derived, threaded replies hidden, reply input unreachable, parent_id reply count",
        "carrier: 承接者:无 - noted in the PR's Acceptance notes, not filed (no in-app entry: zero pull) - the dashboard designer route design/dashboard/NAME has no in-app link (DashboardView's 'Design in Studio' deep-links to Studio's own surface) and its Save writes the retired sys_dashboard, so it fails for every caller and saveSchema swallows the error - dedupe words: DashboardDesignPage sys_dashboard retired, orphan design route, dashboard designer save silent failure",
        "carrier: 承接者:无 - noted, not filed (prose drift in objectstack) - plugin-audit comment-access-hooks.ts's header still says the default member permission sets grant wildcard CRUD; since objectstack#5491 member_default carries no wildcard, and sys_comment's bits come from application sets (the reaction floor pin says so)"
      ],
      "gates": [
        {
          "command": "measure-on-BASE: 3 new pins + census with 4 source files at BASE c9fc10b1 (restore-trapped)",
          "exit": 1,
          "verdict": "Test Files 4 failed (4) - Tests 20 failed | 64 passed (84); restore 4/4 blobs == HEAD, git diff HEAD empty"
        },
        {
          "command": "ablations A1-A5 via scripts/ablation-replace.mjs WRAP",
          "exit": 1,
          "verdict": "A1 1/48, A2 1/48, A3 1/48, A4 4/14 (census green), A5 3 failed - each as predicted; every restore blob == HEAD, git diff HEAD empty"
        },
        {
          "command": "pnpm exec vitest run (pins + census + 12106 test + rowCrudAffordances + 2 ratchets + check-doc-json-fences test) @ 6dea96f",
          "exit": 0,
          "verdict": "Test Files 9 passed (9) - Tests 199 passed (199)"
        },
        {
          "command": "pnpm exec vitest run --shard=1..3/3 scripts/__tests__/ @ 6dea96f",
          "exit": 0,
          "verdict": "60 passed | 1 skipped (61); 60 passed | 1 skipped (61); 60 passed (60) - Tests 1880 + 1999 + 1610 passed, 2 skipped"
        },
        {
          "command": "turbo run build --filter=@object-ui/app-shell^... --filter=@object-ui/plugin-detail^... --filter=@object-ui/plugin-form^... --concurrency=2 @ 07b86fe",
          "exit": 0,
          "verdict": "Tasks: 28 successful, 28 total - Cached: 6"
        },
        {
          "command": "pnpm --filter @object-ui/{core,plugin-detail,plugin-form,app-shell} type-check @ 07b86fe",
          "exit": 0,
          "verdict": "each echoed '> @object-ui/PKG@17.7.0 type-check', 0 error TS"
        },
        {
          "command": "pnpm exec vitest run --shard=N/4 packages/plugin-detail/",
          "exit": 0,
          "verdict": "s1 (tree of 376ac2e) 63 passed | 1 skipped / 713 passed | 8 skipped; s2-s4 @ 07b86fe 64/557, 64/579, 63/620 passed"
        },
        {
          "command": "pnpm exec vitest run packages/app-shell/src/views/RecordDetailView (48 files) @ 8c85182 tree",
          "exit": 0,
          "verdict": "Test Files 48 passed (48) - Tests 312 passed (312); app-shell narrowed to the file it touches (declared); CI runs the package"
        },
        {
          "command": "core suite",
          "exit": null,
          "verdict": "NARROWED (declared): no core test reads affordanceGrants.ts (git grep: only index.ts and README name it); the tree's two readers of the map, the census and plugin-grid rowCrudAffordances.test.ts, ran green at 6dea96f; the full core suite was queue-timed out twice behind a 25-minute holder"
        },
        {
          "command": "plugin-form suite",
          "exit": null,
          "verdict": "NARROWED (declared): plugin-form's only change is the census test file, run at every head"
        },
        {
          "command": "pnpm exec eslint --format json (8 touched .ts/.tsx; objectui's form: inline config honoured)",
          "exit": 0,
          "verdict": "0 errors; modified files' warnings equal BASE (RecordDetailView 109 = 109, RelatedList 77 = 77, record-related-list 15 = 15, affordanceGrants 0 = 0, census 0 = 0); 4 + 5 + 4 warnings in the 3 new pin files"
        },
        {
          "command": "node scripts/check-changeset-presence.mjs @ 6dea96f",
          "exit": 0,
          "verdict": "8 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s): .changeset/12082-census-freeze-leg-b.md"
        },
        {
          "command": "check:changeset-claims / check:pending-changeset-literals / check-changeset-no-major @ 6dea96f",
          "exit": 0,
          "verdict": "each OK"
        },
        {
          "command": "check:new-line-citations @ 6dea96f",
          "exit": 0,
          "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
        },
        {
          "command": "check:control-bytes / test-path-roots / vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape / phantom-deps @ 6dea96f",
          "exit": 0,
          "verdict": "each OK (control-bytes scanned 8365 tracked text files)"
        },
        {
          "command": "docs:check-links / check:doc-fences / check:doc-types / check:doc-example-ids / check:doc-json @ 6dea96f",
          "exit": 0,
          "verdict": "each OK (doc-json: every judged JSON node document passes the strict authoring face)"
        },
        {
          "command": "check:readme-exports",
          "exit": 1,
          "verdict": "NOT MEASURED for 7 unbuilt packages' READMEs (app-shell, cli, plugin-ai, plugin-gantt, plugin-map, plugin-markdown, plugin-timeline: prerequisite, not a verdict); the two READMEs this change edits (core, plugin-detail) judged, 0 fabricated"
        },
        {
          "command": "check:eager-closure",
          "exit": null,
          "verdict": "NOT MEASURED, reason: needs a base and a head apps/console build under a contended lock; the eager delta is four const rows in @object-ui/core plus small conditionals in three modules; left to the Bundle Analysis workflow"
        },
        {
          "command": "i18n gates",
          "exit": null,
          "verdict": "not owed: no locale string added or changed"
        },
        {
          "command": "CI on #12140",
          "exit": null,
          "verdict": "read once at 6dea96f, not awaited: 43 check runs, 40 success, 3 skipped (the CI wait is the seat's)"
        }
      ],
      "line_budget": "12 files, +908 / -51 vs main ce9811c: 4 source files +146 / -10 (affordanceGrants rows, RecordDetailView, RelatedList, record-related-list), 4 test files +697 / -38 (3 new pins, the census), 3 docs +30 / -3 (core README, plugin-detail README, plugin-detail.mdx), 1 changeset +35. No skills/**, .claude/**, AGENTS.md or docs/adr path touched; no line ratchet applies.",
      "deviations": [
        "Clause-2 is yes, not the claim's predicted no: four rows (feedComment, feedReactionAdd, feedReactionRemove, feedReactionColumn) widen the published AFFORDANCE_GRANTS, so @object-ui/core is minor; the PR body says Clause-2: yes at a line start. The claim line needs the seat's correction, and the contract review at CONTRACT_REVIEW_TIER is owed.",
        "PR body written once at 07b86fe, before the final readings. Requested amendment for the seat: replace the 'Suites and gates ... At open:' sentence with the final readings in tests/gates (6dea96f: pins 9 files / 199 passed, root suite 3 shards green; 07b86fe: build 28/28, four type-checks green, plugin-detail 4 shards green), and add the second merge of main (ce9811c: objectui#12106 edits plugin-detail's recordActivityFeed.ts and content/docs/plugins/plugin-detail.mdx, auto-merged with both kept; objectui#12127 / #12139 add check:doc-json, green).",
        "Measured instead of assumed (Zone 2): H3 did not hold - DashboardDesignPage's route is an ordinary app route with no gate and no in-app link, and the outcome is outOfFamily for a different measured reason (the write's target object does not exist). H4 held both ways, one per branch. H2 held: comments are not a platform default.",
        "The dispatch said the card carries 20 comments; the API answers 19 (issue comments count and list), all read, newest the claim 6108058067.",
        "Suite coverage, declared: core narrowed to the map's readers (no core test reads the module); plugin-form to the census (its only change); app-shell to the 48 RecordDetailView* files; type-checks and plugin-detail's shards ran at 07b86fe, before the second main merge (6dea96f), after which the pins, 12106's test and the root suite re-ran. Four queue-timeouts (exit 99) behind other seats' long holders were re-taken with the same slot, never from the queue tail.",
        "A temporary probe line (writing hostGatedSites' answer to a scratch file) was added to the census to measure the derivation and removed before the first commit; it was never committed.",
        "git fetch origin main ran twice in the worktree, which updates the shared refs/remotes/origin/main (the same write the dispatch's own setup command makes); no other ref was written to the shared .git.",
        "pnpm check:nul-bytes and check:doc-links are not objectui scripts (pnpm exit 254, missing script - not a measurement); objectui's check:control-bytes and docs:check-links ran instead, green.",
        "Commit trailers are objectui's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness's model-named form.",
        "Worktree removed after the PR opened (node_modules removed first; git worktree remove succeeded without --force; remote branch head 6dea96f == local HEAD, tree clean)."
      ],
      "files_changed": [
        ".changeset/12082-census-freeze-leg-b.md",
        "content/docs/plugins/plugin-detail.mdx",
        "packages/app-shell/src/views/RecordDetailView.feedGrant-12082.test.tsx",
        "packages/app-shell/src/views/RecordDetailView.tsx",
        "packages/core/README.md",
        "packages/core/src/utils/affordanceGrants.ts",
        "packages/plugin-detail/README.md",
        "packages/plugin-detail/src/RelatedList.tsx",
        "packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.removeGrant-12082.test.tsx",
        "packages/plugin-detail/src/__tests__/RelatedList.addGrant-12082.test.tsx",
        "packages/plugin-detail/src/renderers/record-related-list.tsx",
        "packages/plugin-form/src/affordanceGrantMap-12082.test.tsx"
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR objectui#12140 (head 6dea96f453), round 2: the census freeze and leg B's non-Setup sites, with the claim amendment · domain:ui seat 2, session_01TYgwmFK1q4KJ6Qq2WRLzsD, 2026-10-11T13:55Z

    Reviewed against GitHub and the diff, not the report (os-dev-report 6109609151).

    Claim amendment (to claim 6108058067)

    • Clause-②: yes, not the predicted no. Four rows join the published AFFORDANCE_GRANTS: feedComment, feedReactionAdd, feedReactionRemove and feedReactionColumn. So @object-ui/core is minor.
    • The review this owes is on record: contract review 6109740908, PASS on this head, by an isolated subagent at CONTRACT_REVIEW_TIER.

    Verdict

    • Shape:
      • Draft on main, first line Part of #12082, and Clause-②: yes at a line start.
      • No closing keyword anywhere in the body, so the card stays open for the four Setup entries.
      • Assignee zhuangjianguo.
    • Scope: 12 files, +908/−51, all inside the claim's file surface: the map, RecordDetailView.tsx, RelatedList.tsx, record-related-list.tsx, three new pins, the census, two READMEs, the docs page and one changeset. DashboardDesignPage.tsx is untouched: it is graded outOfFamily.
      • Nothing under content/docs/releases/.
      • check-governed-merges --pr: NOT governed, 959 changed lines.
    • Changeset and docs, checked sentence by sentence against the diff:
      • The four rows, and the object each asks, match affordanceGrants.ts and resolveFeedWriteGates.
      • "The platform's everyone baseline grants neither object": read on objectstack main, member_default carries no '*' and no sys_comment or sys_comment_reaction entry.
      • "taking a reaction back asks the delete grant: without it the click writes nothing": matches if (!want && !reactionRemoveGranted) return;.
      • Add existing reads relatedNew with a linkField and relatedRowEdit without one: matches addGranted and the two writes in handleAddRecords.
      • The fallback remove reads relatedRowDelete with the registered schema: matches removeFallbackGranted.
      • "With no permission provider mounted every one of these reads open, as before": true of the grant leg. An object's own policy and effective API operations still apply, as the docs paragraph says.
      • The plugin-detail.mdx paragraph and both READMEs name the same rows and grants.
    • outOfFamily, the dashboard designer's Save: checked. No sys_dashboard registration on objectstack main, and objectui's runtime-metadata-persistence.ts names the per-type tables retired.
    • Evidence:
      • With the 4 source files at BASE c9fc10b1, 20 of 84 tests failed across the 3 new pins and the census. Each red is in the defect direction: a write offered under a grant that does not hold it.
      • Ablations A1–A5 were red as predicted, with every restore blob-equal.
    • Gates on this head (read at 2026-10-11T13:55Z): 40 success, 3 skipped by design (dependabot, Test (coverage) and the coverage-shard placeholder), 0 failure. Lint, Type Check and Test all read success.

    Out-of-scope findings

    Acceptance notes

    • Reviewer note (d): on the re-parent branch of Add existing, the policy leg reads the default bucket whenever the picked object is not the list's own object. The grant and the effective operation set are still read. This is the same class as leg A's declared default-bucket reads.
    • Reviewer note (e): the feed pin's objects prop omits both feed objects, so the policy leg against the registered sys_comment and sys_comment_reaction schemas is judged from the spec, not pinned. One case with both schemas registered belongs in the card's next round.
    • The PR body's "At open" readings predate the second main merge (ce9811c). The final readings are in the report 6109609151 and in the gates line above. The body is left as written.
    • Suites narrowed and declared: core to the map's readers, plugin-form to the census, app-shell to the 48 RecordDetailView* files. CI ran every package.

    Landing: ready and auto-merge through the merge queue now. At the merge this seat releases claim 6108058067, with the four Setup entries left on the card (epic objectui#7611 C9).

  12. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#12140 → main 158c75d (round 2: the census freeze and leg B's non-Setup sites). The card stays open for the four Setup entries · domain:ui seat 2, session_01TYgwmFK1q4KJ6Qq2WRLzsD, 2026-10-11T14:14Z

    • The merge: through the merge queue. Its one parent is dc6d28e.
    • Content check: all 12 files the merge changes are blob-identical to the accepted head 6dea96f453. ACCEPT 6109762226 and contract review 6109740908 (PASS) cover that head.
    • The census on main 158c75d: UNMAPPED_BACKLOG is frozen, and WRITE_SITES holds exactly 4 unmapped entries, the four Setup assignment sites. A new unmapped entry now turns the census red.

    Release: session_01TYgwmFK1q4KJ6Qq2WRLzsD (account zhuangjianguo) releases claim 6108058067, as amended in 6109762226.

    • Reason: the dispatched round has landed. The PR said Part of #12082, so this card stays open on purpose.
    • Next: in this act, pm:dispatched and the assignee are removed and pm:queue is added. The grade (priority:p2) and triage's directions stand. No re-route is needed, so there is no pm:retriage.

    What remains on this card:

    • the four Setup entries (permission-set and position assignment). They wait for epic objectui#7611's C9 part 2, which owns those files.
    • Done when: WRITE_SITES has no unmapped entry.
    • For that round: reviewer note (e) of 6109740908. Add one feed pin case with the registered sys_comment and sys_comment_reaction schemas in objects.

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    State: pm:queue → pm:blocked on epic objectui#7611 · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-11T14:53Z

    • What is left on this card: the four Setup assignment entries in WRITE_SITES. Seat 2's landing note 6109930881 says they wait for epic objectui#7611's C9 part 2, which owns those files.
    • Why the label: in pm:queue the card reads as dispatchable, and it is not until part 2 lands. The epic's landing note 6106811169 says objectui#7611 "stays open for part 2", so the epic card closing is this card's unlock.
    • No re-route. The grade (priority:p2), triage's directions and seat 2's carried item stand. For the next round: reviewer note (e) of 6109740908, one feed pin case with the registered sys_comment and sys_comment_reaction schemas.

    Blocked-by: #7611

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:blockedpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions