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
Setup Invitations: with no mail transport the toast says "Invitation sent to …" and there is no Copy invite link, so the invitation dead-ends (#27521 row 6) #27797
Ruled: 6512831254 · letter E · 2026-10-30T01:40Z · server half #27843 (wait for a server seat; #27797 is Blocked-by #27843)
Filing gate: ③ triage-directed split. Triage on #27521 (comment 6495068090) said: "row 6 … and row 27 … Each becomes its own p2 card, because both are wrong statements to an administrator, not suggestions." The seat files the split before it picks rows.
Reach: measured on the 18.3.0 RC browser walkthrough (wave anchor #27377, W3-UX track 2), framework main @ c4c515ad, stock examples/app-showcase with --seed-admin, admin, desktop 1440×900.
Business reason: an administrator who invites someone on a deployment with no mail transport is told the invitation was sent. Nothing was sent, and there is no way to hand the link over by hand, so the invitee never arrives and nobody knows why.
Reader who acts: domain:ui (Setup → Invitations, the invite toast, the row menu and the detail drawer). The platform already knows whether mail can leave: IEmailService.isConfigured?() answers false for the log transport (#25335).
Duplicate search: search_issues "invitation sent toast no mail transport copy invite link" → 2 hits, both closed: #25335 (the Setup → Email page states the live transport; not the invite toast or a copy action) and #8090 (a broken copied-link URL in the workspace Invitations tab, not_planned). Neither covers this.
Admin, desktop, Setup → Invitations → Invite. On the stock deployment, which has no mail transport, the toast says "Invitation sent to …". There is no Copy invite link in the row menu or in the detail drawer, so the invitation dead-ends.
Expected
With no mail provider configured, the confirmation reads "Invitation created — copy the link to share it" (or similar). It never says "sent".
Claim: PM loop round 3 · 2026-10-29T23:47Z
Session: session_01Utp6CY2n1JmZZKrHgtp9jC (seat; implementation by a card thread, mode:thread)
Account: os-bill
Branch: claude/project-thread-18obwq (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
Worktree: objectstack-issue-27797
Domain: domain:ui
Seat: domain:ui#2
File surface: the invite dialog / toast, the Setup → Invitations row menu and detail drawer (measured and named by the dev), their tests, i18n keys in the ten packs, a changeset; ⛔ packages/spec/**, every server file (a server change to return the invite link stops the card with needs_decision), examples/**; PR carries Fixes #27797 + Refs #27521 (row 6)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no (expected; the dev measures from the diff)
Metadata-protocol: none (expected; the dev measures from the diff)
Responsibility: the invite confirmation claiming "sent" without reading the transport, and no copy-link action | the server's transport reading (IEmailService.isConfigured, #25335) and the accept route are fixed inputs | every administrator who invites someone on a deployment with no mail transport, today
Thread-read: none
Premise: holds as filed (measured on the 18.3.0 RC walkthrough at c4c515ad); step 1 of the order re-measures on current main and step 2 measures whether the client can obtain the invite link at all
Serial constraints cleared: split from #27521 row 6 by seat 3 (triage 6495068090); seat 3 handed it to seat 2; the #27509 auth pages (sign-up) landed earlier and are not in this surface
Dispatch order: /mnt/project-files/ui-seat2/dispatch-27797.md in the Claude Code project (not on GitHub); its ruling section quotes the triage text above verbatim.
{
"issue": 27797,
"status": "needs_decision",
"branch": "claude/project-thread-18obwq",
"pr": null,
"session": "session_01Utp6CbAThbSd1afHuxTxn6 (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)",
"premise_still_valid": true,
"summary": "Measured on objectstack origin/main 561b7642, source reading (no live run). The defect holds, but every surface the card names is server metadata, not ui code, so the ui file surface cannot deliver it. (1) Setup -> Invitations is the generic ObjectView over sys_invitation (setup-nav.contributions.ts, nav item nav_invitations, type object). (2) The toast is the declared successMessage of the invite_user action, 'Invitation sent to {{ result.email }}', in packages/platform-objects/src/identity/sys-invitation.object.ts and its three mirrors (sys-user, sys-member, sys-organization); the runner shows it as declared (ObjectView.inviteFeedback-11817.test.tsx pins that path). (3) The row menu and the detail drawer render the sys_invitation list_item actions (cancel_invitation, resend_invitation), also declared in that file. (4) The spec ActionType enum is script | url | modal | flow | api | form: no action can copy to the clipboard, and nothing in the console registers a generic copy script. (5) Transport: the client has no machine-readable fact. Setup -> Email reads GET /api/settings/mail, whose live_status info_banner carries prose (label, bannerText, bannerSeverity) and no boolean; the one server reader is AuthManager.isEmailDeliveryLive, private, and nothing reaches the invite-member answer or the public features. (6) The link itself IS obtainable without a server change: the invitation row id gives resolveConsoleUrl('accept-invitation/' + id), the exact pattern the workspace pages already use (organizations/manage InvitationsPage handleCopyLink and InviteMemberDialog), and the server builds the same route in sendInvitationEmail. A ui-only fix would have to special-case one object and one action inside the generic renderer and infer the transport from banner prose, which ui AGENTS.md #0.1 and ADR-0060 rule 2 forbid. The toast half cannot proceed alone either: its copy is metadata. Stopped before any edit.",
"tests": "none: no code written. Live repro on stock app-showcase NOT MEASURED, reason: the source reading already places the toast literal and both menus in server metadata; the live repro moves to whichever card the decision dispatches.",
"gates": "none run: empty diff.",
"dogfood_battery": "n/a: no PR",
"mcp_calls": "4: issue_read get x1, issue_read get_comments x3; no write tool",
"api_writes": "1: this comment, POST /repos/objectstack-ai/objectstack/issues/27797/comments via scripts/pm/post-stamped.mjs",
"open_questions": [
{
"question": "Where do the transport-aware toast and the Copy invite link action live, given both surfaces are sys_invitation metadata and the client has no machine-readable deliverability fact?",
"options": [
"A. Server and contract first, ui after: the invite-member answer (or the public features) carries the deliverability fact as a boolean; the spec gains a clipboard-copy executor for actions, resolved mount-aware with resolveConsoleUrl by the renderer; sys_invitation and the three invite_user mirrors declare the copy action and the branch in their copy. Split as a server/contract card plus this card's ui half. Size L, Clause-2 yes (widening), Metadata-protocol added.",
"B. Point Setup -> Invitations at the existing workspace Invitations page (organizations/manage), which already has a per-row Copy invitation link and an 'Invitation created, share the link' dialog. One nav metadata change plus a transport read in that dialog. Moves administrators off the governed metadata action (role-cap option gates, cloud-managed gate, membership reach) onto the less governed dialog, against the rule that the governed side of two implementations wins; the two invite implementations stay divergent.",
"C. ui only: the renderer special-cases sys_invitation / invite_user, reads the mail live_status banner severity, overrides the declared successMessage and injects a menu item. Inside the file surface, but against ui AGENTS.md #0.1 (contract-first) and ADR-0060 rule 2, and it parses prose.",
"D. Narrow now: change invite_user successMessage on the four mirrors to transport-neutral wording ('Invitation created for ...'); copy link deferred to A. Small server-metadata edit; breaks the card's 'sent stays when mail is live' acceptance."
],
"recommendation": "A, because both halves are metadata and the deliverability fact has no client door today: declaring the copy action and the fact once keeps the renderer generic, keeps the governed invite action the one implementation, and lets the next share-link surface reuse the executor. C is the shortcut the contract-first rule exists to refuse."
}
],
"out_of_scope_findings": [
"carrier: seat 2 · noted, not filed: two invite implementations diverge (sys_invitation metadata action vs organizations/manage InviteMemberDialog); only the second offers the accept link. Dedupe words: invite member dialog duplicate, sys_invitation copy link, invitation share link"
]
}
Ruling: where the transport-aware invite toast and the Copy invite link live (project decision thread 「决定无邮件时邀请如何分享链接」) · letter E · maintainer 「同意你的建议,但是你应该等车道开发,着急的标记高优先」 2026-10-30T01:39Z
The maintainer's own ruling, given in the project thread at 2026-10-30T01:37Z on the review thread's six-section analysis (/mnt/project-files/decisions/27797-邀请无邮件分享链接-呈批.md, recommendation E). Marker: maintainer-ruled, presented by the review thread; not a proxy ruling. It replaces the card thread's recommendation A (report 6511276997) and domain:ui#2's brief, both of which asked for a spec clipboard executor.
What was re-verified (code read at 561b7642 on main)
The card thread's measurement holds: the toast is invite_user.successMessage in packages/platform-objects (sys-invitation.object.ts and the sys-user / sys-member / sys-organization mirrors); the row menu and drawer are the object's list_item actions; the client has no machine-readable mail-deliverability fact (Setup → Email's live_status banner is prose); AuthManager.isEmailDeliveryLive() is private.
Option A's premise — "the spec has no copy executor" — is true of a clipboard action type and beside the point. packages/spec/src/ui/action.zod.ts already carries outcomeMessages (success copy keyed by the server's outcome, for type: 'api' and type: 'script'; the console runner reads payload.outcome and composes the toast from it) and resultDialog with format: 'text' — "plain text with copy" — which sys_oauth_application.rotate_client_secret uses from list_item today. ActionResultDialog.tsx writes the clipboard.
A type: 'script' action in the console is the platform action route (POST /api/v1/actions/<object>/<action> → engine.registerAction), so a server handler can answer the link without the renderer learning anything about invitations.
Adding a member to ActionType stops the console compiling until an executor exists (the enum's own docblock: the ActionRunner dispatch table is typed Record<RunnableActionType, …> for that purpose), so A is a spec Tier H change plus a lock-step console change for a capability the spec already has.
Governing text
AGENTS.md Prime Directive 12 and ui/AGENTS.md #0.1 (contract-first: ask first whether the metadata is spec-compliant; change the spec only when the spec itself is wrong). Here the spec is not wrong — the metadata simply does not use what it offers.
ActionSchema.outcomeMessages (ruling A on objectstack-ai/cloud#2315) and ActionSchema.resultDialog in packages/spec/src/ui/action.zod.ts.
E — use the contract the spec already has; no spec change. One domain:server card (#27843) carries the server and metadata half; #27797 keeps the ui half (a live re-measure and a pin once the server card is on main).
Execution parameters, fixed (一次裁清):
A successful invite-member / resend answer carries a top-level outcome: 'sent' | 'link_only', decided at call time by isEmailDeliveryLive() — ⛔ never by hasEmailTransport / isEmailServiceAvailable.
plugin-auth registers sys_invitation.copy_invitation_link on the platform action route; it answers { acceptUrl } from getConsolePageUrl('/accept-invitation/<id>'), reached by the resend_invitation gates (organization feature, invite_member reach, hidden under cloudManaged) and only for a pending row. The URL is a bearer credential: never logged outside dev.
sys_invitation and its three mirrors: invite_user and resend_invitation move their success copy to outcomeMessages (sent keeps today's wording; link_only says the invitation was created and mail is not configured, never "sent"); a new copy_invitation_link action, list_item + record_header, type: 'script', resultDialog with format: 'text' on acceptUrl.
Pins: log transport → link_only and no "sent" in the toast; a configured transport → sent; the dialog's URL equals the mail's URL for the same invitation; refused outside the reach and for a non-pending row; hidden under cloudManaged.
Clause-②: no; Metadata-protocol: none. Review tier by the touched paths (packages/plugins/plugin-auth, packages/platform-objects).
⛔ Not chosen: A (a spec clipboard executor — Tier H and a console lock-step for a capability the spec has), B (pointing Setup → Invitations at the workspace page, which moves administrators off the governed action and keeps two invite implementations), C (a renderer special case reading banner prose), D (neutral wording alone — absorbed by parameter 3, so not dispatched separately).
Who does the server card — the maintainer's word, verbatim: 「你应该等车道开发,着急的标记高优先」. The server card goes to the domain:server queue and waits for a server seat; ⛔ no cross-lane exception by the ui seat, no seat opened for it by the review thread. Its grade stays p2 (the parent split's grade); the maintainer raises it if it is urgent.
Blocked-by: #27843 is met: the server card closed when PR #27902 merged at 2026-10-30T04:07Z (merge commit f552fa81f1). Per the release note (ruled E, 6512831254), the next claimant re-dispatches this card as a live re-measure plus one ObjectView pin (the outcome toast and the result-dialog copy). No ui code is expected. State: pm:blocked → pm:queue. Seat 2 is going off duty and does not claim it.
Claim: PM loop round 2 · 2026-10-30T14:58Z
Session: session_01L9qibzC6RxMfB59EsgAoNN (seat; implementation by card thread session session_01L9qibvGisaRusgvzEwJxoQ, mode:thread)
Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-tk3ydm (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-27797
Domain: domain:ui
Seat: domain:ui#1
File surface: the ObjectView / action-runner test beside ObjectView.inviteFeedback-11817.test.tsx under ui/ (nothing else expected; a ui-side producer fix only if the live re-measure shows one, written into the report and PR body; stop on breach and explain)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the invite confirmation toast and the copy-invite-link answer, per ruling E (6512831254): server-owned via outcomeMessages and a resultDialog with format: 'text'; the renderer learns nothing about invitations
Thread-read: 6524625478
Premise: holds (#27843 closed by PR #27902, merged f552fa81f1; sys-invitation.object.ts on origin/main10fc9bf1 carries the outcome messages and the result dialog; seat 2 unblocked the card in 6524625478)
Serial constraints cleared: no live claim or open PR touches the ObjectView invite pins; domain:ui#2 released its own claim (6512865993) and is going off duty
Clause-② is the seat's expectation; the dev measures it on the diff and the seat amends the claim if it moves. Order: /mnt/project-files/ui-seat-1/r2026-10-30/dispatch-27797.md under FRAME.md (delivered in the card thread's opening message).
{
"issue": 27797,
"status": "done",
"branch": "claude/project-thread-tk3ydm",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28182",
"session": "session_01L9qibvGisaRusgvzEwJxoQ (card thread; seat session_01L9qibzC6RxMfB59EsgAoNN)",
"premise_still_valid": true,
"summary": "Live re-measure on stock app-showcase at origin/main 10fc9bf1 (full workspace + console build from the tree, Chromium 1440x900): the ruled server fix (#27843, PR #27902) reaches the console unchanged. With the default log transport the invite door answers outcome link_only and the toast reads 'Invitation created for ... Mail is not configured on this server, so no email went out. Use Copy Invite Link on the invitation to share it.'; resend reads 'Invitation renewed for ...'. The row menu and the drawer record header both offer Copy Invite Link, whose result dialog shows and copies http://localhost:38797/_console/accept-invitation/ID (200 text/html). With OS_EMAIL_PROVIDER=smtp against a local SMTP sink the door answers outcome sent, the toast reads 'Invitation sent to ...', and the mail's accept URL equals the dialog's URL byte for byte. No ui code was needed; the diff is one ObjectView pin (link_only toast and its sent control; copy-link dialog to clipboard and its non-pending control).",
"tests": "pnpm --dir ui exec vitest run packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsx at 43ba24a6 under os-verify-lock: VERDICT command-exit 0, Tests 4 passed (4). First run failed 3/4 because the fixture omitted features.cloudManaged (served predicates read it; absent fails closed; a stock boot serves false); fixture corrected to the served value. No ablation: the diff changes no tested behaviour.",
"gates": "dispatch-gates --commands at 43ba24a6: 24 derived, all exit 0 (pnpm --dir ui exec vitest run scripts/: 169 files passed, 2 skipped, 4901 tests passed; node scripts/check-sdui-manifest.mjs + --self-test exit 0). --ran reconciliation: 24 run, 0 NOT-MEASURED, 0 UNRUN. Outside the derivation: ui check-new-cross-file-line-citations exit 0 (0 new); app-shell tsc -p tsconfig.test.json exit 0 (0 errors); eslint on the pin exit 0. CI check-runs on PR 28182: in_progress at report time.",
"line_budget": "+357 / -0, 1 file, under the 3000 human-merge threshold",
"files_changed": ["ui/packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsx"],
"deviations": "skip-changeset not applied by the dev per the order (tests only in private @object-ui/app-shell; built dist has 0 hits for the test vs positive control 8 for ActionResultDialog): the seat labels after its own check. The primary checkout was detached to host the designated branch in the worktree objectstack-issue-27797.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "4 — issue_read get x1, issue_read get_comments x2, pull_request_read get_files x1; no write tool",
"api_writes": "2 — pr_create via fleet-write relay (POST /repos/objectstack-ai/objectstack/pulls, run 41082772131, read-back identical); this comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27797/comments)",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: console result dialog measured live (Copy Invite Link dialog: the copy button's aria-label and text are both empty) · evidence: CopyButton in ui/packages/app-shell/src/views/ActionResultDialog.tsx renders an icon-only Button with no accessible name, used by the text, secret, qrcode, code-list and json formats, so every resultDialog's copy control is an unnamed button to a screen reader · dedupe words: result dialog copy button accessible name, CopyButton aria-label, resultDialog copy icon"
]
}
State: the card closed as completed by Fixes #27797, and pm:dispatched comes off in the same stroke as this note. The live re-measure on the PR shows the ruled server fix (#27843) reaching the console, and the pin holds it. Nothing remains on this card. The out-of-scope copy-button finding is #28186.
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Claim: PM loop round 3 · 2026-10-29T23:47Z
Session:
session_01Utp6CY2n1JmZZKrHgtp9jC(seat; implementation by a card thread,mode:thread)Account:
os-billBranch:
claude/project-thread-18obwq(the card thread's harness push branch, self-reported — the measured platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-27797Domain:
domain:uiSeat:
domain:ui#2File surface: the invite dialog / toast, the Setup → Invitations row menu and detail drawer (measured and named by the dev), their tests, i18n keys in the ten packs, a changeset; ⛔
packages/spec/**, every server file (a server change to return the invite link stops the card withneeds_decision),examples/**; PR carriesFixes #27797+Refs #27521(row 6)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no (expected; the dev measures from the diff)
Metadata-protocol: none (expected; the dev measures from the diff)
Responsibility: the invite confirmation claiming "sent" without reading the transport, and no copy-link action | the server's transport reading (
IEmailService.isConfigured, #25335) and the accept route are fixed inputs | every administrator who invites someone on a deployment with no mail transport, todayThread-read: none
Premise: holds as filed (measured on the 18.3.0 RC walkthrough at
c4c515ad); step 1 of the order re-measures on currentmainand step 2 measures whether the client can obtain the invite link at allSerial constraints cleared: split from #27521 row 6 by seat 3 (triage 6495068090); seat 3 handed it to seat 2; the #27509 auth pages (sign-up) landed earlier and are not in this surface
Dispatch order:
/mnt/project-files/ui-seat2/dispatch-27797.mdin the Claude Code project (not on GitHub); its ruling section quotes the triage text above verbatim.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
os-dev-report
{ "issue": 27797, "status": "needs_decision", "branch": "claude/project-thread-18obwq", "pr": null, "session": "session_01Utp6CbAThbSd1afHuxTxn6 (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)", "premise_still_valid": true, "summary": "Measured on objectstack origin/main 561b7642, source reading (no live run). The defect holds, but every surface the card names is server metadata, not ui code, so the ui file surface cannot deliver it. (1) Setup -> Invitations is the generic ObjectView over sys_invitation (setup-nav.contributions.ts, nav item nav_invitations, type object). (2) The toast is the declared successMessage of the invite_user action, 'Invitation sent to {{ result.email }}', in packages/platform-objects/src/identity/sys-invitation.object.ts and its three mirrors (sys-user, sys-member, sys-organization); the runner shows it as declared (ObjectView.inviteFeedback-11817.test.tsx pins that path). (3) The row menu and the detail drawer render the sys_invitation list_item actions (cancel_invitation, resend_invitation), also declared in that file. (4) The spec ActionType enum is script | url | modal | flow | api | form: no action can copy to the clipboard, and nothing in the console registers a generic copy script. (5) Transport: the client has no machine-readable fact. Setup -> Email reads GET /api/settings/mail, whose live_status info_banner carries prose (label, bannerText, bannerSeverity) and no boolean; the one server reader is AuthManager.isEmailDeliveryLive, private, and nothing reaches the invite-member answer or the public features. (6) The link itself IS obtainable without a server change: the invitation row id gives resolveConsoleUrl('accept-invitation/' + id), the exact pattern the workspace pages already use (organizations/manage InvitationsPage handleCopyLink and InviteMemberDialog), and the server builds the same route in sendInvitationEmail. A ui-only fix would have to special-case one object and one action inside the generic renderer and infer the transport from banner prose, which ui AGENTS.md #0.1 and ADR-0060 rule 2 forbid. The toast half cannot proceed alone either: its copy is metadata. Stopped before any edit.", "tests": "none: no code written. Live repro on stock app-showcase NOT MEASURED, reason: the source reading already places the toast literal and both menus in server metadata; the live repro moves to whichever card the decision dispatches.", "gates": "none run: empty diff.", "dogfood_battery": "n/a: no PR", "mcp_calls": "4: issue_read get x1, issue_read get_comments x3; no write tool", "api_writes": "1: this comment, POST /repos/objectstack-ai/objectstack/issues/27797/comments via scripts/pm/post-stamped.mjs", "open_questions": [ { "question": "Where do the transport-aware toast and the Copy invite link action live, given both surfaces are sys_invitation metadata and the client has no machine-readable deliverability fact?", "options": [ "A. Server and contract first, ui after: the invite-member answer (or the public features) carries the deliverability fact as a boolean; the spec gains a clipboard-copy executor for actions, resolved mount-aware with resolveConsoleUrl by the renderer; sys_invitation and the three invite_user mirrors declare the copy action and the branch in their copy. Split as a server/contract card plus this card's ui half. Size L, Clause-2 yes (widening), Metadata-protocol added.", "B. Point Setup -> Invitations at the existing workspace Invitations page (organizations/manage), which already has a per-row Copy invitation link and an 'Invitation created, share the link' dialog. One nav metadata change plus a transport read in that dialog. Moves administrators off the governed metadata action (role-cap option gates, cloud-managed gate, membership reach) onto the less governed dialog, against the rule that the governed side of two implementations wins; the two invite implementations stay divergent.", "C. ui only: the renderer special-cases sys_invitation / invite_user, reads the mail live_status banner severity, overrides the declared successMessage and injects a menu item. Inside the file surface, but against ui AGENTS.md #0.1 (contract-first) and ADR-0060 rule 2, and it parses prose.", "D. Narrow now: change invite_user successMessage on the four mirrors to transport-neutral wording ('Invitation created for ...'); copy link deferred to A. Small server-metadata edit; breaks the card's 'sent stays when mail is live' acceptance." ], "recommendation": "A, because both halves are metadata and the deliverability fact has no client door today: declaring the copy action and the fact once keeps the renderer generic, keeps the governed invite action the one implementation, and lets the next share-link surface reuse the executor. C is the shortcut the contract-first rule exists to refuse." } ], "out_of_scope_findings": [ "carrier: seat 2 · noted, not filed: two invite implementations diverge (sys_invitation metadata action vs organizations/manage InviteMemberDialog); only the second offers the accept link. Dedupe words: invite member dialog duplicate, sys_invitation copy link, invitation share link" ] }objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6Cb7FDwjs2e5wWuRbRq
Ruling: where the transport-aware invite toast and the Copy invite link live (project decision thread 「决定无邮件时邀请如何分享链接」) · letter E · maintainer 「同意你的建议,但是你应该等车道开发,着急的标记高优先」 2026-10-30T01:39Z
The maintainer's own ruling, given in the project thread at 2026-10-30T01:37Z on the review thread's six-section analysis (
/mnt/project-files/decisions/27797-邀请无邮件分享链接-呈批.md, recommendation E). Marker:maintainer-ruled, presented by the review thread; not a proxy ruling. It replaces the card thread's recommendation A (report6511276997) anddomain:ui#2's brief, both of which asked for a spec clipboard executor.What was re-verified (code read at
561b7642onmain)invite_user.successMessageinpackages/platform-objects(sys-invitation.object.tsand thesys-user/sys-member/sys-organizationmirrors); the row menu and drawer are the object'slist_itemactions; the client has no machine-readable mail-deliverability fact (Setup → Email'slive_statusbanner is prose);AuthManager.isEmailDeliveryLive()is private.packages/spec/src/ui/action.zod.tsalready carriesoutcomeMessages(success copy keyed by the server'soutcome, fortype: 'api'andtype: 'script'; the console runner readspayload.outcomeand composes the toast from it) andresultDialogwithformat: 'text'— "plain text with copy" — whichsys_oauth_application.rotate_client_secretuses fromlist_itemtoday.ActionResultDialog.tsxwrites the clipboard.type: 'script'action in the console is the platform action route (POST /api/v1/actions/<object>/<action>→engine.registerAction), so a server handler can answer the link without the renderer learning anything about invitations.getConsolePageUrl('/accept-invitation/<id>')insendInvitationEmail, and documents that no second link builder exists. The console: copied invitation link is unusable — buildAcceptUrl rebuilds the trailing-dot host that resolveHomeUrl already retired #8090 lesson concerns the client'simport.meta.env.BASE_URL, which this route never touches.ActionTypestops the console compiling until an executor exists (the enum's own docblock: theActionRunnerdispatch table is typedRecord<RunnableActionType, …>for that purpose), so A is a spec Tier H change plus a lock-step console change for a capability the spec already has.Governing text
ActionSchema.outcomeMessages(ruling A on objectstack-ai/cloud#2315) andActionSchema.resultDialoginpackages/spec/src/ui/action.zod.ts.AuthManager.isEmailDeliveryLivedocblock (plugin-auth: under theopenandemail_domainpostures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060): an implementation withoutisConfigured, or one answeringfalse, is not live; thelogtransport is not delivery.The ruling
E — use the contract the spec already has; no spec change. One
domain:servercard (#27843) carries the server and metadata half; #27797 keeps the ui half (a live re-measure and a pin once the server card is onmain).Execution parameters, fixed (一次裁清):
outcome: 'sent' | 'link_only', decided at call time byisEmailDeliveryLive()— ⛔ never byhasEmailTransport/isEmailServiceAvailable.sys_invitation.copy_invitation_linkon the platform action route; it answers{ acceptUrl }fromgetConsolePageUrl('/accept-invitation/<id>'), reached by theresend_invitationgates (organizationfeature,invite_memberreach, hidden undercloudManaged) and only for apendingrow. The URL is a bearer credential: never logged outside dev.sys_invitationand its three mirrors:invite_userandresend_invitationmove their success copy tooutcomeMessages(sentkeeps today's wording;link_onlysays the invitation was created and mail is not configured, never "sent"); a newcopy_invitation_linkaction,list_item+record_header,type: 'script',resultDialogwithformat: 'text'onacceptUrl.logtransport →link_onlyand no "sent" in the toast; a configured transport →sent; the dialog's URL equals the mail's URL for the same invitation; refused outside the reach and for a non-pending row; hidden undercloudManaged.packages/plugins/plugin-auth,packages/platform-objects).⛔ Not chosen: A (a spec clipboard executor — Tier H and a console lock-step for a capability the spec has), B (pointing Setup → Invitations at the workspace page, which moves administrators off the governed action and keeps two invite implementations), C (a renderer special case reading banner prose), D (neutral wording alone — absorbed by parameter 3, so not dispatched separately).
Who does the server card — the maintainer's word, verbatim: 「你应该等车道开发,着急的标记高优先」. The server card goes to the
domain:serverqueue and waits for a server seat; ⛔ no cross-lane exception by the ui seat, no seat opened for it by the review thread. Its grade stays p2 (the parent split's grade); the maintainer raises it if it is urgent.outcome由服务端唯一读者判,链接由服务端唯一构造器出),渲染器保持通用;不为一个剪贴板动作给ActionType加员(加员即锁死控制台编译)。openandemail_domainpostures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060(log 传输不算 live);console: copied invitation link is unusable — buildAcceptUrl rebuilds the trailing-dot host that resolveHomeUrl already retired #8090(客户端 BASE_URL 拼链接的教训);Setup → Email shows provider SMTP with an empty host while mail goes to the development log sink and "Invitation sent" toasts appear — say on the page which transport is live (hotcrm#2194 T2-U12) #25335(Setup → 邮件 的 live_status 横幅)。Execution after the ruling
outcomeby mail delivery andsys_invitationgains a servercopy_invitation_linkaction that answers the accept URL (ruling E on #27797) #27843 (domain:server, p2,pm:queue), filed by this ruling; it waits for a server seat.domain:ui#2moves it topm:blocked, Blocked-by invite-member answersoutcomeby mail delivery andsys_invitationgains a servercopy_invitation_linkaction that answers the accept URL (ruling E on #27797) #27843; the card thread's branchclaude/project-thread-18obwqcarries no code and is released. When the server card is onmain, the seat re-dispatches: a live repro on stockapp-showcase, one ObjectView pin (outcome toast + result-dialog copy), and the card closes on that measurement — no ui code expected./mnt/project-files/coordinator/rulings.md, one row.objectstack-fleet commentedon Oct 30, 2026
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Release (seat's own claim) · 2026-10-30T01:42Z
Blocked-by: #27843
outcomeby mail delivery andsys_invitationgains a servercopy_invitation_linkaction that answers the accept URL (ruling E on #27797) #27843 (domain:server) carries the fix, usingoutcomeMessagesplus aresultDialog(text)server action, both existing spec. This card waits for that lane, with no cross-lane exception.claude/project-thread-18obwqhas no code (Resume-from: none). State:pm:dispatched→pm:blocked, assignee cleared.outcomeby mail delivery andsys_invitationgains a servercopy_invitation_linkaction that answers the accept URL (ruling E on #27797) #27843 is onmain, re-dispatch this card as a live re-measure plus one ObjectView pin (the outcome toast and the result-dialog copy). No ui code is expected.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Unblocked · 2026-10-30T13:59Z
Blocked-by: #27843is met: the server card closed when PR #27902 merged at 2026-10-30T04:07Z (merge commitf552fa81f1). Per the release note (ruled E, 6512831254), the next claimant re-dispatches this card as a live re-measure plus one ObjectView pin (the outcome toast and the result-dialog copy). No ui code is expected. State:pm:blocked→pm:queue. Seat 2 is going off duty and does not claim it.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN
Claim: PM loop round 2 · 2026-10-30T14:58Z
Session:
session_01L9qibzC6RxMfB59EsgAoNN(seat; implementation by card thread sessionsession_01L9qibvGisaRusgvzEwJxoQ,mode:thread)Account:
os-tesla(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-tk3ydm(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27797Domain:
domain:uiSeat:
domain:ui#1File surface: the ObjectView / action-runner test beside
ObjectView.inviteFeedback-11817.test.tsxunderui/(nothing else expected; a ui-side producer fix only if the live re-measure shows one, written into the report and PR body; stop on breach and explain)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the invite confirmation toast and the copy-invite-link answer, per ruling E (6512831254): server-owned via
outcomeMessagesand aresultDialogwithformat: 'text'; the renderer learns nothing about invitationsThread-read: 6524625478
Premise: holds (#27843 closed by PR #27902, merged
f552fa81f1;sys-invitation.object.tsonorigin/main10fc9bf1carries the outcome messages and the result dialog; seat 2 unblocked the card in 6524625478)Serial constraints cleared: no live claim or open PR touches the ObjectView invite pins;
domain:ui#2released its own claim (6512865993) and is going off dutyClause-②is the seat's expectation; the dev measures it on the diff and the seat amends the claim if it moves. Order:/mnt/project-files/ui-seat-1/r2026-10-30/dispatch-27797.mdunderFRAME.md(delivered in the card thread's opening message).objectstack-fleet commentedon Oct 30, 2026
domain:ui#1 · os-tesla · session_01L9qibvGisaRusgvzEwJxoQ
os-dev-report
{ "issue": 27797, "status": "done", "branch": "claude/project-thread-tk3ydm", "pr": "https://github.com/objectstack-ai/objectstack/pull/28182", "session": "session_01L9qibvGisaRusgvzEwJxoQ (card thread; seat session_01L9qibzC6RxMfB59EsgAoNN)", "premise_still_valid": true, "summary": "Live re-measure on stock app-showcase at origin/main 10fc9bf1 (full workspace + console build from the tree, Chromium 1440x900): the ruled server fix (#27843, PR #27902) reaches the console unchanged. With the default log transport the invite door answers outcome link_only and the toast reads 'Invitation created for ... Mail is not configured on this server, so no email went out. Use Copy Invite Link on the invitation to share it.'; resend reads 'Invitation renewed for ...'. The row menu and the drawer record header both offer Copy Invite Link, whose result dialog shows and copies http://localhost:38797/_console/accept-invitation/ID (200 text/html). With OS_EMAIL_PROVIDER=smtp against a local SMTP sink the door answers outcome sent, the toast reads 'Invitation sent to ...', and the mail's accept URL equals the dialog's URL byte for byte. No ui code was needed; the diff is one ObjectView pin (link_only toast and its sent control; copy-link dialog to clipboard and its non-pending control).", "tests": "pnpm --dir ui exec vitest run packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsx at 43ba24a6 under os-verify-lock: VERDICT command-exit 0, Tests 4 passed (4). First run failed 3/4 because the fixture omitted features.cloudManaged (served predicates read it; absent fails closed; a stock boot serves false); fixture corrected to the served value. No ablation: the diff changes no tested behaviour.", "gates": "dispatch-gates --commands at 43ba24a6: 24 derived, all exit 0 (pnpm --dir ui exec vitest run scripts/: 169 files passed, 2 skipped, 4901 tests passed; node scripts/check-sdui-manifest.mjs + --self-test exit 0). --ran reconciliation: 24 run, 0 NOT-MEASURED, 0 UNRUN. Outside the derivation: ui check-new-cross-file-line-citations exit 0 (0 new); app-shell tsc -p tsconfig.test.json exit 0 (0 errors); eslint on the pin exit 0. CI check-runs on PR 28182: in_progress at report time.", "line_budget": "+357 / -0, 1 file, under the 3000 human-merge threshold", "files_changed": ["ui/packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsx"], "deviations": "skip-changeset not applied by the dev per the order (tests only in private @object-ui/app-shell; built dist has 0 hits for the test vs positive control 8 for ActionResultDialog): the seat labels after its own check. The primary checkout was detached to host the designated branch in the worktree objectstack-issue-27797.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "4 — issue_read get x1, issue_read get_comments x2, pull_request_read get_files x1; no write tool", "api_writes": "2 — pr_create via fleet-write relay (POST /repos/objectstack-ai/objectstack/pulls, run 41082772131, read-back identical); this comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27797/comments)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: console result dialog measured live (Copy Invite Link dialog: the copy button's aria-label and text are both empty) · evidence: CopyButton in ui/packages/app-shell/src/views/ActionResultDialog.tsx renders an icon-only Button with no accessible name, used by the text, secret, qrcode, code-list and json formats, so every resultDialog's copy control is an unnamed button to a screen reader · dedupe words: result dialog copy button accessible name, CopyButton aria-label, resultDialog copy icon" ] }objectstack-fleet commentedon Oct 30, 2026
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN
Landed · 2026-10-30T19:37Z · PR #28182 (seat ACCEPT 6526955724, re-pinned to
05e22529).86cf7ceff6through the merge queue. It was ejected once on main-red #28219 and re-queued after #28227origin/maincontentui/packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsxpresentState: the card closed as completed by
Fixes #27797, andpm:dispatchedcomes off in the same stroke as this note. The live re-measure on the PR shows the ruled server fix (#27843) reaching the console, and the pin holds it. Nothing remains on this card. The out-of-scope copy-button finding is #28186.