Skip to content

fix(platform-objects,plugin-approvals): invite_user and approval_approve declare a description; Invite User names the invitee - #22230

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22182-action-descriptions-invitee
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22182-action-descriptions-invitee

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22182

Clause-②: no

What changes

  • invite_user is declared three times, on sys_user, sys_invitation and sys_member. Each declaration now has a description: "Invite someone by email address. They join this organization with the chosen role when they accept the invitation." The console shows it as the parameter dialog's subtitle. Before, the dialog showed the generic "Please provide the required information to continue."
  • invite_user's successMessage is now Invitation sent to ${result.email}. Before, it was "Invitation sent".
  • approval_approve declares a description the way approval_reject beside it does: "Approve this request? Your approval is recorded, and the request moves on once this step has the approvals it requires." The wording says "once this step has the approvals it requires" because one approval finalizes a step only under first_response or an override. Under unanimous, quorum and per_group it does not.
  • Translation bundles. I regenerated them with node scripts/check-i18n-bundles.mjs --write, per package. The extractor seeds each new description leaf with the English source in zh-CN, ja-JP and es-ES. It also keeps the old translated success message, because merge mode keeps any non-empty translated value. I translated those leaves by hand and ran --write a second time, so the provenance companions (*.source-hashes.generated.ts) dropped the copied-from-source entries the first run had recorded. Net: those companions are byte-unchanged against main. Every translated success message keeps the ${result.email} token.
  • Pins:
    • packages/platform-objects/src/identity/invite-user-action-copy.test.ts (34 cases): the three declarations, their parity, the four bundles, and the served object metadata through translateMetadataDocument('object', …) over SetupAppTranslations.
    • packages/plugins/plugin-approvals/src/translations/approve-decision-question.test.ts (6 cases): the same shape over ApprovalsTranslations.
  • Changesets: one patch each for @objectstack/platform-objects and @objectstack/plugin-approvals.

The ${result.email} choice was measured

The card did not measure what invite-member answers, so I booted the door in-repo and read the live answer. I used pnpm dev:crm -- --fresh (before the change) and pnpm dev -- --fresh (showcase, after the change), signed in as the seeded admin, and sent the body the console's api handler sends: POST /api/v1/auth/organization/invite-member with email, role and organizationId. Both runs answered HTTP 200 with the bare invitation row:

{"organizationId":"org_…","email":"grace.hopper@example.com","role":"member","teamId":null,"status":"pending","expiresAt":"…","createdAt":"…","inviterId":"…","businessUnitId":null,"positions":null,"id":"…"}

The answer has no data key, and its top-level keys are not the legacy action envelope. So, at the objectui pin a58626c8:

  1. useConsoleActionRuntime's api handler passes the body through as result.data.
  2. readActionPayload returns it unchanged.
  3. composeSuccessMessage fills ${result.email} from it. Its scope is { result: payload } only. The runner has no submitted-parameter scope for success copy, so ${result.*} is the only route.

The address comes back lowercased: better-auth stores it that way.

Served metadata, before and after

GET /api/v1/meta/objects/NAME with Accept-Language is the object-metadata read the console uses. These are the item.actions[] entries.

object · action · locale before (main 7d7943dd) after (this branch, dist rebuilt)
sys_user · invite_user · en no description · "Invitation sent" description "Invite someone by email address. …" · "Invitation sent to ${result.email}"
sys_user · invite_user · zh-CN no description · "邀请已发送" description "按电子邮件地址邀请他人。对方接受邀请后,即以所选角色加入此组织。" · "已向 ${result.email} 发送邀请"
sys_member / sys_invitation · invite_user · zh-CN no description · "邀请已发送" same as sys_user
sys_approval_request · approval_approve · en no description "Approve this request? …"
sys_approval_request · approval_approve · zh-CN no description "通过该请求?你的审批将被记录,此步骤获得所需的审批后,请求即继续流转。"

I ran the console's success-copy composition step for step over the captured answer and the served successMessage:

ja-JP and es-ES are pinned at the bundle level only. Showcase declares supportedLocales: ['en', 'zh-CN'], so those two locales serve English on every action there. The control is ban_user's label, which reads "Ban User" under ja-JP.

Tests and gates (all at HEAD 4ccbef7b22)

  • Package tests:
    • pnpm --filter @objectstack/platform-objects test: 65 files, 1062 tests, all passed.
    • pnpm --filter @objectstack/plugin-approvals test: 62 files, 905 tests, all passed.
  • Typecheck: pnpm --filter … typecheck exited 0 for both packages. Each run includes check:test-typecheck, and --listFiles shows both new test files are in the test tsc program.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands, and I ran all 65, plus check:i18n-coverage and check:i18n-walk-parity. All 67 exited 0. --ran printed "65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED". The 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"
    • check:nul-bytes: OK
  • Lint, narrowed: I ran eslint --no-inline-config --format json over the 14 touched .ts files, with 0 ignored by the config's own matching. Result: 0 errors and 0 warnings. eslint.config.mjs never enables type-aware linting, so this diff cannot move the verdict on any untouched file. The full pnpm lint is left to CI.

Ablation (fix committed first; every leg through scripts/ablation-replace.mjs WRAP mode)

The subjects resolve to src/ through relative imports, so no rebuild was involved. Each anchor hit as declared, and the blob changed on disk. Each restore was proven against the HEAD blob with git diff HEAD empty, and once more for all four paths at the end.

leg mutation pin result
A sys_member invite_user.description set to undefined 4 failed of 34 (declares a description; mirrors agree; en carries / serves the source for sys_member)
C zh-CN invite_user.successMessage reverted to the extractor-kept "邀请已发送" (3 hits) 6 failed of 34 (token kept ×3; zh-CN served ×3)
D approval_approve.description set to undefined 2 failed of 6
E zh-CN approval_approve.description set to the English source the extractor seeds 2 failed of 6 (translated; served zh-CN)

Acceptance notes


Generated by Claude Code

claude added 2 commits October 8, 2026 06:59
…ove declare a description; Invite User names the invitee

invite_user (sys_user, sys_member, sys_invitation) and approval_approve
declared no `description`, so their parameter dialogs showed the generic
subtitle. invite_user's success toast now interpolates the invitee from
the invite-member answer (`${result.email}`).

Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ
Co-authored-by: Claude <noreply@anthropic.com>
…ndles, translate the new copy, pin it

The extractor seeds the new `description` leaves with the English source and
keeps the old translated success message; both are translated here (zh-CN,
ja-JP, es-ES) and the bundles regenerated so the provenance companions drop
the copied-from-source entries. Pins cover the declarations, the bundles and
the served object metadata under zh-CN.

Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/plugin-approvals, touching 3 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/approvals.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/automation/flows.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/automation/workflows.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/environment-variables.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/self-hosting.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/tenancy-modes.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/authentication.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/delegated-administration.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/permission-sets.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/positions.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/plugins/packages.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/ui/translations.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

⛔ 6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v16.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects), sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-0.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-1.mdx (via sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-3.mdx (via sys_invitation (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects), sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-6.mdx (via sys_approval_request (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects), sys_member (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: sys_user (symbol, 38 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7b926f76007316ec13d2b17ec4b0a316b94e4d7e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55bbc88be844bc774ef546d8d3b31856d0b02d7d — the merge of head 4ccbef7b22e6af0672180e3d7bc2c096fb52efc7 into base 7b926f76007316ec13d2b17ec4b0a316b94e4d7e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55bbc88be844bc774ef546d8d3b31856d0b02d7d && git checkout 55bbc88be844bc774ef546d8d3b31856d0b02d7d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7b926f76007316ec13d2b17ec4b0a316b94e4d7e 4ccbef7b22e6af0672180e3d7bc2c096fb52efc7 && git checkout -B drift-repro 7b926f76007316ec13d2b17ec4b0a316b94e4d7e && git merge --no-ff 4ccbef7b22e6af0672180e3d7bc2c096fb52efc7

node scripts/docs-audit/affected-docs.mjs --json 7b926f76007316ec13d2b17ec4b0a316b94e4d7e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7b926f76007316ec13d2b17ec4b0a316b94e4d7e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 08:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 08:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 0b7ba4d Oct 8, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22182-action-descriptions-invitee branch October 8, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants