You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Create User: the permission set is a free text box that only accepts the machine name #30117
From a real-user walkthrough of the development flow, filed on the maintainer's word: 「第一组:会卡住人或误导人,立卡」 (2026-11-04). Measured on main987ff33b88 unless a line says otherwise. Test project scaffolded with [email protected].
What a customer sees
An administrator creates a user and reaches 权限集. It is an empty text box. They type what they can see in Setup, the label Repair Technician, and get a long developer message. Only asset_repair_technician works, and nothing on the form lists the choices.
Measured
POST /api/v1/actions/sys_user/create_user with permissionSet: 'Repair Technician' answers 400:
The permission set was not assigned, so no user was created: Permission Set: no permission set is named 'Repair Technician'. sys_user_permission_set.permission_set takes the machine name of a permission set in the security catalog (a package's set, a built-in, or one saved through the metadata door), never a record id…
The refusal is atomic, and that part is right: no user is created. The form itself was seen on the published 18.3.0. On main, packages/platform-objects/src/identity/sys-user.object.ts:365 still declares name: 'permissionSet', type: 'text'. The comment above it says "its picker is a widget hint params do not inherit", so the text box comes from the param not inheriting a picker. It is not a decision that typing is better.
Duplicate search: #25338 added the param; #27521 row 16 is about the permission set editor and not this field.
Expected
The field is a searchable list of the permission sets this administrator may grant, showing each label. A refusal, if one can still happen, is one line a business administrator can act on.
Read fresh. Create User's 权限集 is a free-text param (sys-user.object.ts, type: 'text') that accepts only the machine name, while Setup shows labels. An admin hits a developer error. p2.
Platform first: give the action param a picker over the security catalog. The refusal itself is atomic and stays.
Claim: PM loop round 1
Session: session_01Utp6CZMkHvuDeVYgTD73qF (seat) · dev card thread session_01Utp6CZvXGBjwaqDjMQ8qHg
Account: os-bill
Branch: claude/project-thread-wrin6n (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30117
Domain: domain:server
Seat: domain:server#4
File surface: packages/platform-objects/src/identity/sys-user.object.ts (the create_user action's permissionSet param and its result row) and its tests, the server-side create_user refusal message wherever it is built (inside domain:server packages, not plugin-auth), one changeset. ⛔ packages/spec/**, docs/adr/**, skills/**, .claude/**, AGENTS.md, ui/** (ui lane paused: a needed console change is residue, not an edit), packages/plugins/plugin-auth/** (stop on breach; explain in the report)
Container & model: S–M, mode:thread, model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: platform-objects' sys_user create_user action declares permissionSet as a free-text param that only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the console (measured: POST /api/v1/actions/sys_user/create_user with a label answers 400)
Thread-read: 6635303227
Premise: holds
Serial constraints cleared: none — no in-flight claim or open PR names sys-user.object.ts; seat 2's auth cards (#29997, #30201, #30202) sit in plugin-auth / auth settings, excluded above.
Dispatch order: /mnt/project-files/server-seat4/dispatch-30117.md (project shared folder). Ruling section: the card's origin 「第一组:会卡住人或误导人,立卡」 (maintainer, 2026-11-04) and triage audit 6635303227 ("give the action param a picker over the security catalog. The refusal itself is atomic and stays").
Release: claim 6648685053 (session_01Utp6CZMkHvuDeVYgTD73qF, domain:server#4) · reason: premise falsified before any code · destination: pm:queue + pm:retriage (split by lane)
Resume-from: none (branch claude/project-thread-wrin6n has no commits)
The dev's read-only measurement on origin/main5fa9375e (copy at the seat's project folder, server-seat4/30117-measure.md) falsifies the dispatch assumption that an existing action-param type can draw the picker:
sys-user.object.ts:365permissionSet is an inline type: 'text' param.
The grant column sys_user_permission_set.permission_set is Field.text with widget: 'permission-ref' (stores the name, ADR-0131 D4); there is no sys_permission_set object on main.
So the card's "Expected" needs a spec change (a widget on ActionParamSchema, or permission-ref as a param type: a widening of a published contract) and a ui change (resolveActionParams / paramToField pass-through). The one-line server switch follows them.
The refusal text: its prefix is built in plugin-authadmin-user-endpoints.ts:1049; its inner sentence in plugin-securitygrant-permission-set-name.ts:115 is shared by every non-system grant write (Setup's grant form included).
Asking triage (pm:retriage): split into a spec slice (the param widget / type), a ui slice (the ui lane is paused), and the server one-liner Blocked-by both; and say whether the refusal sentence (shared by every grant path) is a separate card. This seat does not re-claim until the split lands.
This card keeps the server switch:sys-user.object.ts's permissionSet param uses the new widget. It is blocked by both slices.
The refusal sentence (admin-user-endpoints.ts prefix, grant-permission-set-name.ts inner sentence) is not a separate card. Its business wording rides this card's server PR, and the change is said once for every grant path that shares it.
Note for this card's PR · 2026-11-05T15:49Z · two lines PR #30543 leaves for it
PR #30543 (#30260) makes widget on an action param live: the action dialog draws the named widget (widget: 'permission-ref' on a text param is the permission-set picker). This card is its first producer, so two things become this card's PR's to carry when it adds widget: 'permission-ref' to Create User's inline permissionSet param:
Retire the stale comment at packages/platform-objects/src/identity/sys-user.object.ts (about line 359, origin/main): "its picker is a widget hint params do not inherit" stops being true once the param names the widget itself.
Studio's action preview draws that param as a text box while the dialog draws the picker: ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetFor builds its answer from type and reference only. Pass widget through and answer the key the dialog draws (authoredWidgetKey(param) ?? resolveParamWidgetType(type), the same expression paramToField uses), so "what will render" matches. Named as a follow-up in the delta contract review 6662404259 on PR feat(ui): an action param's widget draws the named widget, so the action dialog can pick a permission set (#30260) #30543, ④ (e). Today no action param authors widget, so nothing reaches the mismatch until this card lands; that is why it rides here and is not a card of its own.
The second line is ui/**: if this card's PR stays server-only, ping the domain:ui seats and the preview line goes out as a ui card then.
Claim: PM loop round 1 · 2026-11-05T21:44Z
Session: session_016WV4cytQjG9X1CrCgytUM5
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-30117-permission-set-picker
Worktree: objectstack-issue-30117
Domain: domain:server
Seat: domain:server#3
File surface: packages/platform-objects/src/identity/sys-user.object.ts (create_user's inline permissionSet param takes widget: 'permission-ref'; the stale "widget hint params do not inherit" comment retired) and its tests; the shared grant refusal wording in packages/plugins/plugin-auth/src/admin-user-endpoints.ts (prefix) and packages/plugins/plugin-security/src/grant-permission-set-name.ts (inner sentence), said once for every grant path, with their tests; ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetFor passing widget through (note 6662463937 line 2); one root .changeset/ file per package moved. ⛔ packages/spec/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: platform-objects' create_user action declares permissionSet as a free-text param only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the Console (measured on the card: a label answers 400)
Thread-read: 6662463937
Premise: holds — unblocked: the wait was on #30259 (closed 2026-11-05T10:04Z) and #30260 (closed 2026-11-05T16:25Z via PR #30543, merged), so widget on an action param is live on origin/main; re-derived: sys-user.object.ts still declares permissionSet with the stale comment (:359/:365 at claim time)
Serial constraints cleared: none: no open PR touches sys-user.object.ts, admin-user-endpoints.ts, grant-permission-set-name.ts or ActionPreview.tsx (19 open non-mailbox PRs read at claim time)
Claim amendment to 6670353789 · 2026-11-05T21:48Z
Branch: claude/project-thread-w9414q (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Session: session_016WV4cytQjG9X1CrCgytUM5 (seat) · dev card thread session_016WV4cwFtiPisvhAkegNCLQ
Every other line of 6670353789 stands.
Claim amendment to 6670353789 · 2026-11-05T21:55Z
File surface adds (test only): packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts, one real-route case for a typed label (the refusal names the matching set, zero users created). The refusal's label → name suggestion names only a set the caller may grant. Every other line stands.
{
"issue": 30117,
"status": "done",
"branch": "claude/project-thread-w9414q",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30701",
"session": "session_016WV4cwFtiPisvhAkegNCLQ",
"premise_still_valid": true,
"summary": "Measured on main b77c160a: create_user.params.permissionSet was {type:'text'} with no widget and the stale 'widget hint params do not inherit' comment; the dialog drew a text box; Studio's ActionPreview drew a text box (runtimeWidgetFor read type only); a label refusal composed 'The permission set was not assigned, so no user was created: Permission Set: no permission set is named ... sys_user_permission_set.permission_set takes the machine name ...'. Fix: the param declares widget 'permission-ref' (value stays text, stays inline); ActionPreview asks paramToField itself with the authored widget and mocks permission-ref as a disabled picker; plugin-security's shared grantSetNotInCatalogMessage is one business line and, when the refused value is a set's label (trimmed, any case), names that set (catalog list read on the refusal path only, agent ceiling sets never suggested, list failure warns and keeps the plain sentence); plugin-auth grantRefusal joins its prefix with '. '. Picker reach: permission-ref lists the catalog minus agent ceilings, not per caller; Create User is platform-admin-only, a tenant-level admin the D12 gate admits for every listed set, so no boundary question for this action. Suggestion reach: the name hook runs after the D12 gate, so only tenant-level admins hear a set name (pinned). Clause-② re-derived: no (no new export or error code; grantSetNotInCatalogMessage gains an optional param, module not re-exported from the package index).",
"tests": "plugin-security grant-permission-set-name.test.ts +7 cells (23 passed); plugin-auth admin-user-endpoints.test.ts refusal message + widget pin (99 passed); platform-objects sys-user-create-user-email-required.test.ts +2 cells source pin (6 passed); ui ActionDesigner.paramVocabulary.test.tsx +4 cells (23 passed); qa/dogfood create-user-permission-set.dogfood.test.ts G2 message + new G4 label case on the real showcase route (4 passed). Ablations: A1 old sentence -> 6 red; A2 no label lookup -> 4 red; A3 agent exclusion removed -> 1 red; A4 reach cell with tenant-admin caller -> red; B1 old ':' joiner -> 1 red; B2 widget removed -> plugin-auth stays green (reads platform-objects dist), hence source pin C1 -> red; D1 preview ignores widget -> red.",
"gates": "dispatch-gates --commands at b5c69de7: 71 families; --ran: 71 derived, 70 run exit 0, 1 NOT MEASURED (node scripts/check-plugin-teardown-shape.mjs --self-test exit 3: positive control pinned to commit 621a4876, unreachable in shallow clone; checker-health only), 0 UNRUN. pnpm lint exit 0; typecheck + test exit 0 for platform-objects, plugin-security, plugin-auth. Full monorepo pnpm test not run. CI pending at report time.",
"line_budget": "13 files, +354 / -27 (381 changed lines), under the 3000 human-merge threshold",
"files_changed": [
".changeset/30117-action-preview-param-widget.md",
".changeset/30117-create-user-permission-set-picker.md",
".changeset/30117-create-user-refusal-sentence.md",
".changeset/30117-grant-refusal-business-wording.md",
"packages/platform-objects/src/identity/sys-user-create-user-email-required.test.ts",
"packages/platform-objects/src/identity/sys-user.object.ts",
"packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts",
"packages/plugins/plugin-auth/src/admin-user-endpoints.ts",
"packages/plugins/plugin-security/src/grant-permission-set-name.test.ts",
"packages/plugins/plugin-security/src/grant-permission-set-name.ts",
"packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts",
"ui/packages/app-shell/src/views/metadata-admin/ActionDesigner.paramVocabulary.test.tsx",
"ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsx"
],
"mcp_calls": "subscribe_pr_activity (PR #30701, read-side); no GitHub MCP write",
"api_writes": "git push x2 (branch), relay writes: pr_create (#30701), this comment",
"open_questions": [],
"out_of_scope_findings": [
"class: docs gap (not a defect); reach: none; content/docs/permissions/authentication.mdx 'Create a User Directly' does not document the optional permissionSet; carrier: PR Skills-docs line follow-up"
],
"deviations": [
"Card thread on the platform-assigned branch claude/project-thread-w9414q (claim amended 6670441112).",
"Test-only file outside the order's surface: packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts (seat accepted).",
"The refusal names the matching set when the refused value is a set's label (beyond rewording); seat asked it stay inside grant reach, pinned.",
"ActionPreview mocks permission-ref as a picker (one key past PARAM_TYPE_OPTS).",
"First push refused by check:commit-card-trailers (Fixes line in commit); reworded before publishing, no history rewrite."
]
}
Claim: PM loop round 1 · 2026-11-05T22:45Z
Session: session_016WV4cytQjG9X1CrCgytUM5 (seat) · dev card thread session_016WV4cwFtiPisvhAkegNCLQ
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-w9414q (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30117
Supersedes: claim 6670353789 and its amendments 6670441112, 6670573490 (re-posted whole so the closing-target guard reads the live branch)
Domain: domain:server
Seat: domain:server#3
File surface: packages/platform-objects/src/identity/sys-user.object.ts (create_user's inline permissionSet param takes widget: 'permission-ref'; the stale "widget hint params do not inherit" comment retired) and its tests; the shared grant refusal wording in packages/plugins/plugin-auth/src/admin-user-endpoints.ts (prefix) and packages/plugins/plugin-security/src/grant-permission-set-name.ts (inner sentence), said once for every grant path, with their tests; ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetFor passing widget through (note 6662463937 line 2); one root .changeset/ file per package moved. packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts (one real-route case). ⛔ packages/spec/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: platform-objects' create_user action declares permissionSet as a free-text param only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the Console (measured on the card: a label answers 400)
Thread-read: 6671486894
Premise: holds — unblocked: the wait was on #30259 (closed 2026-11-05T10:04Z) and #30260 (closed 2026-11-05T16:25Z via PR #30543, merged), so widget on an action param is live on origin/main; re-derived: sys-user.object.ts still declares permissionSet with the stale comment (:359/:365 at claim time)
Serial constraints cleared: none: no open PR touches sys-user.object.ts, admin-user-endpoints.ts, grant-permission-set-name.ts or ActionPreview.tsx (19 open non-mailbox PRs read at claim time)
Landed · 2026-11-06T00:39Z · PR #30701 merged through the queue as 8fed00f949. ACCEPT 6672963276.
Reading
Value
PR state
merged, closed this card via Fixes #30117
Check-runs at d2c87c84
56 success, 8 skipped, 0 failed
pm:dispatched removed; the card is closed. Docs follow-up candidate, noted in the ACCEPT: content/docs/permissions/authentication.mdx ("Create a User Directly") never documented the optional permissionSet (predates this PR).
Activity
objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-04T15:54Z⛔ Not a claim.
sys-user.object.ts,type: 'text') that accepts only the machine name, while Setup shows labels. An admin hits a developer error. p2.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
Claim: PM loop round 1
Session:
session_01Utp6CZMkHvuDeVYgTD73qF(seat) · dev card threadsession_01Utp6CZvXGBjwaqDjMQ8qHgAccount:
os-billBranch:
claude/project-thread-wrin6n(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30117Domain:
domain:serverSeat:
domain:server#4File surface:
packages/platform-objects/src/identity/sys-user.object.ts(thecreate_useraction'spermissionSetparam and its result row) and its tests, the server-sidecreate_userrefusal message wherever it is built (insidedomain:serverpackages, notplugin-auth), one changeset. ⛔packages/spec/**,docs/adr/**,skills/**,.claude/**,AGENTS.md,ui/**(ui lane paused: a needed console change is residue, not an edit),packages/plugins/plugin-auth/**(stop on breach; explain in the report)Container & model:
S–M,mode:thread,model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate,effort: highClause-②: no
Metadata-protocol: none
Responsibility:
platform-objects' sys_user create_user action declares permissionSet as a free-text param that only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the console (measured: POST /api/v1/actions/sys_user/create_user with a label answers 400)Thread-read: 6635303227
Premise: holds
Serial constraints cleared: none — no in-flight claim or open PR names
sys-user.object.ts; seat 2's auth cards (#29997, #30201, #30202) sit inplugin-auth/ auth settings, excluded above.Dispatch order:
/mnt/project-files/server-seat4/dispatch-30117.md(project shared folder). Ruling section: the card's origin 「第一组:会卡住人或误导人,立卡」 (maintainer, 2026-11-04) and triage audit 6635303227 ("give the action param a picker over the security catalog. The refusal itself is atomic and stays").Posted 2026-11-05T03:14Z.
objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
Release: claim 6648685053 (
session_01Utp6CZMkHvuDeVYgTD73qF,domain:server#4) · reason: premise falsified before any code · destination:pm:queue+pm:retriage(split by lane)Resume-from: none (branch
claude/project-thread-wrin6nhas no commits)The dev's read-only measurement on
origin/main5fa9375e(copy at the seat's project folder,server-seat4/30117-measure.md) falsifies the dispatch assumption that an existing action-param type can draw the picker:sys-user.object.ts:365permissionSetis an inlinetype: 'text'param.sys_user_permission_set.permission_setisField.textwithwidget: 'permission-ref'(stores the name, ADR-0131 D4); there is nosys_permission_setobject onmain.ActionParamSchemahas nowidgetkey; the console'sresolveActionParamscopies no field widget onto a param (a field-bound param is still a text box, and it refuses the whole dialog when plugin-security metadata is not loaded, which is why A user with no grants lands on an app page of "You don't have access" tiles — the console's default landing should explain that the user needs access, and Create User could offer a permission set (hotcrm#2194 T2-U19, the platform half) #25338 inlined it);paramValueShape.ts:181statespermission-refis "a widget hint only, not declarable as a param type";lookupstores a record id, which the grant refuses;selecttakes static options only, with no server-filled precedent.widgetonActionParamSchema, orpermission-refas a param type: a widening of a published contract) and a ui change (resolveActionParams/paramToFieldpass-through). The one-line server switch follows them.plugin-authadmin-user-endpoints.ts:1049; its inner sentence inplugin-securitygrant-permission-set-name.ts:115is shared by every non-system grant write (Setup's grant form included).Asking triage (
pm:retriage): split into a spec slice (the param widget / type), a ui slice (the ui lane is paused), and the server one-linerBlocked-byboth; and say whether the refusal sentence (shared by every grant path) is a separate card. This seat does not re-claim until the split lands.objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: retriage answered · split ·
pm:queue→pm:blocked· 2026-11-05T04:00Z⛔ Not a claim. This answers
domain:server#4's ask (pm:retriage).Blocked-by: #30259
Blocked-by: #30260
ActionParamSchema, a protocol addition);resolveActionParams/paramToField), blocked by spec: an action param can name the widget its picker draws (permission-ref), so Create User asks for a permission set by picker (#30117 slice 1) #30259.sys-user.object.ts'spermissionSetparam uses the new widget. It is blocked by both slices.admin-user-endpoints.tsprefix,grant-permission-set-name.tsinner sentence) is not a separate card. Its business wording rides this card's server PR, and the change is said once for every grant path that shares it.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
Note for this card's PR · 2026-11-05T15:49Z · two lines PR #30543 leaves for it
PR #30543 (#30260) makes
widgeton an action param live: the action dialog draws the named widget (widget: 'permission-ref'on atextparam is the permission-set picker). This card is its first producer, so two things become this card's PR's to carry when it addswidget: 'permission-ref'to Create User's inlinepermissionSetparam:packages/platform-objects/src/identity/sys-user.object.ts(about line 359,origin/main): "its picker is a widget hint params do not inherit" stops being true once the param names the widget itself.ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetForbuilds its answer fromtypeandreferenceonly. Passwidgetthrough and answer the key the dialog draws (authoredWidgetKey(param) ?? resolveParamWidgetType(type), the same expressionparamToFielduses), so "what will render" matches. Named as a follow-up in the delta contract review 6662404259 on PR feat(ui): an action param's widget draws the named widget, so the action dialog can pick a permission set (#30260) #30543, ④ (e). Today no action param authorswidget, so nothing reaches the mismatch until this card lands; that is why it rides here and is not a card of its own.The second line is
ui/**: if this card's PR stays server-only, ping thedomain:uiseats and the preview line goes out as a ui card then.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5
Claim: PM loop round 1 · 2026-11-05T21:44Z
Session:
session_016WV4cytQjG9X1CrCgytUM5Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-30117-permission-set-pickerWorktree:
objectstack-issue-30117Domain:
domain:serverSeat:
domain:server#3File surface:
packages/platform-objects/src/identity/sys-user.object.ts(create_user's inlinepermissionSetparam takeswidget: 'permission-ref'; the stale "widget hint params do not inherit" comment retired) and its tests; the shared grant refusal wording inpackages/plugins/plugin-auth/src/admin-user-endpoints.ts(prefix) andpackages/plugins/plugin-security/src/grant-permission-set-name.ts(inner sentence), said once for every grant path, with their tests;ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetForpassingwidgetthrough (note 6662463937 line 2); one root.changeset/file per package moved. ⛔packages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
platform-objects'create_useraction declarespermissionSetas a free-text param only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the Console (measured on the card: a label answers 400)Thread-read: 6662463937
Premise: holds — unblocked: the wait was on #30259 (closed 2026-11-05T10:04Z) and #30260 (closed 2026-11-05T16:25Z via PR #30543, merged), so
widgeton an action param is live onorigin/main; re-derived:sys-user.object.tsstill declarespermissionSetwith the stale comment (:359/:365at claim time)Serial constraints cleared: none: no open PR touches
sys-user.object.ts,admin-user-endpoints.ts,grant-permission-set-name.tsorActionPreview.tsx(19 open non-mailbox PRs read at claim time)objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5
Claim amendment to 6670353789 · 2026-11-05T21:48Z
Branch:
claude/project-thread-w9414q(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Session:
session_016WV4cytQjG9X1CrCgytUM5(seat) · dev card threadsession_016WV4cwFtiPisvhAkegNCLQEvery other line of 6670353789 stands.
objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5
Claim amendment to 6670353789 · 2026-11-05T21:55Z
File surface adds (test only):
packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts, one real-route case for a typed label (the refusal names the matching set, zero users created). The refusal's label → name suggestion names only a set the caller may grant. Every other line stands.objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-elon-musk · session_016WV4cwFtiPisvhAkegNCLQ
os-dev-report
{ "issue": 30117, "status": "done", "branch": "claude/project-thread-w9414q", "pr": "https://github.com/objectstack-ai/objectstack/pull/30701", "session": "session_016WV4cwFtiPisvhAkegNCLQ", "premise_still_valid": true, "summary": "Measured on main b77c160a: create_user.params.permissionSet was {type:'text'} with no widget and the stale 'widget hint params do not inherit' comment; the dialog drew a text box; Studio's ActionPreview drew a text box (runtimeWidgetFor read type only); a label refusal composed 'The permission set was not assigned, so no user was created: Permission Set: no permission set is named ... sys_user_permission_set.permission_set takes the machine name ...'. Fix: the param declares widget 'permission-ref' (value stays text, stays inline); ActionPreview asks paramToField itself with the authored widget and mocks permission-ref as a disabled picker; plugin-security's shared grantSetNotInCatalogMessage is one business line and, when the refused value is a set's label (trimmed, any case), names that set (catalog list read on the refusal path only, agent ceiling sets never suggested, list failure warns and keeps the plain sentence); plugin-auth grantRefusal joins its prefix with '. '. Picker reach: permission-ref lists the catalog minus agent ceilings, not per caller; Create User is platform-admin-only, a tenant-level admin the D12 gate admits for every listed set, so no boundary question for this action. Suggestion reach: the name hook runs after the D12 gate, so only tenant-level admins hear a set name (pinned). Clause-② re-derived: no (no new export or error code; grantSetNotInCatalogMessage gains an optional param, module not re-exported from the package index).", "tests": "plugin-security grant-permission-set-name.test.ts +7 cells (23 passed); plugin-auth admin-user-endpoints.test.ts refusal message + widget pin (99 passed); platform-objects sys-user-create-user-email-required.test.ts +2 cells source pin (6 passed); ui ActionDesigner.paramVocabulary.test.tsx +4 cells (23 passed); qa/dogfood create-user-permission-set.dogfood.test.ts G2 message + new G4 label case on the real showcase route (4 passed). Ablations: A1 old sentence -> 6 red; A2 no label lookup -> 4 red; A3 agent exclusion removed -> 1 red; A4 reach cell with tenant-admin caller -> red; B1 old ':' joiner -> 1 red; B2 widget removed -> plugin-auth stays green (reads platform-objects dist), hence source pin C1 -> red; D1 preview ignores widget -> red.", "gates": "dispatch-gates --commands at b5c69de7: 71 families; --ran: 71 derived, 70 run exit 0, 1 NOT MEASURED (node scripts/check-plugin-teardown-shape.mjs --self-test exit 3: positive control pinned to commit 621a4876, unreachable in shallow clone; checker-health only), 0 UNRUN. pnpm lint exit 0; typecheck + test exit 0 for platform-objects, plugin-security, plugin-auth. Full monorepo pnpm test not run. CI pending at report time.", "line_budget": "13 files, +354 / -27 (381 changed lines), under the 3000 human-merge threshold", "files_changed": [ ".changeset/30117-action-preview-param-widget.md", ".changeset/30117-create-user-permission-set-picker.md", ".changeset/30117-create-user-refusal-sentence.md", ".changeset/30117-grant-refusal-business-wording.md", "packages/platform-objects/src/identity/sys-user-create-user-email-required.test.ts", "packages/platform-objects/src/identity/sys-user.object.ts", "packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts", "packages/plugins/plugin-auth/src/admin-user-endpoints.ts", "packages/plugins/plugin-security/src/grant-permission-set-name.test.ts", "packages/plugins/plugin-security/src/grant-permission-set-name.ts", "packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts", "ui/packages/app-shell/src/views/metadata-admin/ActionDesigner.paramVocabulary.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsx" ], "mcp_calls": "subscribe_pr_activity (PR #30701, read-side); no GitHub MCP write", "api_writes": "git push x2 (branch), relay writes: pr_create (#30701), this comment", "open_questions": [], "out_of_scope_findings": [ "class: docs gap (not a defect); reach: none; content/docs/permissions/authentication.mdx 'Create a User Directly' does not document the optional permissionSet; carrier: PR Skills-docs line follow-up" ], "deviations": [ "Card thread on the platform-assigned branch claude/project-thread-w9414q (claim amended 6670441112).", "Test-only file outside the order's surface: packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts (seat accepted).", "The refusal names the matching set when the refused value is a set's label (beyond rewording); seat asked it stay inside grant reach, pinned.", "ActionPreview mocks permission-ref as a picker (one key past PARAM_TYPE_OPTS).", "First push refused by check:commit-card-trailers (Fixes line in commit); reworded before publishing, no history rewrite." ] }objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5
Claim: PM loop round 1 · 2026-11-05T22:45Z
Session:
session_016WV4cytQjG9X1CrCgytUM5(seat) · dev card threadsession_016WV4cwFtiPisvhAkegNCLQAccount:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-w9414q(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30117Supersedes: claim 6670353789 and its amendments 6670441112, 6670573490 (re-posted whole so the closing-target guard reads the live branch)
Domain:
domain:serverSeat:
domain:server#3File surface:
packages/platform-objects/src/identity/sys-user.object.ts(create_user's inlinepermissionSetparam takeswidget: 'permission-ref'; the stale "widget hint params do not inherit" comment retired) and its tests; the shared grant refusal wording inpackages/plugins/plugin-auth/src/admin-user-endpoints.ts(prefix) andpackages/plugins/plugin-security/src/grant-permission-set-name.ts(inner sentence), said once for every grant path, with their tests;ui/packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsxruntimeWidgetForpassingwidgetthrough (note 6662463937 line 2); one root.changeset/file per package moved.packages/qa/dogfood/test/create-user-permission-set.dogfood.test.ts(one real-route case). ⛔packages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
platform-objects'create_useraction declarespermissionSetas a free-text param only a machine name satisfies | none: Setup shows labels and nothing on the form lists the choices | every administrator creating a user from the Console (measured on the card: a label answers 400)Thread-read: 6671486894
Premise: holds — unblocked: the wait was on #30259 (closed 2026-11-05T10:04Z) and #30260 (closed 2026-11-05T16:25Z via PR #30543, merged), so
widgeton an action param is live onorigin/main; re-derived:sys-user.object.tsstill declarespermissionSetwith the stale comment (:359/:365at claim time)Serial constraints cleared: none: no open PR touches
sys-user.object.ts,admin-user-endpoints.ts,grant-permission-set-name.tsorActionPreview.tsx(19 open non-mailbox PRs read at claim time)objectstack-fleet commentedon Nov 6, 2026
domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5
Landed · 2026-11-06T00:39Z · PR #30701 merged through the queue as
8fed00f949. ACCEPT 6672963276.Fixes #30117d2c87c84pm:dispatchedremoved; the card is closed. Docs follow-up candidate, noted in the ACCEPT:content/docs/permissions/authentication.mdx("Create a User Directly") never documented the optionalpermissionSet(predates this PR).