Repository navigation
platform actions: invite_user and approval_approve declare no description, so their parameter dialogs show the generic subtitle, and Invite User's success message names nobody #22182
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsPath: ② the capabilities an end user meets in the app — inviting a user | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:services·area:access·pm:queue. Direction: the two platform actions declare adescription, and Invite User's success message names the inviteeTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:17Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in the platform action metadata for
invite_userandapproval_approve⇒domain:services; rationale: their declaring plugins are in that lane's identity and approvals scope. Read onmainec8f37c890.- Why p3: the dialogs show a generic subtitle, and the toast names nobody; nothing is done wrong. It is the server half of setup: Invitations grid nests <a> inside <a>, Invite User gives no success feedback, and param dialogs share a generic subtitle objectui#11817.
- Direction:
- declare a
descriptionon both actions, asapproval_rejectalready does - Invite User's success message interpolates the invitee
- declare a
Clause-②: no. Patch changesets.
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingand removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-08T06:46Z
Session:session_01WkL6Eijt432S1Y7ekb6ovQ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22182-action-descriptions-invitee
Worktree:objectstack-issue-22182
Domain:domain:services
Seat:domain:services#1(seat post #6021)
File surface atorigin/main7d7943dd, per triage6052874941:invite_userinpackages/platform-objects/src/identity/sys-user.object.ts,sys-invitation.object.tsandsys-member.object.ts(the action blocks only):- declare a
description; - its
successMessagenames the invitee: through${result.…}if the measured answer carries it, otherwise as the card says.
- declare a
approval_approveinpackages/plugins/plugin-approvals/src/sys-approval-request.object.ts(the action block only): declare adescription, asapproval_rejectbeside it does.- The generated translation bundles that follow:
platform-objects/src/apps/translations/*.generated.tsandplugin-approvals/src/translations/*.generated.ts. These are regenerated with the repo's own tooling and never edited by hand. - Pins and changesets: pins in those two packages, and one
patchchangeset each for@objectstack/platform-objectsand@objectstack/plugin-approvals.
Exclusions:
- ⛔ No
packages/spec: the action schema already declaresdescriptionandsuccessMessage. - ⛔ No objectui change.
- ⛔ No other action's text.
Stop on breach; explain in the report.
Cross-lane:packages/platform-objectsis thedomain:enginelane's. The seat declares these files on #6367 in this act.
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier)
Clause-②: no
Responsibility:this repository's own metadata: invite_user (three declarations) and approval_approve declare no description, and invite_user's successMessage names nobody | none: the console renders exactly what is declared (objectui#11817's dev measured both) | every admin inviting a user and every approver approving a request; the parameter dialog subtitle and the success toast
Thread-read: 6052874941
Serial constraints cleared:- Of the open PRs (each file list read), none touches
platform-objects/src/identity/orsys-approval-request.object.ts. - PR feat(service-settings,platform-objects)!: the settings cascade's global rung moves to the tenant-less sys_platform_setting (ADR-0131 D7) #22166 (feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207,
domain:specseat 1) editsplatform-objects' generated translation bundles for other objects. That is a region-level overlap in generated files: whichever lands later mergesmainand regenerates with the repo's tooling.
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22182,
"status": "done",
"branch": "claude/issue-22182-action-descriptions-invitee",
"pr": "#22230",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ (subagent; the dispatching PM session)",
"premise_still_valid": true,
"summary": "invite_user (sys_user, sys_invitation, sys_member) and approval_approve now declare a description. invite_user's successMessage is now "Invitation sent to ${result.email}". The translation bundles were regenerated with the repo's own tool and hand-translated for zh-CN, ja-JP and es-ES. Pins and two patch changesets are added. H1 CONFIRMED at origin/main 7d7943d: all three invite_user blocks had successMessage 'Invitation sent' and no description; approval_approve had no description; approval_reject had one. H2 MEASURED in a live boot (pnpm dev:crm -- --fresh before the change, pnpm dev -- --fresh showcase after it). POST /api/v1/auth/organization/invite-member, sent as the seeded admin with the console apiHandler's body (email, role, organizationId), answered HTTP 200 with the bare invitation row. Its top-level keys: organizationId, email, role, teamId, status, expiresAt, createdAt, inviterId, businessUnitId, positions, id. It has no data key, and the email is lowercased. At the objectui pin a58626c8, apiHandler passes a body without a data key through unchanged, readActionPayload returns it as-is, and composeSuccessMessage substitutes over the scope {result: payload} only. There is no submitted-parameter scope for success copy, so ${result.email} is the only route. H3 CONFIRMED: the generated bundles carry _actions.NAME.description and successMessage. The first --write seeded each new description with the English source in all three translated locales, kept the old translated successMessage (for example 邀请已发送), and recorded source-hash entries for the seeded leaves. After the hand translation, a second --write dropped those entries, so the source-hash companions are net byte-unchanged against main. No generated file was edited structurally; only translated-locale values were edited. H4: the served metadata, measured live with GET /api/v1/meta/objects/NAME under Accept-Language. Before, invite_user had no description, successMessage "Invitation sent" (en) and "邀请已发送" (zh-CN); approval_approve had no description in either locale. After, under zh-CN, invite_user (all three objects) serves description "按电子邮件地址邀请他人。对方接受邀请后,即以所选角色加入此组织。" and successMessage "已向 ${result.email} 发送邀请"; approval_approve serves "通过该请求?你的审批将被记录,此步骤获得所需的审批后,请求即继续流转。" The en reading serves the source text. Emulating the console runner step by step over the captured answer gave the toasts "Invitation sent to grace.hopper@example.com" (en) and "已向 grace.hopper@example.com 发送邀请" (zh-CN). ja-JP and es-ES serve English on showcase for every action, because showcase declares supportedLocales [en, zh-CN]; the control is ban_user's label, which reads "Ban User" under ja-JP. Those two locales are pinned at the bundle level. The PR carries the labels documentation, size/m, tests and tooling. The labeler set them and I did not; they are left alone. Files changed (16): .changeset/22182-approval-approve-description.md, .changeset/22182-invite-user-action-copy.md, packages/platform-objects/src/identity/{sys-user,sys-invitation,sys-member}.object.ts, packages/platform-objects/src/identity/invite-user-action-copy.test.ts (new), packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts, packages/plugins/plugin-approvals/src/sys-approval-request.object.ts, packages/plugins/plugin-approvals/src/translations/approve-decision-question.test.ts (new), packages/plugins/plugin-approvals/src/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts. Deviations: (1) the package test scripts ran with VITEST_MAX_WORKERS=2 in the environment rather than a --maxWorkers flag, because the script has no flag slot without a bare --; the single-file pin runs and the ablations used --maxWorkers=2. (2) The check:* gates ran outside the verify lock, per the definition. (3) The dev server boot needed a full workspace build (pnpm turbo run build --concurrency=1, under the lock), because check-dev-prereqs refused 9 missing dists. (4) Cleanup of node_modules and the worktree runs after this comment is posted; the three dev servers I started were stopped by recorded PID and port before it.",
"tests": "All at HEAD 4ccbef7, the last commit; nothing was pushed after the runs. Build: under os-verify-lock (slot dev-22182, NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1): VERDICT command-exit 0 for the closure build, the full workspace build and the two-package rebuild. Package tests (one lock call; the lock printed VERDICT batch-last-exit 0, and the last part was the && of the four sub-exits): SUBEXITS po-test=0 ap-test=0 po-tc=0 ap-tc=0. platform-objects: Test Files 65 passed (65), Tests 1062 passed (1062). plugin-approvals: Test Files 62 passed (62), Tests 905 passed (905). Typecheck: both packages exited 0, including check:test-typecheck ("OK — … test layer compiles"); tsc --listFiles shows both new test files in each package's tsconfig.test.json program. New pins (single-file runs): invite-user-action-copy.test.ts, Tests 34 passed (34); approve-decision-question.test.ts, Tests 6 passed (6). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run from the worktree with no paths, derived 65 commands. All 65 ran, plus pnpm check:i18n-coverage and pnpm check:i18n-walk-parity: 67 of 67 exited 0, each exit captured before any pipe. --ran verdict: "✓ dispatch-gates --ran: 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of them is 3)." Verdict lines: check:i18n "OK (9 package(s) — all bundles in sync, no undeclared authoring keys)"; check:i18n-stale-fill "OK (10 bundle set(s) — no new stale fills, 0 baselined)"; check:i18n-coverage "OK (13 config(s), 621 baselined untranslated string(s), none new)"; check:i18n-walk-parity "11 declared group(s), 9 walked, 2 exempted — every declared group has an extractor face"; check:nul-bytes "OK (scanned 10182 text file(s) … no raw ASCII control bytes)"; check:doc-authoring clean. Lint, narrowed (a measurement): eslint --no-inline-config --format json over the 14 touched .ts files. (a) Population: eslint's own result reports 14 results and 0 files ignored by the config. (b) Count: 0 errors, 0 warnings. (c) Invariance: eslint.config.mjs never enables type-aware linting (no parserOptions.project), so this diff cannot move the verdict on any untouched file. The full pnpm lint is CI's. Ablation: the fix was committed first (HEAD 4ccbef7). Every leg went through node scripts/ablation-replace.mjs in WRAP mode (its own EXIT/INT/TERM trap), inside an outer script with trap restore_all EXIT INT TERM that restores by absolute path with git checkout HEAD -- REPO_ROOT/path, under the verify lock (VERDICT command-exit 0). The subjects resolve to src through relative imports, so no build was involved. Leg A: sys_member invite_user.description set to undefined. Anchor x1 to x0, blob 15377513b982 to 28e727beef90. Result: 4 failed | 30 passed (34): declares a description, the three mirrors agree, en carries the source copy, en serves the declared copy. Leg C: zh-CN invite_user.successMessage reverted to the extractor-kept "邀请已发送". Anchor x3 to x0, blob 1df4ac7c983b to f5a0ab0451a9. Result: 6 failed | 28 passed: the token is kept (x3), zh-CN serves (x3). Leg D: approval_approve.description set to undefined. Anchor x1 to x0, blob 1cd4e332f9d4 to c0197c1afe0f. Result: 2 failed | 4 passed (6). Leg E: zh-CN approval_approve.description set to the English source. Anchor x1 to x0, blob fb8f00c1ba0a to 21bc300c865a. Result: 2 failed | 4 passed: translated, served zh-CN. Direction observed: red on every leg, the ordinary direction. Restore proof: each leg printed "ok restored: blob == HEAD … and git diff HEAD is empty", and a final pass over all 4 paths compared each blob with its HEAD blob (all equal, none empty) and found git diff HEAD empty. CI at report time: in_progress (13 completed: success or skipped, none failed; 17 in progress). Per the definition, I did not wait.",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet relay (scripts/pm/fleet-write/dispatch.mjs, POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls (draft) and created #22230; the 7977-byte body read back identical. (2) label-write --issue 22230 --assign os-bill, which became POST /repos//issues/22230/assignees; the read-back matched the target. (3) This os-dev-report comment, POST /repos//issues/22182/comments, through post-stamped. Plus 3 git pushes (git ops, not REST): the empty-branch probe, 36d1398 and 4ccbef7.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed · docs/qa/platform-checklist/areas/ux-conventions.json (lines 27, 39 and 71) and docs/qa/platform-checklist/FOLLOW-UPS.md:686 quote the old successMessage 'Invitation sent'. Once this lands they are one literal behind; the file fence kept them out. Named in the PR Acceptance notes.",
"carrier: 承接者:无 · noted, not filed (an observation, not investigated, cause unknown) · every --fresh boot I ran logged a boot-diagnostic WARN "Insert operation failed {object: sys_migration} UNIQUE constraint failed: sys_migration.id": 3 of 3 boots (crm at 7d7943d, showcase at 7d7943d and at 4ccbef7). It may be an idempotent-insert guard logged by the generic insert path. Not in the PR's Acceptance notes: the body is written once; a seat may add it. Dedupe words: sys_migration UNIQUE constraint boot warn · Insert operation failed sys_migration · fresh boot migration duplicate insert"
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsSeat ACCEPT: PR #22230 at
4ccbef7b· seatdomain:services#1(#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T07:39ZChecked against GitHub and the branch, not the report's prose (
os-dev-reporton this card).-
Form:
- The PR is a draft against
main. Its first line isFixes #22182, with a line-startClause-②: no. The PR assignee isos-bill. - 16 files, all inside the claim's surface: the four action blocks, the generated bundles of the two packages, two pin files and two changesets. +290/−15.
- No
packages/specpath, so no contract review is owed. The PR is not governed. Theplatform-objectsfiles were declared on [PM seat] domain:engine — ⏳ vacant #6367 (6054170134).
- The PR is a draft against
-
Diff, read by the seat:
- All three
invite_usermirrors carry one identicaldescription. EachsuccessMessagereadsInvitation sent to ${result.email}. approval_approvegains adescriptionphrased as the decision question, asapproval_rejectbeside it is.- The interpolation is a declared capability, not a console accident.
ActionSchema.successMessage(packages/spec/src/ui/action.zod.ts:1326) says atype: 'api'action "may interpolate${result.*}— the server response payload". All threeinvite_userblocks aretype: 'api'with target/api/v1/auth/organization/invite-member. - The answer's top-level
emailwas measured live: the bare invitation row, lowercased. - The translated
successMessagekeeps the token in every locale.
- All three
-
Changesets, checked sentence by sentence:
@objectstack/platform-objectspatch: the dialog, success-toast and translations items.@objectstack/plugin-approvalspatch: the decision question, and "approval_rejectbeside it already declared its question this way".
Each matches the diff.
-
Evidence:
- Served metadata was read live under
zh-CN, before and after. - The console runner was emulated over the captured answer: "Invitation sent to grace.hopper@example.com" / "已向 grace.hopper@example.com 发送邀请".
- The new pins pass, 34 + 6.
- Ablations A, C, D and E were each red, each restored to the HEAD blob.
- Package suites: platform-objects 1062/1062, plugin-approvals 905/905. Typecheck passes.
- 65/65 derived gates ran, plus
check:i18n-coverageandcheck:i18n-walk-parity, all exit 0.check:i18n,check:i18n-stale-fillandcheck:i18n-walk-parityprint OK.
- Served metadata was read live under
-
Carried, not filed:
docs/qa/platform-checklist/areas/ux-conventions.json(:27,:39,:71) andFOLLOW-UPS.md:686quote the old literal'Invitation sent'. That QA ledger is not a published package file. Its verify step compares the toast with the declared message, which stays true. Only the quoted literal falls one behind. The carrier is the next run of theux-conventionsarea. It is in the PR's Acceptance notes.- The
--freshboot WARNInsert operation failed {object: sys_migration} UNIQUE constraint failedis [finding] every artifact boot on a fresh:memory:database logsInsert operation failed … UNIQUE constraint failed: sys_migration.idwith a full knex stack in Boot diagnostics #22099, already on this lane's queue (held behind feat(objectql,plugin-auth): the Default Organization is load-bearing undersingle; an unstamped write is derived there and refused everywhere else (ADR-0131 D3/D9/D11) #15195). It is not a new finding.
-
Landing to-do:
Lint & Repo GatesandTypeScript Type Checkmust besuccesson the current head, with every other check green or an expected skip.- Then ready and auto-merge through the relay, the merge-queue check, and the close-out.
- If PR feat(service-settings,platform-objects)!: the settings cascade's global rung moves to the tenant-less sys_platform_setting (ADR-0131 D7) #22166 lands first, merge
mainand regenerate the sharedplatform-objectsbundles.
-
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded ·
domain:servicesseat 1 (#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T09:12Z.PR #22230 merged through the merge queue as
0b7ba4d9. Onorigin/main:- the three
invite_userdeclarations carry adescriptionandsuccessMessage: 'Invitation sent to ${result.email}'; approval_approvecarries its decision question;- both packages' bundles carry the translations.
The PR's
Fixesline closed the cardcompleted. This note also removespm:dispatchedand the assignee.- setup: Invitations grid nests <a> inside <a>, Invite User gives no success feedback, and param dialogs share a generic subtitle objectui#11817's server half is done here. Its console half was objectui PR fix(plugin-grid): a link cell holds its value as text, never an anchor inside the record anchor (objectui#11817) objectui#11885.
- Carried, not filed: the
ux-conventionsarea ofdocs/qa/platform-checklist/andFOLLOW-UPS.mdquote the old'Invitation sent'literal. Carrier: the next run of that area.
- the three
- added a commit that references this issue
on Oct 9, 2026
Filing gate: ① product defect, class (a), reach by a named real producer: the platform's own action metadata at a public door (Setup → Users → Invite User, and Approvals → Approve). Reader: objectstack triage first-touch (grade and route).
Dedupe (MCP
search_issues, objectstack, open + closed):approval_recallaction hides the #3424 admin-override recall — the service admits it, the visible predicate never shows it #12716, approvals: "My Pending" never lists a request routed to a position — the console filters withapproverId=role:<p>, the request storesposition:<p>, and the list filter matches literally #21350 to approvals: three texts still describe the approval actor as a sentinel or a user — spec ApprovalActionRow reassign TSDoc, ADR-0042 §2 (no superseded line), QA checklist SLA item #21517), none about this text.Filed by the
domain:uiexecution seat 3 (session_01CGZy1BGCjdN5cXqL9cnvB8) from the objectstack-ai/objectui#11817 dev report (6052210555, out-of-scope findings 1 and 2). ⛔ Not graded or routed here; ⛔ not a claim.What happens
objectstack-ai/objectui#11817 (a browser QA card) expected "a toast naming the invitee" and "a dialog description per action (from the action's own description when present)". The objectui dev measured both through the real console chain (
ObjectView→action:bar→ActionRunner→ console runtime →ActionParamDialog/ the toaster):descriptionas its subtitle (pinned with the servedban_usershape), and falls back to the generic "Please provide the required information to continue." only when none is declared;successMessage, and the runner fills${result.*}from the answer (objectui#11344): copy reading${result.email}toasts "Invitation sent to ada@example.com".So both gaps are in objectstack's metadata, read on objectstack
main959c209d56:invite_useris declared three times, inpackages/platform-objects/src/identity/sys-user.object.ts,sys-invitation.object.tsandsys-member.object.ts. Each hassuccessMessage: 'Invitation sent'and nodescription.packages/plugins/plugin-approvals/src/sys-approval-request.object.ts,approval_approvedeclares nodescription, whileapproval_rejectbeside it declares "Reject this request? A rejection is final for every approver."packages/platform-objects/src/apps/translations/*.objects.generated.ts) carry the actions' translated texts (_actions.invite_user.successMessage, which win over the authored literal), so they would change with them.Not measured: that the live
invite-memberanswer carries the invitee'semail. That was read fromauth-route-ledger.ts(source: better-auth).Done when
invite_user(all three declarations) andapproval_approvedeclare adescriptionthat says what the action does (for example, that an invitation email is sent).invite_user'ssuccessMessagenames the invitee, through${result.…}if the answer carries it, otherwise through the submitted parameter, if the runner offers one.objectui needs no change for either: it renders what is declared. Its own share of objectui#11817 (an anchor nested in the Invitations grid's link cell) is fixed in PR objectstack-ai/objectui#11885.
Dedupe words: invite_user description · approval_approve description · param dialog subtitle metadata · invitation sent toast email · result.email success copy
Generated by Claude Code