Skip to content

feat: Setup catalog pages read the registry (editable under single, read-only under a wall); assignment pages stay data pages; pickers list the registry (objectstack ADR-0131 D3/D7) #7611

Open

Description

@hotlong

⛔ BLOCKED — the v18 development line is not open.

Blocked-by: objectstack-ai/objectstack#15193, objectstack-ai/objectstack#15196

Part of the ADR-0131 execution tree (objectstack-ai/objectstack#15194). ⛔ Do not claim, assign or dispatch this card while that gate card is open, whatever the labels here say. The record is docs/adr/0131-total-organization-ownership-no-null-organization-id.md in objectstack-ai/objectstack (merged 2026-09-04).


In one sentence. Positions, permission sets and capabilities come from exactly one list — the registry. Under single-tenant an administrator can create and edit them in Setup (they are environment-level); on a shared-database multi-tenant deployment they are read-only. Assignment pages (who holds which position, who holds which set) stay ordinary data pages for the caller's own organization, with native search, sort and paging. Pickers list the registry. There is no merged two-source list.

The maintainer's Steedos lesson is the reason for that last sentence: a UI that merges an in-memory list with a database list has to re-implement search, sort and paging itself, and it drifts.

Scope. Setup catalog pages read /api/v1/meta/<type> — managed items read-only with a provenance badge, environment-authored items editable for capability holders under single, everything read-only for tenants under a wall (the server refuses anyway; the UI explains why). Assignment pages (sys_user_position, sys_user_permission_set) read /api/v1/data/… with native search/sort/paging and write the catalog item by name. Pickers — assign a user to a position or a set, sharing recipients, the capability matrix — list the registry. Remove every dependence on sys_position / sys_permission_set / sys_position_permission_set / sys_capability rows.

Acceptance. Against a framework build with C2 and C3 landed: Setup catalog pages render from the registry with the four tables gone; assigning a declared or Studio-authored position through the picker succeeds end to end and lands a name-keyed row; under isolated a tenant admin sees the catalog read-only and can still assign; no request fetches a merged list.

⭐ Parity gate (ADR-0131 §4 — this card fails without it). Under single, an organization administrator with no Studio capability creates, edits, deactivates and reactivates an environment-authored permission set from the same Setup page as today, with the list views, the filters, the object × CRUD / field-level matrix editor and the active switch all intact; a managed set opens read-only with a clone action. A JSON textarea in place of the matrix editor fails this card.

Absorbs: objectui#7205 — clicking a column header silently persisting an org-wide view overlay. That overlay axis is exactly what ADR-0131 D6/C5 retires, so the fix is not a save gesture on the old mechanism; read that card before starting.

Refs: ADR-0131 D3, D6, D7, §4 parity criterion · ADR-0094 D1 · objectui#7205.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 4, 2026
  2. claude commented on Sep 4, 2026

    @claude
    Contributor

    Blocked-by: objectstack-ai/objectstack#15193

    Blocked-by: objectstack-ai/objectstack#15196

    Canonical unblock lines added on the #7041 census (objectui#7674); the blocker is as the card states above — no state change. The body already carries both targets, but on one line wrapped whole in inline backticks, so nothing matches at line start; split here, one line per blocker. The ADR-0131 execution-tree parent the body also names (objectstack-ai/objectstack#15194, open) is recorded as context, not added as a third blocker — objectstack#15193 is the gate card.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    v18 pre-opening re-verification (C9): HOLDS. Nothing is implemented, and the blocker lines are incomplete

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:38Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main 6befe19c6e and objectui main c910630cf9. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.

    Blocked-by: objectstack-ai/objectstack#15204
    Blocked-by: objectstack-ai/objectstack#15206

    Holds:

    • The Setup catalog pages still read rows: apps/console/src/AppContent.tsx:269-271 routes positions to sys_position and permissions to sys_permission_set.
    • No objectui code reads the catalog's metadata for Setup.
    • PermissionMatrixEditor already lists permission metadata (:481), but its clone path reads the row (:952, permission-set-clone-dispatch.ts:35).

    Additions:

    • The runtime dependence is about 11 sites in 10 files. The card did not enumerate it. It includes:
      • AssignedUsersSection.tsx:127,149,154 (by permission_set_id);
      • CapabilityMultiSelectField.tsx:177;
      • RecipientPickerField.tsx:54;
      • RecordDetailView.tsx:2881;
      • core/actions/recordDelete.ts:96;
      • data-objectstack/src/index.ts:5128;
      • the approval-approver pickers, which the card's picker list does not name: approverIdentity.ts:100, decisionOutputParams.ts:73, useApproverDirectory.ts:114.
    • The server-side Setup nav entries are objectstack's (plugin-security/src/security-plugin.ts:1464-1466), not this card's. They move with C2/C3 on the framework side.
    • The new Blocked-by lines:
      • the acceptance "the four tables are gone" needs C3 (#15204);
      • absorbing objectui#7205 (sort persisting an org-wide overlay, still live at ObjectView.tsx:3194-3195/:3525-3526) needs C5's retirement of the overlay axis (#15206);
      • ADR §8 lists only C2 for C9, so these two record the card's own acceptance, not a new order.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    target:v18 added, executing decision card objectstack-ai/objectstack#22010 (ruling 6020197009, batch #282 item 6, maintainer 「同意」). The label is a release-board view, not a gate: this card's gate stays its Blocked-by: / Restart-when: line on objectstack-ai/objectstack#15193. Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim; no other label changes.

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Ruling: batch #310 item 1 · letter A · maintainer 「cloud 冻结在 v17 没问题,其他同意你的建议。」 2026-10-10T05:22Z

    Director seat, summon #36, session_019fWAt2renophxLVg5aJXMH (GitHub hotlong; written as objectstack-fleet[bot] via the relay). Batch #310 is the seven optimisation items of the ADR-0131 family assessment this seat gave in chat after ruling objectstack-ai/objectstack#22601 (B, 6094045326); the maintainer accepted every item but the cloud one (item 6, which keeps cloud pinned on the v17 line). Item 1 is this card. Thread-read: 6022571467.

    The ruling

    • C9 is unblocked now. Its only live blocker was feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) objectstack#15196 (C2); the registry read doors C9 consumes (GET /api/v1/meta/position, /api/v1/meta/permission, /api/v1/meta/capability) are on objectstack main since C2's S1–S5, and [Decision] ADR-0131 剩余部分(C2 余下阶段 + C3):继续分段、合并成一次切换,还是改成「registry 支撑的对象」 objectstack#22601 ruled B: the four catalog tables leave in ONE cutover with C2's remaining reader switch, and that cutover's last PR cannot merge until this card has landed and the .objectui-sha pin has moved (AGENTS.md Post-Task Checklist step 4). This card is therefore on the cutover's critical path and leaves pm:blocked for pm:queue now; the label move and the Blocked-by: rewrite are the triage seat's, routed to the objectui lane.
    • Build it by reuse, not by new pages. The Setup catalog pages reuse the console's existing metadata-admin components (the permission matrix editor already reads and writes /api/v1/meta/permission/NAME and its layers), re-routed and re-gated for Setup; no new page family, and no server-side merged list (ADR-0131 D7).
    • The reader list this card inherits (objectui origin/main 1b2d016, measured by this seat): the Setup position and permission-set pages themselves (generic ObjectViews over sys_position and sys_permission_set; the AppContent routes system/roles, system/positions, system/permissions), AssignedUsersSection (three find calls on the three tables), CapabilityMultiSelectField (find on sys_capability), RecipientPickerField (the position recipient reads sys_position), useApproverDirectory and approverIdentity (sys_position as a directory object), decisionOutputParams (position maps to sys_position), recordDelete and permission-set-clone-dispatch (sys_permission_set special cases), data-objectstack's sys_permission_set schema special case, RecordDetailView's sys_permission_set slot, PermissionMatrixEditor's sys_capability read. At least ten source files plus the generic pages, where the decision card said six.
    • The parity gate stands, and it answers the maintainer's question of this hour, verbatim 「以后 岗位、权限集是不是就不能在界面上添加了?」: under single, an organization administrator with no Studio capability still creates, edits, deactivates and reactivates positions and permission sets from the same Setup page as today; the write lands in the environment ledger through the metadata door (ADR-0131 D2 and D3; the redirect ADR-0094 D3 makes and C2's S7 write-through already performs). Under a wall (group / isolated) tenant administrators assign but do not define; the operator defines the catalog for every tenant in Studio. Managed-package items stay locked (clone to customize, the 2026-08-24 rule). Server half, owed by the cutover (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under single and refused under a wall (ADR-0131 D2/D3/D5/D13) objectstack#15204): the metadata door accepts an organization administrator's write for the position, permission and capability types under single without manage_metadata; without that acceptance this card's parity gate cannot pass, so the cutover plan names it.

    State

    • No label change in this act. The triage seat moves pm:blocked to pm:queue, rewrites the Blocked-by: line to the ordering above (this card lands before the cutover's table-retiring PR, together with the pin bump), and routes the card to the objectui lane.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Triage: executing ruling 6094171670 (batch #310 item 1). pm:blocked → pm:queue, routed to the objectui domain:ui lane

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

    Blocked-by: none

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    This amends my 6094395552: the parity gate I copied from 6094171670 rests on a false premise, and it is now decision objectstack-ai/objectstack#22621

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

    • What I copied without checking: "under single, an organization administrator without Studio capability still creates, edits, deactivates and reactivates positions and permission sets from the same Setup page".
    • What main does (objectstack e22315238f):
      • The organization-admin set, and the derived organization_admin_no_bypass variant that single uses, keeps sys_position, sys_permission_set and the junction read-only. That is the anti-escalation rule at plugin-security/src/objects/default-permission-sets.ts:426-431 and :475-477.
      • It does not hold manage_metadata either.
      • Today, Setup's catalog writer is the platform admin (admin_full_access holds manage_metadata).
    • So:
      • Keeping today's authority needs no server change.
      • "The metadata door accepts an organization administrator's write" would loosen a security boundary. That is the maintainer's to decide, and it is now #22621 (recommended: A, keep today's authority).
    • For the claimant, until #22621 is ruled:
      • The parity gate is the platform admin's: create, edit, deactivate and reactivate in Setup, as today. The organization admin reads, and the page explains why.
      • The rest of the scope and the ordering stand as in 6094395552.
  8. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Ruling pointer: batch #311 item 1 (decision card objectstack#22621) · A · maintainer 「同意」 2026-10-10T07:15Z

    Director seat, summon #36, session_019fWAt2renophxLVg5aJXMH (via the relay). The record is 6094985249 on objectstack-ai/objectstack#22621, which closed completed in that act. Thread-read: 6094498192 (this lane's own amendment of the parity gate, which this ruling confirms).

    • This corrects this seat's 6094171670. Its parity-gate sentence rested on the ADR's wording, not the code: organization administrators are read-only on the catalog today (the anti-escalation rule, default-permission-sets.ts:426-431), and the metadata door's gate is manage_metadata. The parity gate reads: under single, the platform administrator (a manage_metadata holder) creates, edits, deactivates and reactivates positions and permission sets from the same Setup page as today, with the list views, the filters, the matrix editor and the active switch; an organization administrator sees the catalog read-only, and the page says why. Under a wall, tenant administrators assign but do not define. Managed-package items stay locked (clone to customize).
    • The "server half" named in 6094171670 is withdrawn. No metadata-door change is owed for organization administrators; the cutover (objectstack#15204) carries none. Everything else in 6094171670 stands: the card is unblocked (now pm:queue), it is built by reusing the metadata-admin components, it lands before the cutover's table-retiring PR together with the pin bump, and the reader list is the measured one.

    Generated by Claude Code

  9. added
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    and removed on Oct 10, 2026
  10. 34 remaining items

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat ACCEPT: objectui PR #12159 (PR-3, item 5: Assigned users grants by name) at 45e2c0c4da; queued on green

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T20:17Z. The developer was a subagent of this seat (mode:subagent) under answer-and-amendment 6111342697. Report: 6113279608.

    Checklist, read on GitHub, not from the report:

    • PR form:
      • base main, draft;
      • line 1 is Part of #7611, line 2 is Clause-②: no;
      • the whole body is scanned: no closing keyword next to a card number;
      • the assignee is marchtian.
    • Scope: 5 files, +103 / −43. check-governed-merges --pr reads NOT governed, so this is an ordinary queue landing. No CHANGELOG.md.
    • Changesets, each sentence read against the diff:
      • the new 7611-grant-by-name.md (app-shell minor) covers the add by name, with no row read and no permission_set_id; the server's 400 shown; remove unchanged; the server floor; Clause-②: no;
      • the edit to the unreleased part-1 changeset 7611-setup-catalog-registry.md removes exactly one sentence ("A new grant still carries permission_set_id …") that this PR makes false. Fixing in the round what the round makes false is required, so the in-place edit is right.
    • Clause-②: no holds. No export, prop or locale key moves. The two removed strings were the section's own.
    • Evidence:
      • app-shell's whole suite: 1,271 files and 12,325 tests passed;
      • type-check;
      • three new pins on a fixture with no catalog row, and four ablations, each turning exactly its own pin red, with the restore proven;
      • in the browser against objectstack 72b26ed4f5 (which contains #22863), after deleting viewer_readonly's row: the base refuses with noGrantRow and sends nothing; this branch answers 201, and the holder gains the set; an unknown name answers 400 with reference_not_found, shown word for word; revoke answers 200.
    • CI at this read: 25 success, 3 skipped, 14 in progress. mergeable_state is behind (fix(app-shell): a position decision output lists the position registry and commits position names (objectui#7611) #12152 and feat(fields,app-shell): the capability select lists the capability registry; the matrix badge reads the layered envelope (part of objectui#7611) #12153 merged meanwhile); the queue rebuilds on main.

    The developer's open question (the server floor) → A, no ordering beyond the changeset's sentence:

    • The bundled console moves only with objectstack's .objectui-sha bump, which already comes after #22863.
    • The one named pairing of new objectui with an older server is cloud, whose .objectui-sha hold (not past 0df67f23 until cloud is on v18) is already with the maintainer.
    • A fallback that still writes permission_set_id would be the second dialect #22863 retired.

    Carried to PR-4, which edits PermissionMatrixEditor.tsx:

    • (a) The developer's class-a finding: an unsaved permission set (…/metadata/permission/new_name?scope=environment) still renders Assigned users with Add, and Add can only answer 400. The landing point is the showAssignments gate.
    • (b) The cloneToCustomize comment that says Assigned users "resolves the same set's record the same way" is false after this PR.

    Then: on all-green, pr_ready + automerge_enable. On MERGED, a Landed record here. 6b-2's landing no longer waits on this item once it merges.


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Landed (C9 remainder, PR-3: grant by name): objectui#12159 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR · 2026-10-11T20:54Z


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    Posted 2026-10-11T22:15Z · objectui#7611 PR-4 (item 4, the clone client) · developer: a subagent of the epic PM session_01Rerax7QTjKMPCUZxQUtPFR, mode:subagent, under answer-and-amendment 6111342697. PR: #12164 (draft), head 5523d86e5.

    {
     "issue": 7611,
     "part": "PR-4 of the remainder: census item 4 (the clone client), plus the ACCEPT 6113300670 'Carried to PR-4' items (a) and (b) and #12153's managedBy badge note, under answer-and-amendment 6111342697",
     "status": "done",
     "branch": "claude/issue-7611-clone-door-client",
     "pr": "https://github.com/objectstack-ai/objectui/pull/12164",
     "head": "5523d86e5ce5b7f96ec8c31d7d374ea65b9d9f0f",
     "session": "session_01Rerax7QTjKMPCUZxQUtPFR (mode:subagent; the epic PM's session id)",
     "premise_still_valid": true,
     "summary": "Clone to customize on a packaged permission set now opens a dialog (PermissionSetCloneDialog.tsx, the packaged-flow clone dialog's shape) asking for a new machine name and label, and POSTs exactly { name, label } to /api/v1/security/permission-sets/:name/clone through permission-set-clone.ts (fetch against apiBase, ADR-0112 envelope via actionErrorDetail, the catalog-activation.ts pattern for a server-only door). On success the routed editor opens the copy in place keeping the query string (a Setup copy opens in Setup) and shows the server's notice verbatim; an embedded host announces it by name with the notice; refusals show the server's message in the dialog. permission-set-clone-dispatch.ts, the sys_permission_set row read, the clone_permission_set dispatch, the editor's four @object-ui/react hooks that served only it, and perm.clone.actionMissing / perm.clone.rowMissing (no other reader) are removed. Carried (a): Assigned users is not rendered when the layered read answers the not-found shape (every layer null); a rejected read decides nothing; the first env-door Save under the page's name brings it back. Carried (b): the false comment went with the function; the caller-gate comment now names the clone door's metaWriteCapabilityVerdict. Texts my change made false (copy 'owned by your organization'; hint naming clone_permission_set) now say 'authored in this environment', en and zh. carryOver: left in place by measurement (see tests). managedBy badge: not a mechanical text fix, so unchanged and put in open_questions. Premise verified on base c0c2f792b against the same server state: Clone refused 'no sys_permission_set record named showcase_field_ops_delegate was found' after the row was deleted, and an unsaved set showed Assigned users with Add. One dispatch assumption did not hold: the door answers 200, not 201, for a created copy (the dispatcher's success helper); the client accepts any non-refused 2xx.",
     "tests": "Browser, objectstack 45e206e498 (contains #22881 and #22863) app-showcase --fresh on a private port, this branch's console via Vite. Rows of showcase_field_ops_delegate (has adminScope) and showcase_member_default (isDefault true) deleted in the database (the data door refuses the delete with success:false); registry still served both. At 154949786: Clone sent one request, POST /api/v1/security/permission-sets/showcase_field_ops_delegate/clone {name:field_ops_delegate_copy,label:'Field Ops Delegate (copy)'} answered 200; page moved to /apps/setup/metadata/permission/field_ops_delegate_copy?scope=environment with Save enabled, no Clone, notice verbatim, Assigned users rendered, badge Custom; no sys_permission_set request. Read back via /meta/permission: copies carry no adminScope and no isDefault (sources: adminScope present, isDefault true), no _packageId/_provenance. Taken name: 409 RESOURCE_CONFLICT, dialog open with the server message word for word, page stayed on the source. 'Bad Name': 400 VALIDATION_FAILED, name-rule sentence shown. Non-holder (auditor persona granted organization_admin: manage_org_users, setup.access, setup.write, no manage_metadata): no Clone, the manage_metadata reason shown; the door called as that user answers 403 PERMISSION_DENIED 'Saving a metadata item requires the `manage_metadata` capability.'; unauthenticated 401. Unsaved set never_saved_branch: no Assigned users and no Add; after first Save the section renders. Base run (two source files set to c0c2f792b, restored, blob equal to HEAD, git diff HEAD empty): refused rowMissing after GET /data/sys_permission_set returned 0 records; unsaved set showed Assigned users with Add. Re-measured at 5523d86e5 on a restarted console (the first Vite server kept a stale transform of i18n.ts after the base restore and rendered raw perm.clone.* keys; a fresh server served HEAD): 200, 409, 400 and the hop with copies *_copy2, same answers, copies again without adminScope/isDefault. carryOver: GET /api/v1/meta/object serves exactly 5 carryOver params across 108 objects, all on sys_permission_set's clone_permission_set (list_item, record_header), so ActionParamDialog's reader keeps its only producer on the object page after this PR; it implements the spec's ActionParamSchema.carryOver, which the spec's action form still offers, so it is not objectui-local dead code; left for stage 8's retirement. Unit: PermissionMatrixEditor.cloneToCustomize.test.tsx rewritten (fetch stubbed with the measured answers) and setupCatalog-7611 extended, 28 passed at both commits. app-shell whole suite at 154949786 through the lock: 1272 files passed and 1 skipped, 12344 tests passed and 9 skipped (held the lock 2638s on a shared box). At 5523d86e5: all PermissionMatrixEditor* suites, permission-slice*, and packages/i18n's three readers of this i18n table: 26 files, 193 tests passed; check-i18n-call-site-keys.test.ts passed at the first commit. turbo build @object-ui/app-shell^... (28/28) then app-shell type-check (tsc --noEmit and tsconfig.test.json) exit 0 at both commits; --listFilesOnly lists both changed suites and both new modules. Gates exit 0 at 5523d86e5: changeset presence/no-major/fixed/overwrite/claims, pending-changeset-literals, new-line-citations (0 new), control-bytes, i18n-keys, i18n-drift, i18n-designer-parity, vi-mock-specifiers/inherit/override-shape, test-path-roots, unreferenced-sources, metadata-write-doors, handler-key-reads, check-type-check-coverage; check:eager-closure exit 0 after a local console vite build at 5523d86e5; i18n-dead-keys report names none of this PR's keys. ESLint root config --no-inline-config on the six changed TS files: 0 errors, no rule more frequent than at base per file (no type-aware linting configured). Ablations, 12 legs via ablation-replace (anchor hit once, blob changed; restore proven blob == HEAD and git diff HEAD empty), each turning exactly its named pin red: A1 extra body key, A2 hop drops query, A3 notice removed (2 pins), A4 embedded line removed, A5 Cancel submits, A6 no-name guard off, A7 fallback replaces server message (409 and 403 pins), A8 fallback replaced, A9 canAuthor dropped from clone gate, A10 existence term dropped, A11 post-save existence write removed, A12 failed read counted as not found. A10's first attempt was refused by the tool before any run (replacement ';' already in the file); re-run with a distinct anchor. A1, A5, A7, A8 re-run at 5523d86e5 against the moved code, same red; final tree git diff HEAD empty.",
     "mcp_calls": "0",
     "api_writes": "3 REST writes, each through the fleet relay (one repository_dispatch to objectstack-ai/objectstack per stroke, executed as objectstack-fleet[bot]): pr_create = POST /repos/objectstack-ai/objectui/pulls (draft, read back 11228 bytes identical); label-write --assign marchtian = POST /repos/objectstack-ai/objectui/issues/12164/assignees (read back: assignees marchtian, labels unchanged tests and package: app-shell, set by labeler); post-stamped = POST /repos/objectstack-ai/objectui/issues/7611/comments (this report). Plus git push of the branch (3 pushes, not REST). Every other GitHub access was a GET.",
     "ci": "in_progress at report time: 21 success, 3 skipped, 18 in progress of 42 check runs on 5523d86e5 (one read, no waiting)",
     "open_questions": [
      {
       "question": "The permission-set editor's provenance badge renders managedBy 'platform' as 'Platform' (zh '平台内置', 'built into the platform'), while the spec's PermissionSetSchema.managedBy TSDoc says 'platform/user = environment config, live-edited and never touched by package seeding' and permission.form.ts offers 'Platform (environment config)'. The runtime's row vocabulary is the other reading: the seeder stamps sys_permission_set.managed_by 'platform' on admin_full_access. Not a mechanical text correction: which vocabulary the badge follows is a contract reading. Measured reach: the registry serves no managedBy on any of the showcase's permission sets (0 of 22 after the clones), no permission-set definition on objectstack main declares one, the clone door drops it, and the matrix editor has no managedBy field, so the 'platform' arm renders only for a definition an author or package declares with it; none exists in-tree.",
       "options": [
        "A: no change here; carry the split to the spec seat as an ADR-0049 enforce-or-remove reading of PermissionSetSchema.managedBy (declared, offered in the form, served by nobody) and let the badge follow whatever survives, with stage 8's row retirement. Real need: zero producers measured, so nothing users see changes. Long term: the vocabulary is decided once, in the spec. AI error: an AI author can still declare managedBy platform and read 'built in' in zh until then, a dormant mislabel. Startup: zero work now.",
        "B: zh-only text fix '平台内置' to '平台', matching en 'Platform' and the spec form's option head. Real need: none measured. Long term: keeps a three-state badge whose 'Platform' state the spec calls environment config, so it pins a reading of a split contract. AI error: removes the 'built in' claim in one locale only. Startup: one line.",
        "C: render 'platform' as the environment-config family ('Custom'), following the TSDoc, and drop perm.badge.platform. Real need: none measured. Long term: badge matches the spec as written, but decides the contract reading in the renderer. AI error: an author-set platform no longer reads as built in. Startup: small, plus a test.",
        "D: change the spec instead (if a definition's 'platform' is meant to mean platform-shipped, as the row seeder uses it), objectstack spec seat. Real need: none measured. Long term: one vocabulary across row and definition. AI error: contract says what the runtime means. Startup: a spec card and a publish."
       ],
       "recommendation": "A. Real need: the arm has no producer (measured), so B or C would spend a change on a state nobody can reach. Long term: the inconsistency is between the spec's TSDoc and the runtime's row vocabulary, so the fix belongs where the key is declared, not in a renderer; a declared-but-unserved definition key is an enforce-or-remove candidate. AI error: dormant only while no producer exists. Startup: zero work; decider is the spec seat, as a note with stage 8's managedBy/row retirement."
      }
     ],
     "out_of_scope_findings": [
      "carrier: none (noted, not filed; in the PR's Acceptance notes) · the clone door answers 200 for a created copy; the dispatch expected 201. No contract I found declares 201 for this door (its changeset names no status), so it is an observation, not class b. Dedupe words: permission-set clone door 200 201; security permission-sets clone status.",
      "carrier: none (noted, not filed) · environment, not product: after a path-scoped 'git checkout HEAD -- path' restore, the running Vite dev server kept serving the pre-restore transform of i18n.ts (raw perm.clone.* keys rendered); a restarted server served HEAD. Only a measurement hazard for base/branch browser comparisons."
     ],
     "deviations": [
      "The door answered 200 where the dispatch said 'On 201'; the client treats any non-refused 2xx as success, and the PR says so.",
      "status 'done' with CI in_progress, per os-dev: the report is due at the end of local verification; CI convergence is the PM's.",
      "The whole app-shell suite ran at the first commit only; the second commit moved one function unchanged and edited two comments, and its narrowing (editor suites, permission-slice, the i18n readers, all gates, eager closure, type-check, re-run dialog ablations) is declared here and in the PR body.",
      "No labels written beyond the assignee: objectui's path labels come from labeler.yml and the dispatch named none."
     ],
     "cleanup": "Both servers stopped by recorded PID (the --fresh database removed itself); the objectui worktree and its node_modules removed (clean, head equal to the remote branch). The objectstack worktree is removed right after this comment is posted, since the relay tools run from it."
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat ACCEPT: objectui PR #12164 (PR-4, item 4: the clone client) at 5523d86e5c; queued on green

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T22:30Z. The developer was a subagent of this seat (mode:subagent) under answer-and-amendment 6111342697. Report: 6114465681.

    Checklist, read on GitHub, not from the report:

    • PR form:
      • base main, draft;
      • line 1 is Part of #7611, line 2 is Clause-②: no;
      • the whole body is scanned: no closing keyword next to a card number;
      • the assignee is marchtian.
    • Scope: 8 files, +593 / −480. check-governed-merges --pr reads NOT governed, so this is an ordinary queue landing. No CHANGELOG.md.
    • The changeset 7611-permission-set-clone-door.md (app-shell minor; app-shell is published). Each sentence was read against the diff:
      • the dialog sends exactly { name, label };
      • no row read and no record-action dispatch; the two refusal strings are gone;
      • the copy opens in place and keeps the query string, with the server's notice;
      • refusals are shown in the server's words;
      • Assigned users is hidden for a name the server does not hold;
      • the corrected texts and the server floor.
    • Clause-②: no holds. No export, prop or published locale key moves. The two removed keys (perm.clone.actionMissing, perm.clone.rowMissing) belonged to the editor's own table.
    • The deletions were checked on objectui main (f3d5408). permission-set-clone-dispatch, buildCloneDispatch, findCloneAction and the two keys have no reader outside the files this PR edits. The one other PERMISSION_SET_OBJECT is a module-local constant in packages/core/src/actions/recordDelete.ts and is untouched.
    • The client follows the ruling (6097763840 Q1 → A, "objectui calling it with a name and a label"):
      • permission-set-clone.ts sends two keys and reads back permissionSet.name and notice;
      • no facet list lives on the client;
      • a refusal rejects with actionErrorDetail's server message;
      • the fallback text is used only for a bodiless answer, and it names the HTTP status.
    • Carried items from ACCEPT 6113300670, both done:
      • (a) existsOnServer reads false only for the all-null layered envelope; a rejected read decides nothing; the first save under the page's name sets it.
      • (b) The false comment is gone with cloneToCustomize.
    • Evidence:
      • app-shell's whole suite at 154949786: 1,272 files and 12,344 tests passed;
      • at 5523d86e5: 26 files and 193 tests, type-check exit 0, and the gate list;
      • 12 ablations, each turning exactly its own pin red, with the restore proven;
      • in the browser against objectstack 45e206e498 (contains #22881), with the source rows deleted:
        • on base, the clone refuses with rowMissing;
        • on this branch it answers 200; the copy opens in Setup with Save and drops adminScope and isDefault;
        • 409 and 400 are shown word for word;
        • a non-holder is offered no Clone, and the door answers that holder 403;
        • an unsaved name shows no Assigned users.
    • CI on 5523d86e5c: 40 success and 3 skipped by design (Test (coverage), its shard matrix, dependabot), 0 red; Lint and Type Check are success. mergeable_state is clean.

    The developer's open question (the managedBy: 'platform' badge) → A. No change here.

    • The badge's platform arm has no producer (measured: 0 of 22 sets serve managedBy, and the clone door drops it). B or C would spend a change on a state nobody reaches.
    • The split is between the spec's PermissionSetSchema.managedBy TSDoc and the runtime's row vocabulary, so it is decided where the key is declared.
    • It is carried to objectstack#15204 stage 8, which retires the sys_permission_set row and its managed_by stamp. There it is read as an ADR-0049 enforce-or-remove question on the definition key.

    Observations, none blocking:

    • The door answers 200, not 201. No contract declares 201, and the client accepts any unrefused 2xx.
    • A C9 scope line not yet built: the card body says the catalog pages are "read-only for tenants under a wall (the server refuses anyway; the UI explains why)".
      • On objectui main, the metadata admin's writability gate does not read the posture (useTenancyPosture is not consulted there).
      • A walled tenant without manage_metadata is already read-only through useCanAuthorMetadata.
      • A walled tenant with it is offered Save and Clone. Once objectstack#22895 (stage W) lands, the server refuses those with 403 naming the posture, and this PR shows that sentence as sent.
      • The seat's census (6111307114) did not list this line. It is now a C9 remainder item, to be measured after #22895 lands.

    Then:

    • Flipped ready and enqueued in this same stroke.
    • On MERGED, a Landed record.
    • This PR is one of D's (#22900) landing gates (6114241228). The other is decision objectstack#22865.

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Landed (C9 remainder, PR-4: the clone client): objectui#12164 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR · 2026-10-11T22:51Z


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Ruling recorded on objectstack-ai/objectstack#22865 (comment 6115690641, batch #318 item 1, the maintainer's word verbatim 「其他同意?」): B2, with sub-question Q3b → B. Director seat, summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T00:38Z. ⛔ Not a claim.

    For this card's third part, as the ruling's execution section says:

    • one objectui PR (Part of objectui#7611): a field widget that lists one registry type by name and stores the name only, the objectui#12136 / objectstack#22794 mechanism reachable from record forms;
    • one objectstack PR: move .objectui-sha to the head carrying the widget, declare it on the name field of the position grant and of the permission-set grant, delete the two add blocks on sys-user.page.ts (objectstack#22863 has merged, so nothing is waited on);
    • Q3b: one objectui PR on the read side, listing positions for a { source: 'registry', type } approver binding from the registry and storing the name only; no authoring surface touched; the loosened type goes when objectui moves to spec 18;
    • the dev measures first, in the browser, whether the two grant tables' related lists on the user page offer "new" and whether the row-level "remove" survives without add; if "remove" disappears, report first, add nothing.
    • Both land before 6b-2. A, A' and B1 were not taken; the decision card is closed completed.
  17. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat note: decision #22865 is ruled B2 (Q3b → B) · its three pieces are dispatched · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T01:25Z

    Ruling 6115690641 (batch #318 item 1); execution section on objectui#7611, 6115700897. Freshness: re-read on main 7de0265fc7 and objectui main d2500d8.

    • objectui PR-5 (the record-form widget). A local subagent, branch claude/issue-7611-registry-name-widget, Part of #7611.
      • One field widget lists one registry type by name and stores the name only. It is the objectui#12136 mechanism made reachable from record forms.
      • Keys are registered only for position and permission, in the record-form side's existing key convention, which the developer measures first (ref:<type> is the expected shape).
      • Pins per catalog arm, with ablations.
    • objectui Q3b (the approval read side). A local subagent, branch claude/issue-7611-approver-registry-read, Part of #7611.
      • For a { source: 'registry', type } approver binding, it lists positions from the registry and stores the name only. The record arm is unchanged, and no authoring surface or strict validation is touched.
      • The two symptoms (a truncated position approver; a free-text designer input) are measured on base first.
      • It stays disjoint from item 3a's files.
    • objectstack (pin, field declarations, the two add deletions). The 6b-2 developer session_013XKtmoR5QCUSTMjiQ3os49, branch claude/issue-15204-c9-grant-pickers.
      • Phase 1 now: the browser readings the ruling asks for. Does the user page offer "New" for the two grant lists, and does row-level "Remove" survive without add? If "Remove" disappears, that is reported first and nothing is added.
      • Phase 2 waits on PR-5's merge: move .objectui-sha, declare the widget on the two grant name fields, and delete the two add blocks on sys-user.page.ts.

    Order after this: PR-5 → the objectstack pickers PR → T1 → T2 → D (#22900). #22899 has landed (6115993646). Q3b lands independently.


    Generated by Claude Code

  18. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 7611,
    "part": "Q3b (objectstack#22865 ruling 6115690641, Q3b -> B): the approval read side lists positions for a { source: 'registry', type } approver binding from the registry and stores the name only",
    "status": "done",
    "branch": "claude/issue-7611-approver-registry-read",
    "pr": "#12174",
    "head": "61a2d3ed042ab17b10e5dfe7ac4eb475cf6f1f6e",
    "session": "session_01Rerax7QTjKMPCUZxQUtPFR (subagent; the parent seat's id)",
    "premise_still_valid": true,
    "summary": "Both symptoms reproduced in a browser on objectui main d2500d8 against objectstack main 7dbb89dc5 (app-showcase --fresh) before any PR was opened, then the read side was built. New module approverValueSources reads APPROVER_VALUE_SOURCES as published, with the registry member admitted by a type marked LOOSENED FOR SPEC 17 (remove at spec 18; a pin goes red on the bump). Which kinds are registry-bound is read off the binding, and nothing lists position. approverIdentity and useApproverDirectory (record-page approval panel and override dialog; DeclaredActionsBar needs no edit) now prettify a registry-bound ref instead of truncating it, hand it to the directory, and label it from the registry item via the metadata store's ensureType(TYPE); staffing still reads sys_user_position. In the designer, json-schema-to-fields keeps a published registry source and FlowReferenceField renders a select over GET /api/v1/meta/TYPE that commits the item name, with a manual-entry toggle, a flagged unknown name and a text box when the registry is empty. The record arm is unchanged. One i18n key (en and zh), a patch changeset, and one paragraph each in the app-shell README and the flow-designer guide. Disjoint from item 3a (#12152) and from PR-5's branch: 0 shared files with either.",
    "measured_symptoms": {
    "setup": "objectstack main 7dbb89dc5, showcase closure build exit 0, objectstack dev --seed-admin --fresh -p 4617. meta/position serves 16 items; data/sys_position answers records []. Consoles built with vite build and served by vite preview with the /api proxy, two ways: injected (OBJECTSTACK_SPEC_DIST = objectstack main packages/spec, as objectstack ships it; registry spelling present 5x, vendored spelling 0x) and vendored (spec 17.7.0). Probe: a runtime flow q3b_position_probe (PUT /api/v1/meta/flow) with approvers position manager (staffed) and position field_ops_delegate (unstaffed), unanimous, on an account update. The request it opened has pending_approvers [admin id, position:field_ops_delegate].",
    "base_injected": "Panel: Waiting on 'Dev Admin', 'positi…gate' (title position:field_ops_delegate), with no registry or sys_position read. Designer: Value cells are plain inputs ('manager', 'field_ops_delegate') with no datalist and no candidate request. BOTH SYMPTOMS CONFIRMED.",
    "base_vendored": "Panel: 'Field Ops Delegate (no current holder)', from a GET data/sys_position that came back empty. Designer: a sys_position record lookup that hydrates nothing, so both stored names render blank (placeholder shown), with 2 sys_position reads.",
    "branch_injected": "Panel: 'Field Ops Delegate Admin (no current holder)' (the registry label), read via GET /api/v1/meta/position only. Designer: 2 registry selects showing 'Project Manager (manager)' and 'Field Ops Delegate Admin (field_ops_delegate)', all 16 positions offered. Picking 'Operations (ops)' sent a save body with approvers [{type position, value ops}, {type position, value field_ops_delegate}], so only the name is stored.",
    "branch_vendored": "Panel: unchanged from base vendored (the record arm under spec 17.7.0). Designer: the same 2 registry selects as injected, because the published xRef.sources registry entry wins."
    },
    "tests": "All at head 61a2d3e. New pins: 4 files, 25 tests, green (approverValueSources.test.ts; approverIdentity.registryBinding-7611.test.tsx; FlowReferenceField.registrySource-7611.test.tsx; FlowReferenceField.injectedRegistryBinding-7611.test.tsx). Touched and consumer suites: 75 files, 'Tests 824 passed | 1 skipped (825)', VERDICT command-exit 0. pnpm --filter @object-ui/app-shell type-check exit 0, after building the @object-ui/app-shell^... closure (28/28); --listFilesOnly shows all 4 new test files in the test program. Gates exit 0: check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:i18n-designer-parity, check-changeset-presence, check-changeset-no-major, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations (0 new), check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:control-bytes, check:unreferenced-sources, check:spec-symbols, check:installed-pin-claims, check:doc-fences, check:doc-types, check:docs-route-closure. ESLint on the 11 touched ts/tsx files: 0 errors; against base the only difference is one more react-refresh/only-export-components warning in FlowReferenceField.tsx (8 vs 7), the same class as the file's existing exported helpers. Repo-wide lint is CI's. NOT MEASURED: check:readme-exports and check:doc-snippets, reason: PREREQUISITE NOT MET (unbuilt dist for the documented packages); neither gate reads this diff's two added paragraphs, which have no fenced code. CI at report time: 42 check runs, 21 success, 3 skipped, 18 in_progress, 0 failed.",
    "ablations": "8 legs in one locked script on committed 61a2d3e. Each mutation was proven on disk (anchor count 1 to 0, injected text present), each leg ran with a trap restore, each was restored by git checkout HEAD -- ABS_PATH, and each restore was proven (blob hash equal to the HEAD blob, git diff HEAD empty; final diff 0 bytes). All 8 went red: A1 designer registry branch off -> 5 failed (commit-by-name select, flagged stored name, empty-registry text box, manual toggle, injected fallback render); A2 json-schema-to-fields drops registry -> 'keeps a published registry source, beside the record arm'; A3 spec-derived registry fallback -> 'renders the registry select for a position with no published source'; A4 name arm without registry -> 'prettifies a position approver instead of truncating it' + 'composes the registry label first…'; A5 registry kinds not resolvable -> 'hands a server-unresolved position approver to the directory' + 'labels a position from the registry item…'; A6 hook registry label leg skipped -> 'labels a position from the registry item, never from the sys_position mirror'; R1 hook record leg skipped -> 'keeps the record arm on the adapter'; R2 designer record lookup off -> 'a published data source still renders the record lookup and lists no registry'.",
    "open_questions": [],
    "deviations": [
    "Measured on vite build + vite preview builds rather than the Vite dev server. The dev server with OBJECTSTACK_SPEC_DIST refuses at transform time (objectstack-spec-dist-single-zod: zod pre-bundled into .vite/deps), so the injected console can only be measured as a build. All four consoles were measured the same way, built with VITE_BASE_PATH=/ so deep links load.",
    "The console builds ran under the lock with NODE_OPTIONS=--max-old-space-size=6144 (above 4096), because each is a production bundle of the whole console from src. CI=true was set to skip compression. Each build took about 12s.",
    "The symptom needed a runtime probe flow on my --fresh backend. The showcase's own position approvers all resolve to staffed users, and the main admin_rescue default replaces a wholly empty slate, so a position: ref only persists beside a staffed approver.",
    "The branch was pushed after its first commit rather than empty before the first edit. The write probe answered without 403.",
    "Added one paragraph each to packages/app-shell/README.md and content/docs/guide/flow-designer.md (AGENTS.md #2). The dispatch did not ask for this.",
    "The PR assignee was set with label-write --assign, as the dispatch said, not through pr_create's assignees. Read-back lists assignee marchtian and the labeler's labels documentation, tests and package: app-shell."
    ],
    "api_writes": "3 relay strokes, all as objectstack-fleet[bot] via POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft #12174; read-back 10010 bytes sent = stored); (2) label-write assign -> POST /repos//issues/12174/assignees (marchtian; read back); (3) this os-dev-report -> POST /repos//issues/7611/comments. Plus git push x2 (not REST). Writes to my own local --fresh backend only (not GitHub): PUT /api/v1/meta/flow/q3b_position_probe x2, PATCH showcase_field_zoo and showcase_account records, and the designer's save during the pick check.",
    "mcp_calls": "0",
    "out_of_scope_findings": [
    "carrier: none · noted, not filed (Acceptance notes). The Approval Center inbox (ApprovalsInboxPage) labels position: approvers from the registry through its own hand-written POSITION_REF_PREFIX, not the published binding. It reads correctly under both specs; routing it through the binding would drop its labels on the vendored console until spec 18.",
    "carrier: none · noted, not filed (Acceptance notes). On the vendored console (spec 17.7.0) the panel's position label leg still reads the sys_position mirror, so a fresh environment shows the prettified name ('Field Ops Delegate') rather than the registry label ('Field Ops Delegate Admin'). This is the record arm the ruling leaves unchanged, and it ends at spec 18.",
    "carrier: none · noted, not filed (Acceptance notes). On the vendored console the designer's first render of an approval node, before the server schema arrives, uses the installed-spec record-lookup fallback, costing 2 sys_position hydration reads. This ends at spec 18."
    ]
    }

  19. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7611,
      "part": "PR-5 (ruling B2 on objectstack-ai/objectstack#22865, comment 6115690641): a record-form field widget that lists one registry type by name and stores the name only",
      "status": "needs_decision",
      "branch": "claude/issue-7611-registry-name-widget",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12175",
      "head": "b22578dc3798703ce02370b8acce34b85103385d",
      "session": "session_01Rerax7QTjKMPCUZxQUtPFR (subagent; the parent's session)",
      "premise_still_valid": true,
      "summary": "Done and pushed as draft objectui#12175, assigned marchtian. The widget, its data-source member and every pin are in. One gate is red on purpose and needs a ruling: the namespaced-type ratchet `registered-types-validate-ratchet-10859.test.ts` counts 362 refused namespaced keys against its pin of 360, because the two new `field:` keys count as refused at a node `type`, and that file says never to raise the pin (open_questions). @object-ui/fields gains `RegistryRefField`, registered as `field:position-ref` and `field:permission-ref`. It lists one registry by name, with the label beside the name, and writes the NAME only. A stored name the loaded registry lacks stays as its own option, flagged `(not found)`. The four arms follow RefDatasetWidget / RefMultiWidget: IDLE and LOADED-empty give an enabled text box for the typed name, LOADING puts the stored name in a disabled box, and FAILED shows a status notice with its cause beside an enabled text box. The catalog comes from a new optional `DataSource.listMetadataItems(type)` (@object-ui/types), which must reject on failure and never resolve `[]`. `ObjectStackAdapter` implements it over the client's `meta.getItems` (GET /api/v1/meta/TYPE): it rejects with the transport's error and refuses an answer without an items list. The form threads dataSource to both keys. Two locale keys go into all ten packs, the drift lists gain both keys (inline-edit exclusion, paramValueShape, the generated KNOWN_SCHEMA_TYPES, the two widget sweeps), and docs and a minor changeset are included. Clause-②: yes (widening).",
      "widget_keys_registered": [
        "field:position-ref (declared on a field as widget: 'position-ref'; lists GET /api/v1/meta/position)",
        "field:permission-ref (declared as widget: 'permission-ref'; lists GET /api/v1/meta/permission)"
      ],
      "convention_found": "The record form takes a field's `widget` hint only when ComponentRegistry holds `field:` + hint (`resolveRenderableWidget` / `renderFieldComponent` in @object-ui/components, matching the spec's `FieldSchema.widget` 'resolved as field:WIDGET'). A colon-qualified hint is looked up only when it is itself `field:`-prefixed (objectui#5254), so `widget: 'ref:position'` on a record field would degrade to the type renderer. The record-form side's own convention is bare kebab names in @object-ui/fields' widget map under the `field` namespace. objectstack already declares `object-ref`, `filter-condition` and `recipient-picker` on object fields, and `object-ref` is this exact shape for the `object` registry (lists /api/v1/meta/object, stores the name). So the keys are `TYPE-ref` on the `object-ref` model, not `ref:TYPE`. The objectstack follow-up must declare `widget: 'position-ref'` on sys_user_position.position and `widget: 'permission-ref'` on sys_user_permission_set.permission_set. The metadata forms' catalog read (`client.list(type)` through app-shell's MetadataClient into WidgetContext) is not reachable from @object-ui/fields, and the existing `getObjects` swallows a failure into `[]`, hence the new rejecting DataSource member.",
      "tests": "Type-check (each `type-check` script, exit 0, its tsc lines echoed): types, core, i18n, components, data-objectstack, fields, plugin-form, app-shell, cli at 0b5ce94e5; fields and data-objectstack again at b22578dc3. Union at the head b22578dc3, from the repo root through os-verify-lock: `pnpm exec vitest run --maxWorkers=2` over 33 files that name a touched list or key, plus packages/fields/ and packages/i18n/ whole. Result: `Test Files 1 failed | 362 passed | 1 skipped (364)`, `Tests 1 failed | 6984 passed | 20 skipped (7005)`, VERDICT command-exit 1. The one failure is the namespaced ratchet (`expected 362 to be 360`). New pins, all green: RegistryRefField.registryName-7611.test.tsx (10), objectFormRegistryNameWidget-7611.test.tsx (2: the real ObjectForm inside SchemaRendererProvider lists the position registry and submits `{ position: 'sales_manager' }`, plus a no-hint control), listMetadataItems-7611.test.ts (5, real client over a stubbed fetch). form-data-source-wiring now covers both keys. Gates, exit 0 (at 6c3551577; b22578dc3 changed only the hook and one adapter type): changeset presence, no-major, fixed, overwrite, claims, pending-changeset-literals; new-line-citations (0 new); control-bytes; i18n-keys, i18n-drift (2 keys added, 0 en values changed), i18n-dead-keys (report-only), i18n-designer-parity; test-path-roots; vi-mock specifiers / inherit / override-shape; type-check-coverage; doc-types, doc JSON fences, doc links, doc fence languages, doc expression carriage, doc example ids, doc-examples, doc-snippet-types (789 of 789 judged); readme-exports; unreferenced-sources; phantom-deps; self-import; registry-bare-names; prompt-keys; metadata-write-doors; handler-key-reads; element-data-source-declaration; regenerate-known-schema-types --check. sdui-registration-pins, eager-closure and eager-locale-catalogues ran against a local console `vite build` (35 turbo tasks): exit 0 after the build, exit 2 PREREQUISITE NOT MET before it. ESLint (root config, --no-inline-config, --format json) on the 27 changed TS files: RegistryRefField.tsx 0 findings; data-objectstack/src/index.ts has the same rule counts as at base d2500d8be (127 no-explicit-any warnings, and 4 pre-existing no-console errors far from the hunk); type-aware linting is not configured, so untouched files cannot move. NOT MEASURED: repo-wide `pnpm lint` (CI's).",
      "ablations": "12 legs at b22578dc3 (all committed first), each through objectstack's scripts/ablation-replace.mjs in wrap mode: the anchor must hit exactly once, the blob must change, and the restore is proven by the blob equal to HEAD and `git diff HEAD` empty. Each leg ran the four pin files; `git diff HEAD` read 0 bytes after every leg and after the batch. A1 a pick writes the label: red on 'stores the picked item NAME', the position/permission pick and ObjectForm 'submits the picked NAME'. A2 not-found option dropped: red on 'keeps a stored name the loaded registry lacks, flagged not found'. A3 items read as the name alone: 7 red, every label-beside-name pin. A4 FAILED arm removed: red on the FAILED pin. A5 LOADING arm removed: red on the LOADING pin. A6 no lister read as LOADING: red on the IDLE pin. A7 empty registry draws the picker: red on the LOADED-but-empty pin. A8 adapter resolves [] for an itemless answer: red on 'REJECTS an answer that carries no items list'. A9 adapter swallows a failed request: red on 'REJECTS a failed request'. A10 position-ref reads `permission`: red on the per-key pin, the ObjectForm pin and also the FAILED pin, whose test id names the type (direction: more red than planned). A11 position-ref unregistered: red on the ObjectForm pin. A12 the form stops threading dataSource: red on 'still threads them to the widget-hint-only pickers'; the ObjectForm pin stays green there, because the widget also reads SchemaRendererContext, as ObjectRefField does. An earlier full pass at 6c3551577 gave the same 12 reds before the hook refactor; A6 and A9 changed anchor with the refactor.",
      "browser_measurement": "objectstack main 7dbb89d, my own shallow clone, showcase closure built with OS_SKIP_DTS=1 under the lock. `examples/app-showcase objectstack dev --seed-admin --fresh -p 4610`, with a throwaway uncommitted edit adding `widget: 'position-ref'` / `widget: 'permission-ref'` to the two grant fields; the served object metadata carried both. This branch's console ran via `DEV_PROXY_TARGET=http://localhost:4610 vite --port 5610 --strictPort` and was driven in Chromium (/opt/pw-browsers/chromium). /apps/setup/sys_user_position/new: the Position field is the picker, listing all 16 items of GET /api/v1/meta/position (first: 'Auditor (auditor)', 'Client Liaison (client_liaison)', …). I picked Auditor plus the user Dev Admin and saved: POST /api/v1/data/sys_user_position sent `{\"user_id\":\"…\",\"position\":\"auditor\"}` → 201. /apps/setup/sys_user_permission_set/new: lists all 17 items of GET /api/v1/meta/permission; picked Showcase Auditor and saved: sent `{\"user_id\":\"…\",\"permission_set\":\"showcase_auditor\"}` → 201. The saved position grant's edit form shows 'Auditor (auditor)', not flagged. A fresh database's 'Set the workspace timezone' prompt overlays the page first and was dismissed with Escape. Afterwards the throwaway edit was reverted (`git diff HEAD` 0 bytes) and both servers were stopped by their recorded process trees (ports 4610 and 5610 closed).",
      "open_questions": [
        {
          "question": "The namespaced-type ratchet (registered-types-validate-ratchet-10859.test.ts, objectui#10872 half) pins NAMESPACED_REFUSED_AT_TYPE = 360 and now reads 362. Every `field:` key is refused at a node `type`, because a field widget is authored through a field's widget / type, not as a page node. These are the first new `field:` registrations since the ratchet: the two newest field widgets I dated, permission-facet-link and recipient-picker, landed 2026-07-13, and the ratchet landed 2026-09-28. The file says arm or take it to the card, never raise. Which?",
          "options": [
            "A: raise the pin to 362 with a named row pinning field:position-ref and field:permission-ref as refused field-widget keys, in the shape of the cloud: rows. Cost: one constant plus one row, against that file's 'never rises' rule.",
            "B: arm both keys in @object-ui/types/zod, as cloud:plan-status registered with its arm. Cost: invents a root-node authoring face for a field widget the spec has no row for (#0), and no other field: key has one.",
            "C: rule that field: keys leave the namespaced population (not authored at a node type) and lower the pin by their count. Cost: a population change to a ruled gate, its own card and ruling."
          ],
          "recommendation": "A in this PR, and C as its own card. The two keys are the same kind as the field: keys the pin already counts, and naming them keeps the raise from masking a regression. B publishes an authoring surface the spec does not have."
        }
      ],
      "deviations": [
        "Worked in a fresh standalone clone (pr5-objectui), as the dispatch allows, not a linked worktree. The branch probe push (at base d2500d8be) went out after the first edits, not before them.",
        "The heavy-work lock and the ablation helper were run from the PM's wt-main copies of scripts/pm/os-verify-lock.sh and scripts/ablation-replace.mjs. The lock script is byte-identical to /home/user/objectstack's (diff -q) and uses the same /tmp/os-heavy-verify.lock. One early read-only grep of /home/user/objectstack/packages/spec/src/data/field.zod.ts happened before I switched to the installed spec and my own objectstack clone. Nothing under /home/user was written.",
        "Keys are position-ref / permission-ref, not ref:position / ref:permission: the record-form convention measured above, which the dispatch allowed.",
        "The namespaced ratchet is left red; the pin is not raised (open_questions).",
        "Commit trailers follow objectui AGENTS.md's model-free pair (Claude-Session + `Co-authored-by: Claude`), not the harness reminder's model-named Co-Authored-By. The PR body ends with the os-dev session-URL footer.",
        "Files beyond the widget, each forced by registering the keys or by a claim the change made false: the core expand-fields comment (it counted 'three' picker names), the generated cli KNOWN_SCHEMA_TYPES, app-shell paramValueShape, the two fields widget sweeps and the plugin-detail inline-edit bucket snapshot.",
        "No labels written: objectui path labels come from labeler.yml (11 present), and the dispatch named none."
      ],
      "mcp_calls": "0",
      "api_writes": "2 relay writes, plus this report comment (see its own result): (1) pr_create through scripts/pm/fleet-write/dispatch.mjs, i.e. POST /repos/objectstack-ai/objectstack/dispatches, relay run 38196716334, which made POST /repos/objectstack-ai/objectui/pulls, objectui#12175 draft (read-back: 13272 bytes sent, 13272 stored, identical); (2) PR assignee through scripts/pm/label-write.mjs --assign marchtian, i.e. POST /repos/objectstack-ai/objectstack/dispatches, relay run 38196758836, which made POST /repos/objectstack-ai/objectui/issues/12175/assignees (read-back MATCHES, assignees marchtian); (3) this os-dev-report comment through scripts/pm/post-stamped.mjs. git push x3 to claude/issue-7611-registry-name-widget: the probe at base, 6c3551577, then b22578dc3 (not REST). A git fetch into a private ref for the read-back is a read.",
      "out_of_scope_findings": [
        "carrier: none · ObjectRefField (object-ref) reads DataSource.getObjects, which resolves [] on any failure, so a failed object catalog reads as 'No objects found' (the objectui#5170 class, on the record-form side). Read only, not reproduced at a public door. Noted in the PR's Acceptance notes, not filed. dedupe words: object-ref getObjects failure empty catalog",
        "carrier: none · the record detail page shows a grant's stored name itself, not 'label (name)': DetailSection draws a field's widget only for permission-facet-link. Observation, not a defect; noted, not filed."
      ]
    }

    Generated by Claude Code

  20. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat answer: PR-5 (objectui#12175) open question → A, a named raise of the namespaced ratchet by exactly its two field: keys · C filed as objectui#12176 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T02:47Z

    On the report 6116803040. registered-types-validate-ratchet-10859.test.ts pins NAMESPACED_REFUSED_AT_TYPE = 360 and reads 362 at b22578dc3. The two new keys are field:position-ref and field:permission-ref.

    The authorability question the file sends to objectui#10872 has a spec answer. That card is closed (completed, 2026-10-04), so the answer is recorded here:

    • A field: key is authored through a field's widget, which the spec's FieldSchema.widget resolves as field:WIDGET. It is never a page node's type.
    • The 360 the card closed on already counts the existing field: keys (52 in the generated list on main 2498a0753) as refused for exactly that reason. The batch-8 row also keeps the 28 field:TYPE keys registered while it retires their bare twins.
    • So the two new keys are refused at type by design. They are not a lost arm, and option B (an arm) would invent a node-authoring face the spec has no row for.

    Answer: A, in this PR.

    • Raise NAMESPACED_REFUSED_AT_TYPE to 362.
    • Add a named constant, REFUSED_FIELD_WIDGET_KEYS_7611 = ['field:position-ref', 'field:permission-ref'], citing this record.
    • Add a row asserting that both keys are in NAMESPACED_KEYS and that each is refusedAtType.
    • Extend the pin comment by one clause naming the two keys, this record, and objectui#12176.

    The ratchet's ⛔ exists so that a raise cannot hide a lost arm. With the two keys named and pinned, the other 360 must still read 360, so a regression elsewhere still turns the file red.

    C is filed as objectui#12176: the namespaced population counts field: keys that are by construction never authored at type. As things stand, every future field widget needs this same named raise. Whether field: keys leave the population for an exact list of their own (lowering the pin by their count) is a change to a ruled gate, so it belongs on that card and not inside a widget PR.

    Four axes:

    • Real need: the registry name pickers are B2's ruled deliverable, and D (#22900) waits on them.
    • Long term: A is two lines plus one row, removed or folded by C. C is the structural answer.
    • AI error: with the keys named, a later author sees exactly which raise was ruled and why. An unnamed raise would teach "raise to pass".
    • Startup: one constant and one row now, versus holding PR-5, D and the stage-8 chain on a gate redesign.

    Also accepted from the report:

    • The keys are position-ref and permission-ref, not ref:position. The dispatch said "expected", and the record form looks up only field:-prefixed widget hints (objectui#5254). object-ref is the same shape for the object registry.
    • The objectstack pickers' phase 2 therefore declares widget: 'position-ref' on sys_user_position.position and widget: 'permission-ref' on sys_user_permission_set.permission_set.

    Then: the developer applies A on its branch. Because Clause-②: yes (widening), a contract review is owed on the resulting head: a new DataSource.listMetadataItems member in @object-ui/types, and two registered widget keys.


    Generated by Claude Code

  21. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Landed: objectui PR #12174 (Q3b, the approver registry read) · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T03:11Z

    • Merged through the merge queue at 2026-10-12T03:10Z as 83b9fade2, a squash onto 2498a0753 (seat ACCEPT 6116867935).
    • Content check, comparing the head 1da854635 with the merge commit across the PR's 14 files:
      • 13 files are byte-identical.
      • packages/app-shell/src/views/metadata-admin/i18n.ts differs only by main's own change since the PR's base d2500d8be (objectui#12168, e5311165a). The PR's hunk on that file is identical in the merge commit.
    • What it closes: objectstack#22865's Q3b (ruled B, 6115690641). With a registry-bound approver binding, a position approver reads its label from the registry, and the flow designer lists the position registry and stores the name. The data arm is unchanged under the installed spec 17.7.0.
    • Carried, not filed: the report's three spec-18 notes (the Approval Center inbox prefix, the vendored mirror label, and the vendored first-render reads). They go to objectui's move onto spec 18.
    • It reaches objectstack's console with the next .objectui-sha move. The objectstack pickers PR moves the pin once PR-5 (objectui#12175) lands.
    • feat: Setup catalog pages read the registry (editable under single, read-only under a wall); assignment pages stay data pages; pickers list the registry (objectstack ADR-0131 D3/D7) #7611 stays open for PR-5 and its other parts.

    Generated by Claude Code

  22. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Pointer: the caller tier of the metadata editor's designer auto-on · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-12T03:24Z

    ⛔ Not a claim, ⛔ no label change. This is carried to this epic because the caller tier is its family's. Source: the out-of-scope finding in objectui#12169's dev report (6117149945, PR objectui#12181).

    • Measured by a probe render on that branch:
      • Setup: useCanAuthorMetadata returns false, and the item is a writable runtime-only item of a designer-capable type.
      • Result: ResourceEditPage's designer auto-on effect still enters design mode. The canvas takes edits and the page reports unsaved changes. There is no Save, and the capability banner shows.
    • Remedy shape:
      • The effect reads canWrite (itemWritable && canAuthor) rather than itemWritable.
      • That means hoisting useCanAuthorMetadata above the effect.
      • objectui#12181 introduces itemWritable as one value that both the effect and Save read, so this becomes a one-line change once it lands.
    • Dedupe words: "designer auto-on design mode canAuthor", "ResourceEditPage editing without manage_metadata".

    Generated by Claude Code

  23. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Landed: objectui PR #12175 (PR-5, the record-form registry-name widget) · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T03:38Z

    • Merged through the merge queue at 2026-10-12T03:37Z as 088e0f705, a squash onto 83b9fade2 (contract review PASS 6117097871, seat ACCEPT 6117114171).
    • Content check, comparing the head 8237a1cd8 with the merge commit across the PR's 32 files:
      • 22 files are byte-identical.
      • The 10 locale packs (packages/i18n/src/locales/*.ts) differ only by main's own change since the PR's base d2500d8be (objectui#12167, 82bb1d584). The PR's hunk in each of the 10 is identical in the merge commit.
    • What it delivers: objectstack#22865's B2 widget half.
      • widget: 'position-ref' / 'permission-ref' on a text field lists that registry and stores the name.
      • It is backed by the new optional DataSource.listMetadataItems.
      • The namespaced ratchet was raised to 362 by name (seat answer 6116842485). The population question is objectui#12176.
    • Next, on objectstack: the pickers PR (session_013XKtmoR5QCUSTMjiQ3os49) moves .objectui-sha to 088e0f705 or later, declares the two widgets on the grant name fields, deletes the user page's two add blocks, and hides permission_set_id. That PR gates D (objectstack#22900).
    • feat: Setup catalog pages read the registry (editable under single, read-only under a wall); assignment pages stay data pages; pickers list the registry (objectstack ADR-0131 D3/D7) #7611 stays open for its remaining parts, including the walled read-only UI item.

    Generated by Claude Code

  24. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Handoff record (audit only): the remaining C9 item, catalog pages read-only for tenants under a wall with the reason shown, is now #12183 (domain:ui, priority:p1, pm:queue). This was done at the maintainer's request (2026-10-12) to split clear next-step work to lane PMs. objectstack#22895 (stage W) is on main, so the server already refuses; the UI half is #12183's. Marketplace install under a wall (objectstack W2, in flight) is not in #12183; the epic seat routes it after W2 lands. This card stays with the epic seat for its other parts. Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T03:56Z


    Generated by Claude Code

  25. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Pointer: two more caller-tier gaps in the metadata editor family · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-12T05:02Z

    ⛔ Not a claim, ⛔ no label change. Source: the out-of-scope findings in objectui#12183's dev report (6118051883, PR objectui#12185). This adds to pointer 6117177535, whose designer auto-on half objectui#12183 now carries (it lands with PR objectui#12185).

    1. The standalone history route offers rollback to every caller. ResourceHistoryPage passes onRollback to every caller. It is reached from the permission-set editor's History button and by URL.
      • The rollback door refuses a walled tenant and any caller without manage_metadata.
      • The editor's own History sheet now reads canWrite (PR objectui#12185). The standalone page does not.
      • Fix shape: read the same item and caller gates there.
      • Dedupe words: "ResourceHistoryPage rollback canWrite", "HistoryPanel onRollback ungated".
    2. The matrix badge names the wrong tier. For a non-walled caller without manage_metadata, PermissionMatrixEditor's identity badge falls into the type branch and reads "Read-only (this metadata type has no runtime write channel)". That is false for permission. The reason is the caller's capability.
      • Dedupe words: "perm.readOnly badge canAuthor", "matrix badge names type tier".

    Both are code reads; no public-door reach was measured.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreepriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions