Repository navigation
metadata-admin: register a ref-multi:permission widget (multi-pick declared permission sets by name) so the position form's permissionSets row can bind the registry (objectstack#22682 item 3, ADR-0131 D4) #12126
Description
Activity
- addedenhancementNew feature or requestNew feature or requestdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portaland removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01TYgwmFK1q4KJ6Qq2WRLzsD
Account:zhuangjianguo
Branch:claude/issue-12126-ref-multi-permission-widget
Worktree:objectui-issue-12126
Domain:domain:ui
Seat:domain:ui#2
File surface:packages/app-shell/src/views/metadata-admin/widgets.tsx(theref-multi:permissionwidget, itsWIDGETSandWIDGET_LABELLINGentries, and aWidgetContextcatalog member for the permission-set names);packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx(feeding that list intowidgetContextonly);packages/app-shell/src/views/metadata-admin/i18n.ts(the widget's placeholder and failure strings, en and zh); the widget-labelling parity pinsSchemaForm.widgetLabelling.test.tsxand, if the widget is declared'control',__tests__/SchemaForm.controlWidgetFailureArmNaming-9931.test.tsx; new pins beside them; the docs page that lists the form widget vocabulary, if one does;.changeset/12126-ref-multi-permission-widget.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"; theClause-②: yesbelow owes a review atCONTRACT_REVIEW_TIERby an isolated reviewer before the queue
Clause-②: yes
Responsibility:n/a — not a defect card
Thread-read: none
Serial constraints cleared: none blocking. Read at 2026-10-11T07:55Z:- objectui#12089 (the objectui#7611 epic,
Part of) merged as0df67f2, and it editedResourceEditPage.tsx. The dev starts from that merged code. - The epic's declared territory ([epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194) covers the C9 readers, and this card is ADR-0131 D4 position-form work. This seat posts a cross-lane declaration there before the dev edits.
- No open objectui PR touches
packages/app-shell/src/views/metadata-admin/. objectui#12123, fix(scripts): an injected tree hashes apart from an installed one, so a local Spec Main Shape Gate reproduction cannot leave verdicts a sibling worktree replays (objectui#12114) #12125 and feat(scripts): judge JSON doc fences through the strict authoring face (objectui#5250 slice B) #12127 touchplugin-gridandscripts/. - Downstream: feat(spec): the position form picks permissionSets from the permission-set registry (ref-multi:permission) objectstack#22794 lands after this card, once objectstack's
.objectui-shapin carries it.
Clause-② is
yes:SchemaFormis re-exported from@object-ui/app-shell's entry, so a newWidgetContextmember widens its publishedwidgetContextprop type. objectui#11601 (ref:dataset) measured the same thing on its builtdist.
Generated by Claude Code
- objectui#12089 (the objectui#7611 epic,
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12126,
"status": "done",
"branch": "claude/issue-12126-ref-multi-permission-widget",
"pr": "#12136",
"session": "session_01TYgwmFK1q4KJ6Qq2WRLzsD (subagent: the parent PM's id, per the dispatch)",
"premise_still_valid": true,
"summary": "Registeredref-multi:permissionin metadata-admin's WIDGETS (labelling 'group', the field-multi/action-multi measured reason): one RefMultiWidget, parameterised by its registry binding, lists the stored names with remove buttons, offers the declared permission sets not yet picked, appends the picked NAME (the stored value stays a list of names), and flags a stored name the LOADED catalog lacks as '(not found)'; FAILED/LOADING/idle arms follow RefDatasetWidget and never fall to the raw-JSON face. WidgetContext gainspermissionSets(LoadState OF RegistryItemOption[]); ResourceEditPage loadsclient.list('permission')(whole registry, no package scope) through usePickerLoad, gated by a newformDeclaresWidget(form, 'ref-multi:permission')so no other editor sends the request; three en/zh strings in metadata-admin/i18n.ts. BEFORE on BASE 1071393 source: the row renders the announced raw-JSON textarea ('falling back to JSON'); the new pins are 19/20 red there and 20/20 green at HEAD 56761dd. Draft PR objectui#12136 open, assignee zhuangjianguo; CI in_progress at report time (17 success, 3 skipped, 22 in progress, 0 failed).",
"tests": "All at HEAD 56761dd. (1) New pins SchemaForm.refMultiPermission-12126.test.tsx (16) + ResourceEditPage.permissionSetsFeed-12126.test.tsx (4) + parity SchemaForm.widgetLabelling.test.tsx (+2 CASES rows): 'Test Files 3 passed', 90 tests. (2) BEFORE leg: widgets.tsx/ResourceEditPage.tsx/i18n.ts checked out at BASE 1071393 under a trap restoring from HEAD (on-disk count of 'ref-multi:permission' in widgets.tsx = 0), new pins: 'Tests 19 failed | 1 passed (20)'; first failure 'the announced raw-JSON fallback is on screen'; the 1 green is the no-request control; restore proven: diff-HEAD-bytes=0 and blob == HEAD for all three files. (3) Ablations via objectstack scripts/ablation-replace.mjs (anchor x1 -> x0, blob changed, restore blob == HEAD, git diff HEAD empty): A1const notFound = false;-> 3 failed | 17 passed; A2true || formDeclaresWidget(-> 1 failed | 3 passed ('the tag-box row reads no catalog, so the permission list is never asked for'). Source-resolved (relative imports), so no dist preflight applies. (4)pnpm exec vitest run packages/app-shell/src/views/metadata-admin/--shard 1..4/4: 118+118+118+117 = 471 files passed; 1307+1194+1562+1281 tests passed, 1 skipped. (5)turbo run build --filter=@object-ui/app-shell... --concurrency=2: 'Tasks: 29 successful, 29 total'; thenpnpm --filter @object-ui/app-shell type-checkechoed '@object-ui/app-shell@17.7.0 type-check' / 'tsc --noEmit && tsc -p tsconfig.test.json', VERDICT command-exit 0;tsc -p tsconfig.test.json --listFilesOnlylists the three test files. (6) H6 on built dist: index.d.ts re-exports SchemaForm; SchemaForm.d.tswidgetContext?: WidgetContext; widgets.d.tspermissionSets?: LoadStateOFRegistryItemOption[]beside positive controldatasets?:; scratch probe against dist: tsc exit 0 (two @ts-expect-error refusals hold: bare list, misspelledpermissionSet); reverse leg with a refused row keytitle: tsc exit 2, TS2353 on RegistryItemOption. (7) Root suitescripts/__tests__/--shard 1..3/3: 60+59+60 = 179 files passed, 2 skipped; ratchets column-identity.ratchet + one-authority-per-exported-name-6273: 2 files, 18 tests passed. (8) Eager closure:vite buildof apps/console at HEAD and at BASE source (trap-restored, proven): eagerGzipBytes 3250219 -> 3250896 = +677 B gzip (+3064 raw); SchemaForm chunk +619, metadata-admin i18n chunk +51, rest 1-4 B hash churn; eager chunk count 290 of 2474 both;pnpm check:eager-closureat HEAD exit 0: 'Console eager closure is 3174.7 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 29.9 KB)'. NOT MEASURED: browser drive (no backend run for this card); packages outside app-shell (public face widened additively; no in-repo consumer passes widgetContext besides app-shell itself).",
"mcp_calls": "0",
"api_writes": "2 relay strokes via objectstack scripts/pm/fleet-write/dispatch.mjs (each = POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12136/assignees [zhuangjianguo], read-back 9591 bytes sent = stored; (2) comment -> POST /repos//issues/12126/comments (this report). git push x2 (empty-branch probe, then 9df6a95..56761dd), not REST. Zero label writes, zero PR-body PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — PermissionMatrixEditor's ownclient.list('permission', {})read (the admin-scope assignable allowlist) swallows a failed load with an empty.catch, so a failure renders as an empty allowlist: the objectui#5170 shape. Read-only inference, not reproduced through a public door, file outside this card's surface. It is not in PR #12136's Acceptance notes (the body is written once); if the seat wants it there, append one Acceptance-notes bullet saying exactly this. Dedupe words: PermissionMatrixEditor allSetNames catch failure allowlist"
],
"gates": {
"vitest metadata-admin/ (4 shards)": "pass — 471 files",
"new pins + widgetLabelling parity": "pass — 3 files, 90 tests",
"app-shell type-check": "pass — VERDICT command-exit 0",
"build closure (turbo, app-shell...)": "pass — 29/29 tasks",
"root suite scripts/tests (3 shards)": "pass — 179 files, 2 skipped",
"ratchets column-identity + one-authority-6273": "pass — 2 files, 18 tests",
"check:i18n-designer-parity": "exit 0",
"check:i18n-keys": "exit 0",
"check:control-bytes": "exit 0",
"check:new-line-citations": "exit 0 — 0 new",
"check:changeset-claims": "exit 0",
"check:pending-changeset-literals": "exit 0",
"check:test-path-roots": "exit 0",
"check:vi-mock-specifiers": "exit 0",
"check:vi-mock-inherit": "exit 0",
"check:vi-mock-override-shape": "exit 0",
"check:phantom-deps": "exit 0",
"check:designer-field-key-parity": "exit 0",
"check:component-surface-parity": "exit 0 (report-only by design)",
"check-changeset-presence.mjs": "exit 0",
"check-changeset-no-major.mjs": "exit 0",
"check-governed-queue-guard --test (7 paths)": "NOT GOVERNED",
"check:eager-closure (after apps/console vite build)": "exit 0 — +677 B gzip vs BASE, headroom 29.9 KB",
"eslint (package lint formeslint .) on 6 touched files": "0 errors; +1 warning react-refresh/only-export-components on formDeclaresWidget",
"check:readme-exports": "not run — no README changed",
"CI on PR #12136": "in_progress at report time (17 success, 3 skipped, 22 in progress, 0 failed)"
},
"line_budget": "git diff --numstat 1071393..56761dd: +763 / -3 over 7 files — widgets.tsx +230/-2, ResourceEditPage.tsx +31/-1, i18n.ts +10/-0, SchemaForm.refMultiPermission-12126.test.tsx +290, ResourceEditPage.permissionSetsFeed-12126.test.tsx +177, SchemaForm.widgetLabelling.test.tsx +3, changeset +22. No skills/** or governed ledger touched, so no line ratchet applies.",
"deviations": [
"The HEAD leg of the apps/console vite build ran with NODE_OPTIONS=--max-old-space-size=6144, above the 4096 default, without a measured need; the BASE leg ran at 4096 and succeeded, so the raise was unnecessary.",
"Commit trailers and PR footer follow objectui AGENTS.md (model-freeClaude-Session+Co-authored-by: Claude) and the os-dev session-URL footer, not the harness reminder's model-namedCo-Authored-Byline and its other footer form; the reminder yields to repo rules by its own precedence clause.",
"The call running metadata-admin shards 1+2 back-to-back exceeded the foreground cap and the harness moved it to the background; I blocked on the shard-2 lock process withtail --pidand read both logs (both exit 0). Shards 3-4 and the root suite then ran detached, one per call, each waited on withtail --pid.",
"One verify-lock call returned exit 99 (queue-timeout, NOT MEASURED); it was re-run on the kept slot ui2-12126 and not counted as a result.",
"eslint was first run with--no-inline-config(the os-dev text's objectstack spelling): 3 errors, allreact-hooks/static-componentson existing lines in ResourceEditPage.tsx that carry inline disables. objectui's package lint iseslint .(inline config honoured): 0 errors. The second form is the one reported. Standing-text note for the seat: os-dev'spnpm lint = eslint . --no-inline-configis objectstack's form and does not hold in objectui.",
"The first run of ResourceEditPage.permissionSetsFeed-12126 was red because the page opens form-only types in view mode. The test now clicks Edit first (commit 56761dd); no product change."
],
"files_changed": [
".changeset/12126-ref-multi-permission-widget.md",
"packages/app-shell/src/views/metadata-admin/widgets.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/metadata-admin/SchemaForm.widgetLabelling.test.tsx",
"packages/app-shell/src/views/metadata-admin/SchemaForm.refMultiPermission-12126.test.tsx",
"packages/app-shell/src/views/metadata-admin/ResourceEditPage.permissionSetsFeed-12126.test.tsx"
]
}objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT — PR objectui#12136 (head
56761dd213) ·domain:uiseat 2,session_01TYgwmFK1q4KJ6Qq2WRLzsD, 2026-10-11T10:54ZReviewed against GitHub and the diff, not the report (
6108144870).Verdict
- Shape:
- Draft on
main, first lineFixes #12126, andClause-②: yesat a line start. Fixes #12126is the only closing keyword. feat(spec): the position form picks permissionSets from the permission-set registry (ref-multi:permission) objectstack#22794 is named with no keyword.- Assignee
zhuangjianguo.
- Draft on
- Scope: 7 files, +763/−3, all on the claim's surface (
6106885644):widgets.tsx,ResourceEditPage.tsx,i18n.ts, the widget-labelling parity pin, two new pins and one changeset.- Nothing under
content/docs/releases/. - No docs page lists the metadata-admin widget vocabulary, so no docs change is owed.
check-governed-merges --pr: NOT governed, 766 changed lines.- None of the epic's C9 files is touched, as declared on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194 (
6106890347).
- Nothing under
- What lands:
ref-multi:permissionis registered inWIDGETS.- One
RefMultiWidgetlists the stored names, each removable, and offers the declared permission sets not yet picked. A pick appends the set's name, so the stored value stays a list of names. - A stored name that the LOADED catalog lacks stays visible and is flagged
(not found). - The FAILED, LOADING and idle arms follow
RefDatasetWidget. The row never falls back to the raw-JSON face.
- One
WidgetContextgains the optionalpermissionSetsmember.ResourceEditPageloadsclient.list('permission')through the sharedusePickerLoad, and only for a form that declares the widget.- Labelling:
WIDGET_LABELLINGreads'group', asfield-multiandaction-multido. - All three "Done when" lines of the card are met.
- Changeset prose, checked sentence by sentence against the diff:
@object-ui/app-shell: minor, the published widening ofSchemaForm'swidgetContexttype. - Contract review:
6108250004on this head reads PASS, withServed-tier: CONTRACT_REVIEW_TIERandLocal-runs: none, andImplemented-byandReviewed-bysigned. - Tests:
- On BASE source, the new pins went 19 red and 1 green; the green one is the no-request control. At the head they are 20/20 green.
- Ablations A1 (the not-found flag) and A2 (the load gate) each turned their own pins red. Every restore was blob-equal.
metadata-admin/passes in 4 shards (471 files), the root suite and the ratchets pass, and the builtdistshows the new member besidedatasets?:.
- Eager closure: +677 B gzip, measured by the dev base to head. The Bundle Analysis bot reads 3174.7 KB of 3204.6 KB.
- Gates on this head (read at 2026-10-11T10:54Z): 40 success, 3 skipped by design (
dependabotand the two coverage placeholders), 0 failure.LintandType Checkboth readsuccess.
Acceptance notes (carried, not filed)
- The
(not found)pill usesborder-amber-300 bg-amber-50 text-amber-900with nodark:variant.widgets.tsxalready has both shapes: a light-only amber box, and one withdark:variants. Not a contract matter. formDeclaresWidgetdoes not follow a{ group }section reference thatSchemaFormresolves. No form served today declares the widget that way. Follow-up only.- Duplicate names: a stored list that already holds a duplicate name collides on
li key, which logs a React warning. Authored picks are deduplicated. PermissionMatrixEditor's ownclient.list('permission', {})swallows a failed load with an empty.catch(the objectui#5170 shape). This is a code reading only, outside this card, with no measured reach. Not filed.
Landing: ready and auto-merge through the merge queue now. Then objectstack's
.objectui-shamust carry this merge before objectstack-ai/objectstack#22794 can land (Restart-when:on objectstack-ai/objectstack#22682). The seat notes the merge on the epic's.objectui-shastage (objectstack-ai/objectstack#15194), which owns that file.
Generated by Claude Code
- Shape:
Filing gate: ④, the objectui layer of a cross-layer item. Its spec layer is objectstack-ai/objectstack#22682 item 3, ready as objectstack-ai/objectstack#22794 and held in draft until this lands. Acting reader: the objectui
domain:uiseat. When it lands and objectstack's.objectui-shapin carries it, objectstack'sdomain:specseat lands objectstack#22794.Dedupe: the semantic issue search "position form permissionSets permission set multi picker widget ref-multi metadata-admin" on objectstack-ai/objectstack-ai/objectui returned 1 hit, #5092 (closed, unrelated: duplicate SchemaForm ids).
What objectui needs
objectstack#22794 sets the position form's
permissionSetsrow (packages/spec/src/identity/position.form.ts) towidget: 'ref-multi:permission', where today it istype: 'tags'. This is ADR-0131 D4: references to declared items are by machine name, resolved registry-first, so the designer should offer the declared permission sets rather than free text.objectui registers no such widget today. Measured at
origin/main206505cand at objectstack's pin20c6d351ad74, which read the same at every line below:inferWidgetreturns an explicit widget first (SchemaForm.tsx:447).resolveFieldFacesends an unregistered hint to{ kind: 'raw-json', hint }(:870), rendered at:2223as a JSON textarea under "widget ref-multi:permission — falling back to JSON until a custom renderer is registered."ref:datasettook (objectui#11601, then objectstack#21714).Where (measured by the objectstack dev, report objectstack 6106502193)
packages/app-shell/src/views/metadata-admin/widgets.tsx:WIDGETSentry (:3033) and aWIDGET_LABELLINGentry (:3142);WidgetContextcatalog member for the permission-set names;RefDatasetWidget(:684) does.ref:family's list form, with the arity in the prefix and the exact registry type after the colon. The vocabulary already spells multi-value pickers as their own keys (field-ref/field-multi,select/multiselect,action-multi), and the singleref:widgets readString(value)and write one string.packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx: feed thepermissionlist intowidgetContext. The position form arrives asentry?.formfrom/meta/types(:456,:457,:2943).SchemaForm.widgetLabelling.test.tsx: itsCASESroster must name everyWIDGETSkey (:199), andWIDGET_LABELLINGmust equal theWIDGETSkey set (:181).__tests__/SchemaForm.controlWidgetFailureArmNaming-9931.test.tsx, if the widget is declared'control'.i18n.ts: the placeholder and failure strings (en and zh).permissionon both sides: specMetadataTypeSchema, wheregetMetadataTypeSchema('permission')isPermissionSetSchema, and objectuiregisterMetadataResource({ type: 'permission' })(PermissionMatrixEditor.tsx:28).Done when
permissionSetsrow, givenwidget: 'ref-multi:permission', renders a multi-picker of declared permission-set names; the stored value stays a list of names..objectui-shapin is then bumped past it, so objectstack#22794 can land.