Repository navigation
spec: ActionSchema gains outcomeMessages (a closed outcome key → I18nLabel, interpolating ${result.*}), and the client stops requiring message on the environments.delete answer (ruling A on cloud#2315) #21095
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
enhancement·priority:p2·domain:spec·area:i18n·pm:queue. The first link of ruling A on objectstack-ai/cloud#2315.Clause-②: yesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T07:02Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. It heads a maintainer-ruled three-repo chain. objectstack-ai/objectui#11344 and objectstack-ai/cloud#2315 wait on this card's published key.
Routing.
ActionSchemaisdomain:spec.@objectstack/clientisdomain:cli. The client change is declared as the same claim's cross-lane surface and dispatched vertically, in one PR.Direction. It is the card's own scope, confirmed:
outcomeMessages: snake_case outcome keys mapping toI18nLabel, fortype: api | script;successMessageand each outcome message interpolate${result.*}, the scopeonSuccess.navigatealready declares. ⛔ No new dialect;- the key joins the strict schema and the liveness ledger.
Clause-②: yes. It adds a published key, and the client'senvironments.deleteanswer stops requiringmessage. That is a type change consumers can see, so the PR carries its ADR-0087 disposition.
Generated by Claude Code
- addedarea:i18nThe customer's own language, across UI, metadata and notificationsThe customer's own language, across UI, metadata and notificationsenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01UtnxvdiN376GF3sgXwAw4d
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21095-action-outcome-messages
Worktree:objectstack-issue-21095
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface:packages/spec/src/ui/action.zod.ts(ActionSchema.outcomeMessages, valid fortype: api | script;successMessageand each outcome message interpolate${result.*}in the scopeonSuccess.navigatealready declares), its liveness-ledger row, the translation face besidesuccessMessage, the regenerated spec artefacts, tests, and one.changeset/21095-*.md. Cross-lane, declared by triage (domain:cli, same PR):packages/client/src/index.ts(environments.delete's archive answer stops requiringmessageand gains the closedoutcomefact) and its tests. The objectui renderer (objectui#11344) and cloud's actions (cloud#2315) are later links, ⛔ not this PR. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier; the contract review runs atCONTRACT_REVIEW_TIERthrough an isolated subagent)
Clause-②: yes
Thread-read: 5926403550
Serial constraints cleared: census of the 9 open PRs' file lists at 2026-10-01T15:09Z onorigin/main2c1cef3345: none touchespackages/spec/src/ui/action.zod.tsorpackages/client/src/index.ts. Same-day churn:action.zod.tschanged atb3917d90(PR #21122, today); the dev works on the merged code. #21180 (seat 2, branch52903dbd44) and #20928 (this seat) regenerate theuigenerated shards: base merges go throughscripts/pm/os-regen-merge.sh.objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21095,
"status": "done",
"branch": "claude/issue-21095-action-outcome-messages",
"pr": "#21214",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"summary": "ActionSchema gainsoutcomeMessages: snake_case outcome key to I18nLabel, valid on type api|script only, refused located (code+path) on url/modal/flow/form, beside resultDialog and beside operation:'update';successMessageand each outcome message interpolate ${result.}, the scope onSuccess.navigate already declares (cited, not re-described); not picked into InlineActionSchema. The translation face rides beside successMessage (TranslationData slot with snake_case keys, translateAction per-outcome overlay of declared outcomes only, os i18n extract emits one key per declared outcome — the extractor lives in @objectstack/cli, named in the PR body). The liveness row landsplanned+ authorWarn (carrier objectui#11344; the console reader is absent at both the base pin e420df31 and the post-merge pin 31971ff1), because the gate defines planned as declared-not-yet-read and reds a warned live row. client.environments.delete: archive answermessage?: string+outcome?: 'archived'|'already_archived'|'purge_deferred', teardown answeroutcome?: 'destroyed'(the vocabulary split by the existing deleted/purged arms); zero in-repo readers ofmessagebesides the client's own test (noos environments deletecommand exists). Selection + ${result.} substitution are the console's and move to objectui#11344; this repo pins parse fidelity, the refusals, translation and extraction of the cloud-shaped delete_environment fixture.",
"tests": "All at HEAD 00855f7 (after the origin/main merge via scripts/pm/os-regen-merge.sh; check:generated 15/15 current). pnpm --filter @objectstack/spec test: 595 files / 17431 passed / 1 todo. @objectstack/client vitest run: 50 files / 644 passed. @objectstack/cli vitest run --project unit: 243 files / 3439 passed (integration layer declared to CI: no spawn entry touched). typecheck spec/client/cli (each incl. check:test-typecheck): exit 0 (pre-merge tree; merge touched none of their TS except spec component/view/dataset, covered by the full spec suite + check:generated); tsc -p tsconfig.test.json --listFiles names environments-delete-two-step.test.ts (1 hit). Ablations via scripts/ablation-replace.mjs (landing = anchor count + blob hash; restore = blob == HEAD, git diff HEAD empty): delete .superRefine(refuseInertOutcomeMessages) → 6 failed/14 passed; rename the declarative-update table row → 1 failed/19 passed; drop the translateAction overlay → 3 failed/17 passed; reverse-verify client type (message?: back to message:) → tsc TS2578 Unused '@ts-expect-error' at the optional-message pin. Narrowed lint: eslint --no-inline-config --format json over the 9 touched .ts files → 9 results, 0 errors, 0 warnings; population = eslint.config.mjs's own /*.{ts,...} globs (no file reported ignored); invariance: the config sets no parserOptions.project (0 hits; its own header states type-aware linting is never enabled), so the diff cannot move an untouched file's verdict.",
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read, to measure the delete route's wire) — refused: no access. Zero GitHub MCP tools; no MCP write.",
"api_writes": "3 — all through the fleet-write relay (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #21214; read-back 11774 bytes sent = stored); (2) label-write --assign os-sales → POST /repos//issues/21214/assignees (read-back matches; zero label writes — the dispatch names no labels and skip-changeset does not apply, a changeset exists); (3) this os-dev-report comment → POST /repos//issues/21095/comments. Plus git pushes (not REST).",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths; merge base 0d42104) derived 114; all 114 run at 00855f7, all exit 0; --ran with recorded exit codes: '114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN'. First-pass exit 3 (PREREQUISITE NOT MET, sibling dist absent) then green on re-run: check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage. check:type-check-debt hit a 400s wrapper timeout once, re-run exit 0 ('none above its recorded number'). check-adr-0087-registration: '[BREAKING+bang+clause-②-narrowing] not-required (runtime-interface-only) -- verified: packages/client/src/index.ts#ObjectStackClient (class)'. CI: in_progress at report time (not awaited, per contract).",
"line_budget": "n/a — the diff touches no skills/ and no line-ratcheted governed ledger; not a governed surface (no .claude/, docs/adr/, AGENTS.md, CLAUDE.md, skills/).",
"deviations": [
"PR body carriesClause-②: yescopied verbatim from the claim; the changeset carries the measured armClause-②: yes (narrowing)(spec widens; the client's return type narrows —messageno longer guaranteed is compile-breaking for a reader typing it string). If the PM wants the arm on the PR body line too, the edit is to append(narrowing)to line 3 of #21214's body (seat write; dev writes the body once).",
"File surface beyond the claim's enumeration, each forced by a gate or by where the producer lives: packages/cli/src/utils/i18n-extract.ts + packages/cli/test/i18n-extract-outcome-messages.test.ts (the translation face's extractor is in @objectstack/cli); packages/spec/src/system/metadata-form-zod-reconciliation.test.ts (its root ledger reds on a new authorable key not offered by action.form.ts — recorded as a planned omission like onSuccess); packages/spec/liveness/translation.json (two notes name outcomeMessages); regenerated content/docs/references/ (5) + authorable-surface/ui.json + liveness/state-counts/action.md.",
"One MCP call outside GitHub MCP (add_repo for cloud read) — refused, no effect.",
"Branch commits are titled wip(...) — intended for squash landing."
],
"files_changed": [
".changeset/21095-action-outcome-messages.md",
"content/docs/references/data/object.mdx",
"content/docs/references/kernel/metadata-plugin.mdx",
"content/docs/references/system/translation.mdx",
"content/docs/references/ui/action.mdx",
"content/docs/references/ui/component.mdx",
"packages/cli/src/utils/i18n-extract.ts",
"packages/cli/test/i18n-extract-outcome-messages.test.ts",
"packages/client/src/environments-delete-two-step.test.ts",
"packages/client/src/index.ts",
"packages/spec/authorable-surface/ui.json",
"packages/spec/liveness/action.json",
"packages/spec/liveness/state-counts/action.md",
"packages/spec/liveness/translation.json",
"packages/spec/src/system/i18n-resolver.ts",
"packages/spec/src/system/metadata-form-zod-reconciliation.test.ts",
"packages/spec/src/system/translation.zod.ts",
"packages/spec/src/ui/action-outcome-messages.test.ts",
"packages/spec/src/ui/action.zod.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: os validate --json on a probe stack (built CLI at this branch) whose zh-CN bundle carries objects.probe_env._actions.delete_probe.outcomeMessages.restored for an action declaring onlyarchivedanswers NO finding, while the same probe's undeclared params.not_a_param answerstranslation-target-unknown(error) — control leg green · evidence: packages/lint/src/validate-translation-references.ts judges onlyparamsunder_actions.ACTION/globalActions.ACTION(checkActionParams); translateAction overlays declared outcomes only, so such a key is silently never read · introduced with this PR's key; not fixed here because it widens packages/lint, outside the claim's file surface (the bounded in-place exemption needs a claim amendment a dispatched dev cannot post) · suggested home: its own card, Blocked-by nothing, best landed before objectui#11344 flips the row live · dedupe words: outcomeMessages translation-target-unknown; undeclared outcome translation key; validate-translation-references outcomeMessages; checkActionParams outcome",
"carrier: objectstack-ai/objectui#11344 · the runner today composesresult.data.message || action.successMessage || default(ActionRunner.handlePostExecution at pin 31971ff1); the spec contract written here declares outcomeMessages[result.outcome] → successMessage → default and names no server-message rung, so the renderer card decides where the servermessagesits (ruling B implies it leaves) · noted, not filed",
"carrier: 承接者:无 · packages/spec/src/system/metadata-form-zod-reconciliation.test.ts's existingonSuccessomission says both children areplanned; the ledger flipped themliveon 2026-09-27 (stale rationale in a test ledger, no public door) · noted, not filed (PR Acceptance notes)"
]
}objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21095,
"status": "done",
"branch": "claude/issue-21095-action-outcome-messages",
"pr": "#21214",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"round": "patch round 1 (same claim 5934282106, no new claim) — head 00855f7 → 3f94d67",
"summary": "Test Core (2/6) red on 00855f7 reproduced locally before any edit: packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts 'servesactionwith a populated schema, not the empty husk' → AssertionError: expected 49 to be 48 (1 failed / 17 passed). It pins the DECLARED key count of the servedactionschema (served properties + retired tombstones); outcomeMessages moves it 48 → 49 (46 accepted + 3 refused). Updated load-bearingly in commit 3f94d67: the constant is 49, its comment says 46 accepted and why it moved, and the addressed-key sample now names 'outcomeMessages', so the pin asserts the new key is SERVED, not only that the count rose (18/18 green after). Sweep found no other pin this key moves: no other numeric count of ActionSchema/served-action properties in packages/, apps/, scripts/** (git grep of toBe/toHaveLength 45–49 and of properties/shape/keysOf length enumerations — every other hit is unrelated); the only ActionSchema-shaped key lists outside spec's own green suites are the action:button / action:icon FORWARDED rows in packages/spec/src/ui/component-action-element-rows-20371.test.ts, which are measured from objectui's renderer forward lists (outcomeMessages is not forwarded there yet — correct as is, and a carrier item for objectui#11344); no snapshot file carries action keys; the dogfood expression-conformance ledger discovers by expression schema name, which outcomeMessages (z.record of I18nLabel) is not.",
"tests": "At HEAD 3f94d67: @objectstack/metadata-protocol full suite (vitest run --maxWorkers=2, under the verify lock): 200 files passed + 3 skipped (the three env-gated .live-mysql / .live-postgres migration suites, CI's live-DB job) — 2973 tests passed, 19 skipped; exit 0. Target file before fix (00855f7): 1 failed / 17 passed, 'expected 49 to be 48'; after: 18/18. Readings carried from round 0 at 00855f7 stay valid for their packages — the only diff since is this one metadata-protocol test file: @objectstack/spec 595 files / 17431 passed; @objectstack/client 50 files / 644 passed; @objectstack/cli unit 243 files / 3439 passed. Builds for this worktree: turbo build of ./packages/ (71 tasks, 30 cached) under the lock before the gate union.",
"mcp_calls": "0 this round. (Round 0: 1 — mcp__claude-code-remote__add_repo for cloud read, refused.) No GitHub MCP tool used.",
"api_writes": "1 this round — this os-dev-report comment via scripts/pm/post-stamped.mjs → fleet-write relay → POST /repos//issues/21095/comments. Plus git push of 3f94d67 (not REST). No PR body edit (dev writes it once).",
"gates": "Re-derived at 3f94d67 with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths; 20 paths vs merge base 0d42104): 115 commands — round 0's 114 plus pnpm check:durability-log-level (newly matched by the metadata-protocol path). All 115 run at 3f94d67, all exit 0 (check:type-check-debt re-measure 12s: 'none above its recorded number'). --ran with recorded exit codes: '115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero). CI on 3f94d67: in_progress at report time, not awaited.",
"line_budget": "n/a — no skills/** and no governed surface in the diff.",
"deviations": [
"PR body still cites round-0 readings at 00855f7 and does not mention the metadata-protocol pin; dev writes the body once. Suggested seat edit: under '## Tests' add '-@objectstack/metadata-protocol(consumer):vitest runpassed at3f94d67b: 200 files (3 env-gated live-DB suites skipped), 2973 tests. Its served-actionkey-count pin moves 48 → 49 and now namesoutcomeMessages.' and change the Gates line to '115 derived … at3f94d67b'.",
"Carried from round 0: PR body lineClause-②: yes(verbatim from the claim) vs the changeset's measuredClause-②: yes (narrowing)."
],
"files_changed": [
"packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: objectstack-ai/objectui#11344 · packages/spec/src/ui/component-action-element-rows-20371.test.ts pins the action:button / action:icon FORWARDED key set measured from objectui's renderer forwards; when the console reader lands, the forward ofoutcomeMessages(and the ActionButtonPropsSchema / ActionIconPropsSchema rows) must grow in the same landing, or an outcomeMessages authored on an action:button node is refused/dropped one hop before the runner · noted, not filed",
"Round-0 findings stand unchanged (class c translation-lint gap for undeclared outcome keys, measured at os validate; the server-message rung carrier for objectui#11344; the stale onSuccess omission rationale)."
]
}objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21095,
"status": "done",
"branch": "claude/issue-21095-action-outcome-messages",
"pr": "#21214",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"round": "patch round 2 (same claim 5934282106, no new claim) — head 3f94d67 → a43024e",
"summary": "One-sentence changeset correction, nothing else in the diff (1 file, 1 insertion / 1 deletion). .changeset/21095-action-outcome-messages.md line 27: the forecast 'That reader ships in the console\'s next release. Until then the key is accepted, …' is now the present fact 'The console does not read it yet. Until it does, the key is accepted, …'. No release promise remains in the changeset. Left as written, by instruction: the preceding sentence 'The console picksoutcomeMessages[result.outcome], falls back tosuccessMessage, and then to its default text.' states the selection contract in present tense; read with the new next sentence it describes the rule, not shipped behaviour. If the seat wants it unambiguous, the one-word edit is 'picks' to 'is to pick' (not made here).",
"tests": "No code changed this round; package suites not re-run. Round-1 readings at 3f94d67 stand for every package (metadata-protocol 200 files / 2973 passed; spec 595 / 17431; client 50 / 644; cli unit 243 / 3439 — the last three at 00855f7, unchanged since).",
"mcp_calls": "0 this round. No GitHub MCP tool used in any round.",
"api_writes": "1 this round — this os-dev-report comment via scripts/pm/post-stamped.mjs → fleet-write relay → POST /repos//issues/21095/comments. Plus git push of a43024e (not REST). PR body untouched, per instruction.",
"gates": "Scoped to the round's delta, declared: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands .changeset/21095-action-outcome-messages.md derived 19 commands at a43024e; all 19 run, all exit 0 — including node scripts/check-adr-0087-registration.mjs --base origin/main ('[BREAKING+bang+clause-②-narrowing] not-required (runtime-interface-only) -- verified: packages/client/src/index.ts#ObjectStackClient (class)'), node scripts/check-changeset-no-major.mjs --base origin/main, node scripts/check-empty-changeset.mjs --base origin/main (+ each --self-test), pnpm check:changeset-gate-self-tests, check:objectui-changeset, check:pm-changeset-deadline-census, check:nul-bytes, check-comment-mask-corpus. --ran with recorded exit codes and the same path: '19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN'. Extras, not derived for this path but asked: pnpm check:doc-authoring exit 0, node scripts/check-issue-citations.mjs exit 0. The full 115-family union was last run at 3f94d67 (115/115 exit 0); this round's delta is one changeset line. ⚠ The derivation printed STALE TREE: the branch is 8+ commits behind origin/main and one file a derived family reads changed there (scripts/doc-authoring-prose-id.baseline.json). NOT MEASURED: check:doc-authoring against main's copy of that baseline — a probe that restored only main's baseline into this tree (then restored HEAD's bytes, hash-verified, git diff HEAD empty) went red solely on packages/rest/src files that main also changed (7 rest files differ HEAD vs origin/main) and on nothing this PR touches, so it measured a mixed tree, not this PR; the real verdict is CI's on the merge ref. CI on a43024e: in_progress at report time, not awaited.",
"line_budget": "n/a — no skills/** and no governed surface in the diff.",
"deviations": [
"No merge of origin/main this round (instruction: change nothing else); the branch is behind main by 8+ commits — the seat may want a base merge before landing.",
"Worktree recreated from remote head 3f94d67 and removed after this comment (node_modules removed, git worktree remove without --force); remote branch head = a43024e."
],
"files_changed": [
".changeset/21095-action-outcome-messages.md"
],
"open_questions": [],
"out_of_scope_findings": [
"Rounds 0 and 1 findings stand unchanged; nothing new this round."
]
}objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21214 @
a43024e7fedomain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5934282106· 2026-10-01T18:34Z- Shape (read on GitHub): draft against
main; first lineFixes #21095;Clause-②: yes (narrowing)at a line start (seat-edited to carry the changeset's arm); PR assigneeos-sales. No other card number sits beside a closing keyword. - Rounds:
- Round 0
00855f76was red inTest Core (2/6): ametadata-protocolcount pin. At-tier review FAIL5936832663, on that reason alone. - Round 1
3f94d67bfixed the pin load-bearingly (48 → 49, and the pin namesoutcomeMessages). At-tier review PASS5937329231. - Round 2
a43024e7corrected one changeset sentence. The seat's sentence check found that "That reader ships in the console's next release" forecast a reader that objectui#11344 (pm:on-hold, unassigned) does not schedule. It now states the present fact. At-tier review PASS5937974378on this head.
- Round 0
- Prose checked sentence by sentence (changeset):
- The refusal list (
url/modal/flow/form, besideresultDialog, besideoperation: 'update') and snake_case keys refused at their own path matchrefuseInertOutcomeMessages, the declarative-update row and the pins. ${result.*}reusesonSuccess.navigate's scope.- "The console does not read it yet" matches objectui
ActionRunnerat the pin, and the liveness gradeplanned+authorWarn. - The client paragraph (
message?,outcome?split by arm) matches the diff. - The ADR-0087 marker
not-required (runtime-interface-only …#ObjectStackClient)was verified by the gate.
- The refusal list (
- Gates on this head: 35 check-runs, 33
successand 2 skipped.check-expected-skips: OK.check-governed-merges --pr 21214: NOT governed, 744 changed lines.mergeable_state: clean. - Behind main: 11 commits. The review read the trees: the only file both sides touched is the generated
references/ui/component.mdx, in disjoint hunks, and the merge-ref gates are green. The queue rebuilds on currentmain. - Out-of-scope, one line each:
os validatedoes not judgeoutcomeMessages.OUTCOMEbundle keys: filed lint:os validatenever judges a translation bundle's_actions.ACTION.outcomeMessages.OUTCOMEkeys against the outcomes the action declares — an undeclared outcome's copy passes clean and is never read #21216 (domain:spec, after this lands).- Where the server
messagerung sits, and theaction:button/action:iconforwarded-row pin: both carried by objectui#11344. The seat posts one pointer there at landing. - The stale
onSuccessrationale in the form-reconciliation ledger: stays in Acceptance notes.
- Batch note: PR feat(spec): ObjectSchema.imageField — an object declares its record picture, refused unless it names an image or avatar field #21221 also moves a count in
protocol.meta-types-degenerate-derivation.test.ts(object, a different line). The queue serializes them. - Landing: this PR is readied and enters the merge queue now.
- Shape (read on GitHub): draft against
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded: PR #21214 →
d6d6e872e5domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5934282106· 2026-10-01T19:04Z- Landed: PR feat(spec,client)!: ActionSchema gains outcomeMessages (success copy per handler outcome, ${result.*}), and environments.delete stops guaranteeing message #21214 went through the merge queue as
d6d6e872e5, with one parent (read withgit rev-list --parents).action.zod.ts,client/src/index.ts,liveness/action.json, the changeset and the CLI extractor are blob-equal to the reviewed heada43024e7fe. - Shared pin file:
protocol.meta-types-degenerate-derivation.test.tsdiffers from the head only by PR feat(spec): ObjectSchema.imageField — an object declares its record picture, refused unless it names an image or avatar field #21221'sobject43 → 44 line, which landed first. Onmainboth moves are present:actionreads 49 and namesoutcomeMessages(:250/:253), andobjectreads 44 (:310). - Card: closed
completedby the PR'sFixesline. The other issues closed in the window, Queue-flake anchor: src/__tests__/caller-content-admission-door.test.ts #21200 and Queue-flake anchor: src/__tests__/field-read-admission-gate.test.ts #21201, are queue-flake anchors, not this PR's. This act removespm:dispatchedand the assignee. - Follow-through:
- lint:
os validatenever judges a translation bundle's_actions.ACTION.outcomeMessages.OUTCOMEkeys against the outcomes the action declares — an undeclared outcome's copy passes clean and is never read #21216 (the translation-lint gap for undeclared outcome keys) is unblocked by this landing; its release is triage's act. - ActionRunner: compose the success toast from
outcomeMessages[result.outcome], thensuccessMessage(both interpolating${result.*}), then the default; stop readingdata.message(ruling A on cloud#2315) objectui#11344 (the console reader) gets one pointer comment with the two carrier notes from this card's reviews: where the servermessagerung sits, and theaction:button/action:iconforwarded-row pin. - The liveness row flips to
live(droppingauthorWarn) in objectui#11344's landing.
- lint:
- Landed: PR feat(spec,client)!: ActionSchema gains outcomeMessages (success copy per handler outcome, ${result.*}), and environments.delete stops guaranteeing message #21214 went through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ③ a maintainer-ruled task. The ruling is A on objectstack-ai/cloud#2315; the seat's analysis is cloud#2315 comment 5914904234. Recorded verbatim on that card. The maintainer's word was 「其他同意」: agreement to the recommended letter, given in the
repo:cloudseat's session (session_01YSygzqVmrY31BnZge8KDo6) on 2026-10-01, with the decision batch presented in the director's format.domain:speclane is the expected home, with the client type in the same PR or a paired one.search_issueson objectstack for "action outcomeMessages outcome key success message interpolation result": 0 hits.The ruling it executes
Ruling B on cloud#2315 (5731313414), not reopened: 「server returns facts, the console composes the message in the user's locale」. Ruling A on the mechanism: metadata-declared outcome copy. Three repos land it in order:
What to build (at objectstack
main)ActionSchema(packages/spec/src/ui/action.zod.ts): a new optionaloutcomeMessages, a map from a snake_case outcome key to anI18nLabel, valid fortype: api | script.outcomestring, and the console picksoutcomeMessages[outcome]. Failing that, it usessuccessMessage, then the runner default.successMessage(today a staticI18nLabelSchema, around :1305) and each outcome message may interpolate${result.*}. That is the scopeonSuccess.navigatealready declares in the same file, so there is no new dialect.successMessage.@objectstack/client(packages/client/src/index.ts): theenvironments.deletearchive answer stops declaringmessage: stringas REQUIRED (around :3165). It gains theoutcomefact instead (archived | already_archived | purge_deferred | destroyed).Why this shape (the measured pull)
ActionRunnercomposesdyn || action.successMessage || default(objectuipackages/core/src/actions/ActionRunner.ts, around :1472).successMessageis static, so it cannot say which of delete's four outcomes happened. Three of those four destroy nothing, and cloud'ssys-environment.object.tsandenvironment-delete-action.test.tshold the invariant that a user can tell an archive from a destruction.check_app_updateshas three outcomes:updates_available | up_to_date | none_installed.package-install.ts,package-review.ts) that the same mechanism serves.Acceptance
outcomeMessageson api and script actions. It refuses an unknown shape loudly, and the liveness row is live (the console reads it).{ outcome: 'archived', … }resolves to the archived copy, interpolated.message.Related
objectstack-ai/cloud#2315 (blocked on this card and on the objectui renderer card) · objectstack-ai/cloud#2435 (where
export_packagealready dropped itsmessage, PR objectstack-ai/cloud#2514).Generated by Claude Code