Skip to content

feat(spec,service-automation): a flow run's result carries the flow's authored label as flowLabel - #20633

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20318-flow-label-on-result
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20318-flow-label-on-result

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20318 — stage 1 of 2 (the served authored label). Stage 2, the objectui reader of flows.FLOW.label in the runner header and completion toast, stays open on the card; this PR does not flip the ledger row.

Clause-②: yes (widening: AutomationResult gains flowLabel)

What changes

AutomationResult (@objectstack/spec/contracts) gains one optional member, flowLabel?: string: the flow definition's authored label, copied verbatim by service-automation at the same exits that copy successMessage / errorMessage. TriggerFlowResponseSchema.data (@objectstack/spec/api) mirrors it, which its compile-time parity guard with AutomationResult requires. The generated automation API reference is regenerated. translateFlow's docblock now says where the fallback label comes from and that the translation key is still unread.

This is option (b) of seat 4's fork, taken by the claim from standing text: the result already carries flow-level strings copied from the definition for a runner's benefit.

The served shape (for the stage-2 sub-issue)

  • Field: flowLabel, type string, optional, on AutomationResult and on TriggerFlowResponse.data.
  • Set on every result of an evaluation of a registered flow:
    • status: 'paused' (first attempt, a retry attempt, a resume that pauses again, a subflow chain that pauses again);
    • terminal success with no status, including the two skip exits (condition_not_met, reentrancy_loop_guard);
    • 'failed' (the trigger exit and the exhausted-retry exit), 'stranded', 'refused';
    • a resumed parent whose delegated subflow child failed (no status, success: false).
  • Absent on every refusal that carries a code (FLOW_DISABLED, FLOW_NO_START_NODE, FLOW_INPUT_SCHEMA_INVALID and all six resume refusals, a nested child's screen refusal included), and when the flow is not registered.
  • Subflow rule: always the label of the run the caller addressed, which is the parent. The child that supplied the screen never lends its label. Decided from what the runner displays: ScreenFlowState.flowName is the launched flow, and the delegated pause answers with the parent's runId.
  • Verbatim: FlowSchema requires label, so the member is always present on the set above and is always what the author wrote, an empty string included. It is never replaced by the API name.
  • At the wire: both runner doors relay the result verbatim on a 200, so data.flowLabel arrives on POST /api/v1/automation/NAME/trigger (paused, finished, refused or skipped launch) and on POST /api/v1/automation/NAME/runs/RUN_ID/resume (a further pause, the completion, a refusal). A 400 FLOW_FAILED answer does not carry it: the trigger door and classifyResumeResult build error.details from their own key set (errorMessage, summary, and on resume the stranded verdict), in packages/runtime, and this PR leaves that set alone. See the open question below.

Premise, verified before the first edit (on origin/main c1d8051)

  1. Every successMessage / errorMessage copy site is in engine.ts and reads the local parsed flow, whose label is a required string. The one exception, retryExecution's exhausted exit, receives flow.errorMessage as a parameter from execute(), which holds flow; the label is now passed beside it. The hits in subflow-node.ts / map-node.ts are comments; node executors return a NodeExecutionResult, never an AutomationResult.
  2. respondToFlowTrigger answers the paused arm and the terminal arm with deps.success(result), the resume door answers every success !== false result the same way, and the dispatcher's success() is { success: true, data, meta } with nothing projected. The 400 FLOW_FAILED arms project details from their own key set (above).

Verification (at 01cd34c7e4)

  • service-automation: new flow-label-on-result.test.ts, 16 tests (every PRESENT arm, the subflow chain, verbatim '', and the ABSENT refusals). Full package vitest run: 152 files, 1867 tests passed. typecheck: exit 0, test layer 0 debt.
  • Ablation, three legs through scripts/ablation-replace.mjs (anchor must hit, restore proved by blob == HEAD): blanking the 13 direct stamps turned 10 tests red and left 6 green (the refused, retry-exhausted and 4 absent tests), as predicted; blanking the refused chokepoint turned exactly the refused test red; blanking the retry-exhausted exit turned exactly that test red.
  • Reverse type check: flowLabel: 42 at the refused chokepoint makes service-automation's tsc --noEmit red with TS2322 at engine.ts:9060, so it reads the rebuilt spec .d.ts. Renaming the mirror member in automation-api.zod.ts makes spec's test typecheck red with 3 errors in automation-api.zod.test.ts, so the mirror is load-bearing.
  • spec: the touched test files (3 files, 340 tests) pass, typecheck exit 0, check:generated --fix regenerated only content/docs/references/api/automation-api.mdx.
  • Wire, @objectstack/verify (the one package depending on both the doors and the engine): new automation-trigger-flow-label.test.ts plus the four sibling automation suites, 5 files, 17 tests passed. The existing paused-run parity test still deep-compares the two routes' bodies, now with flowLabel on both.
  • Pin sweep: no test deep-compares an engine AutomationResult outside this package. Consumer suites run green: plugin-approvals (791), trigger-record-change (101), trigger-schedule (150).
  • Gates: dispatch-gates --commands derived 111; --ran reconciles 109 run and 2 NOT MEASURED. check:liveness exit 0.
    • NOT MEASURED: check:dual-build-cjs-loads, reason: its prerequisite is every package built (33 had no dist), which is CI-owned.
    • NOT MEASURED: check:type-check-debt, reason: --re-measure re-runs tsc for every debt-ledger entry repo-wide, none of which is a package this diff touches, and a 300s ceiling fired. check:type-check-coverage ran green.

Acceptance notes

  • Surface beyond the claim's list, declared: packages/spec/src/api/automation-api.zod.ts (+ its test and the regenerated reference page) is the trigger transport's declared response schema, bound to AutomationResult by TriggerFlowDataMatchesContract, and its z.object strips undeclared keys, so a client parsing the response would drop the label. packages/verify/src/automation-trigger-flow-label.test.ts is the route pin the dispatch asked for. It lives in verify because nothing else depends on both packages.
  • Two stamped exits no test observes: reentrancy_loop_guard (needs a same-record re-entrant dispatch) and executeWithoutRetry's failed exit (the retry loop consumes it and answers from the exhausted exit). Both are stamped, and the first ablation leg blanked them.
  • The wire file was not ablated at dist level. Its PRESENT assertions are toBe(label), which an absent member cannot satisfy, and the engine-side legs above already show the mutation reds the equivalent pins.
  • seedRunVariables still writes $flowLabel as flow.label ?? flowName. The ?? arm is unreachable because label is required. Noted only.
  • Read, not measured: the resumed-parent exit for a delegated child failure carries no errorMessage. The contract says errorMessage is set on failure, so a runner would show the raw subflow error there instead of the parent author's text. The probe at the resume door never got the shared verify lock, so this stays a note and no card is filed. Carrier: none.
  • The translations.mdx bullet, the ledger flip and the authorWarn drop ride the stage-2 reader, per the claim. packages/spec/liveness/translation.json is unchanged: no note names a missing producer, and nothing in it became false.

Open question for the seat

Should the 400 FLOW_FAILED details carry flowLabel? My recommendation is no. No runner surface names the flow on a failure: the failure toast shows the error text, and the Flow "NAME" failed fallback is built client-side before the request. Widening an ADR-0112 details set in packages/runtime with no reader is outside this claim.


Generated by Claude Code

…d label

Every result of an evaluation of a registered flow (paused, terminal
success including the skip exits, failed, stranded, refused, and a resumed
parent whose delegated child failed) now carries `flowLabel`, the flow
definition's `label` copied verbatim the way `successMessage` /
`errorMessage` are. Refusals carrying a `code` and unknown flows carry none.
A subflow chain answers with the addressed (parent) run's label.

The trigger response schema mirrors the new member, as its compile-time
parity guard with `AutomationResult` requires.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Present on every evaluation's result (paused, terminal success, skip,
failed, retry exhausted, retry-attempt success and pause, stranded,
refused), absent on every code-bearing refusal and on an unknown flow; a
subflow chain answers with the parent's label; an empty label is served
verbatim, never replaced by the API name.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
The paused launch, a resume that completes and a terminal launch each
carry data.flowLabel; the resume door's 400 FLOW_FAILED details keep their
fixed set (no flowLabel), fenced so a widening is deliberate.

translateFlow's docblock now says where the authored label the client
falls back to comes from, and that the translation key is still unread.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:system tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/system/i18n-resolver.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/api/client-sdk.mdx (via AutomationResult (symbol, a top-level interface))
  • content/docs/api/declarative-endpoints.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol TriggerFlowResponseSchema — its route source's handler names it))
  • content/docs/automation/flows.mdx (via AutomationResult (symbol, a top-level interface), condition_not_met (literal, a string literal in execute), reentrancy_loop_guard (literal, a string literal in execute), /api/v1/automation/:name/trigger (route, bridged from symbol TriggerFlowResponseSchema — its route source's handler names it))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol TriggerFlowResponseSchema — its route source's handler names it))

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

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level class), retryExecution (symbol, a method of class AutomationEngine))
  • content/docs/releases/v17/17-1.mdx (via AutomationResult (symbol, a top-level interface))
  • content/docs/releases/v17/17-3.mdx (via AutomationResult (symbol, a top-level interface))

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 changed file(s) yielded no anchor (packages/spec/src/system/i18n-resolver.ts) — pages documenting those are invisible to this run
  • 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 — 137 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 a918fe7fd0150578db13e6ae8d5cff24582e213e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a0c35106ccb2fdf34a4fbcf701612fb928ae6692 — the merge of head 01cd34c7e4e3f416c44b5a1139f4e44ec4375ba1 into base a918fe7fd0150578db13e6ae8d5cff24582e213e, 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 a0c35106ccb2fdf34a4fbcf701612fb928ae6692 && git checkout a0c35106ccb2fdf34a4fbcf701612fb928ae6692
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a918fe7fd0150578db13e6ae8d5cff24582e213e 01cd34c7e4e3f416c44b5a1139f4e44ec4375ba1 && git checkout -B drift-repro a918fe7fd0150578db13e6ae8d5cff24582e213e && git merge --no-ff 01cd34c7e4e3f416c44b5a1139f4e44ec4375ba1

node scripts/docs-audit/affected-docs.mjs --json a918fe7fd0150578db13e6ae8d5cff24582e213e

⚠️ 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 a918fe7fd0150578db13e6ae8d5cff24582e213e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 01cd34c7e4e3f416c44b5a1139f4e44ec4375ba1
Local-runs: none

Inputs: card #20318 (body; comments 5860419823, 5863837992, 5880531686, 5886917830, 5889584401), PR #20633 (body, its one comment 5889543530, the 9-file list, the net diff against main from merge-base c1d8051e0a), the head's 32 check-runs read once at 2026-09-29T11:47:25Z, and objectui main reads of views/FlowRunner.tsx (e03f5d9f), hooks/useConsoleActionRuntime.tsx (93058128), views/RecordDetailView.tsx (7128bd8a) and utils/flowResponse.ts (a8de1d09); the pin is dd3f7e1b. Every line number below is at the head.

① Derived judgments

  1. The ruling's premise, (b) from standing text — RIGHT. At the merge-base, AutomationResult's own docblock (packages/spec/src/contracts/automation-service.ts) reads "Friendly terminal messages copied from the flow definition (flow.successMessage / flow.errorMessage) so a screen-flow runner can show a meaningful toast", and TriggerFlowResponseSchema's docblock reads "Keep new AutomationResult members mirrored here (and vice versa)". Copying a flow-level string onto the result for the runner is the contract's standing pattern, and the diff copies flow.label verbatim from the same local parsed flow the two messages are copied from, at those exits plus the paused, refused and skip exits. The exit set is a superset by necessity (the runner header's slot IS the paused exit, which no terminal message ever reaches); the copy is identical. No new pattern is invented.

  2. Accept-set: AutomationResult.flowLabel?: string, optional and additive — RIGHT, and the served set is exactly the dev's. All seven producers that return an AutomationResult (execute, resume, resumeInternal, retryExecution, executeWithoutRetry, finishRefusedRun, refuseInvalidScreenInput) walked by return statement in packages/services/service-automation/src/engine.ts:

    • Stamped, 15 sites: execute — condition_not_met :5370, reentrancy_loop_guard :5417, terminal success :5577, paused :5641, failed :5846, and the retry handoff passes flow.label :5792; resumeInternal — subflow chain re-pause :6549, delegated-child failure :6611, success :7025, paused :7116, stranded :7275; finishRefusedRun chokepoint :9060 (all three refusal producers :5592, :7042, :11671 route through it); retryExecution exhausted :11357; executeWithoutRetry — success :11653, paused :11756, failed :11829. retryExecution :11335-:11336 forwards an attempt's result unchanged.
    • Absent, every one either coded or the unregistered arm: execute :5260 (unregistered, no code), :5287 FLOW_DISABLED, :5349 FLOW_NO_START_NODE, :5768 FLOW_INPUT_SCHEMA_INVALID; resume :5977 PERMISSION_DENIED; resumeInternal :6418 and :6842 RESUME_IN_PROGRESS, :6467 and :6863 STORE_UNAVAILABLE, :6472 / :6497 / :6501 RUN_NOT_FOUND, :6583 a child's coded refusal forwarded verbatim (a coded child result was never stamped), :6706 INVALID_SCREEN_INPUT from refuseInvalidScreenInput :7390, :6730 INVALID_SIGNAL; executeWithoutRetry :11501 unregistered, :11519 FLOW_DISABLED, :11548 FLOW_NO_START_NODE.
      No un-coded evaluation exit is left unstamped, and no coded exit is stamped.
  3. Subflow rule (the addressed parent's label, never the child's) — RIGHT. Both delegated-leg exits (:6549, :6611) read flow = this.flows.get(run.flowName) (:6495), the addressed run's own definition; the child result lends only screen. :6583 forwards the child's coded refusal, which carries no label.

  4. Hypothesis 2 falsified — RIGHT. FlowSchema.label is z.string().describe('Flow label') (packages/spec/src/automation/flow.zod.ts:1017), no .optional(), no .min(1); registerFlow goes through canonicalizeStoredFlow, which is FlowSchema.parse (:4155), so a flow without a label never registers, and '' parses and is served as '' (pinned). No stamp site falls back to the API name. retryExecution's new flowLabel: string parameter is fed flow.label, which type-checks only because FlowParsed['label'] is string.

  5. Transport mirror TriggerFlowResponseSchema.data.flowLabel — RIGHT, and a necessary part of this change. TriggerFlowDataMatchesContract (automation-api.zod.test.ts:51) is an identity pin, Assert( Eq( TriggerFlowResponse['data'], AutomationResult ) ), so adding the member to the contract alone reds spec's test typecheck; and data is a plain z.object, which strips undeclared keys, so a client parsing with the schema would drop the label. That is the claim's "transport only if the premise shows a pass-through gap" clause, met. The doors themselves relay verbatim: trigger door deps.success(result) (packages/runtime/src/domains/automation.ts:1600, :1605), resume door :2704; the /actions and declarative-endpoint doors also return the engine result unprojected (action-execution.ts, endpoint-executor.ts). The client SDK's automation.trigger resolves to AutomationResult through unwrapResponse (packages/client/src/index.ts), bound to the contract, so it needs no edit. The regenerated content/docs/references/api/automation-api.mdx row is the only projection: api-surface/ records that an export exists, not its members; no other zod schema mirrors AutomationResult (ResumeFailureDetailsSchema is a deliberate status-subset pin, untouched and consistent with ③); the successMessage sweep outside spec and service-automation hits only authoring-key readers (cli i18n-extract, lint, metadata-protocol) and the client docblock.

  6. Skip exits carry the label, unlike successMessage / summary — RIGHT. Both are success: true with no status and leave the trigger door as a 200 through respondToFlowTrigger's terminal arm, a body the runner parses; the label names the flow and claims no work done.

  7. translateFlow docblock — RIGHT, one nit. "reaches the runner on every run result" is strictly every result of a dispatched evaluation; a coded refusal (never dispatched) carries none. Prose only; the contract docblock and the zod describe state the precise set.

  8. packages/spec/liveness/translation.json untouched — RIGHT. The flows.children.label note ("declared here, read by no shipped runner yet") and the container's authorHint stay true after this diff; the claim's "notes only" is a ceiling, not a demand. Spec property liveness is green.

  9. Tests. 16 engine pins (present, absent, subflow, verbatim ''), 1 schema-survival pin, 4 wire pins through a real HttpDispatcher and engine in @objectstack/verify, including the 400 boundary fence. The verify file is test-only and not shipped: packages/verify/tsconfig.json excludes **/*.test.ts from the build program, the sibling tsconfig.test.json is what typecheck (tsc --noEmit && pnpm check:test-typecheck) covers it with, and main / files point at dist.

② Semver level

  • .changeset/20318-automation-result-flow-label.md: @objectstack/spec: minor, @objectstack/service-automation: minor (both published, 17.5.0, not private). An optional member added to a public interface and to a public response schema is a widening, so the value is yes, and yes takes at least minor (AGENTS.md Post-Task §3). Level right, arm right. Nothing removed, renamed or re-meant, so no migration text and no ADR-0087 marker are owed. @objectstack/runtime and @objectstack/client are untouched and owe none (they relay or resolve the contract type); @objectstack/verify gains a test only and owes none.
  • The PR body's Clause-②: yes (widening: AutomationResult gains flowLabel) parses per scripts/pm/clause2-line.mjs as value yes, arm widening (first token of the parenthetical; trailing text tolerated) and agrees with the changeset's Clause-②: yes (widening). Check Changeset (which runs check-adr-0087-registration --base MERGE_BASE and check-changeset-no-major) is green on this head.
  • Clause-②: yes (widening: AutomationResult.flowLabel and TriggerFlowResponseSchema.data.flowLabel)

③ Boundary flags

  • Open question — the 400 FLOW_FAILED error.details: A, answered, not escalated. On the code: the trigger door (domains/automation.ts:1566-1574), classifyResumeResult (:1868-1874) and the two sibling doors build details from errorMessage and summary (plus the stranded verdict on resume), so flowLabel is dropped by construction and ResumeFailureDetailsSchema declares no such key; the new verify case fences that. On objectui main: utils/flowResponse.ts reads error.details only through flowFailureMessage (errorMessage, else actionErrorDetail(json, LABEL + ' failed')) where LABEL is the caller-built Flow "NAME" (useConsoleActionRuntime.tsx:631, RecordDetailView.tsx:1059) or the literal 'Resume' (FlowRunner.tsx:350); FlowRunner's failure path is toast.error(outcome.error) plus the inline Alert (:355-356) and never names the flow. The stage-2 header slot is fed by the paused 200 the runner already holds in ScreenFlowState, so a failure answer has no reader for the label. Widening an ADR-0112 details set with no reader is outside this claim; if stage 2 decides the failure toast should name the flow, that is a new card (the dev's option B).
  • Deviation: spec/api mirror, its test and the regenerated page — accepted, inside the claim's transport clause (①-5).
  • Deviation: the verify test file — accepted, test-only, not in dist (①-9); it is the one package depending on both the doors and the engine.
  • Deviation: translation.json unchanged — accepted (①-8).
  • Deviation: dists broken by a killed local gate run, repaired — a local-environment event; nothing of it is in the diff (9 files, all accounted for).
  • Deviation: the 400 arm reported rather than STOP — accepted: that arm serves an error envelope, not an AutomationResult; the premise "the transports pass AutomationResult fields through" holds on every door that serves the result as data.
  • Deviation: attribution trailer per AGENTS.md — not a contract matter.
  • Out-of-scope finding 1 — CONFIRMED from the code, for the seat to file. resumeInternal's delegated-child-failure exit (:6611) returns { success: false, error, durationMs, flowLabel } with no status, no errorMessage and no summary, while the contract says errorMessage is set on failure and summary on every terminal result. At the wire classifyResumeResult answers 400 FLOW_FAILED with details lacking errorMessage, so objectui's flowFailureMessage falls to the envelope message, i.e. the raw subflow run '…' (child) failed: … text instead of the parent author's errorMessage. Pre-existing, not this diff's; the dev's note omits that summary is missing there too. Dedupe words as in the report.
  • Out-of-scope finding 2 — CONFIRMED. seedRunVariables :11479 flow.label ?? flowName: the ?? arm is unreachable (①-4). Cosmetic.
  • Scope. 9 files as listed; no governed surface (Governed Surface Queue Guard green), nothing under skills/** or .claude/**; no ledger flip, no authorWarn change, no translations.mdx rewrite, no objectui edit, flow-credential-projection.ts untouched. The PR body opens with "Part of i18n: the flow launcher and runner header read translation.flows.<flow>.label (1 key) #20318" (not a close), carries the claim's Clause-② line verbatim and the session-URL footer.
  • Gate coverage, read once at 2026-09-29T11:47:25Z on 01cd34c7: 32 check-runs, 17 concluded, 0 failed, 15 not concluded. Concluded green: Build Core, Build Docs, Check Changeset, Spec property liveness, Type Check · source gates (check:type-check-coverage, check:generated --reconcile-only), Type Check · debt ledger, Governed Surface Queue Guard, Check PR Size, Check Documentation Links, the three claim and branch guards, Auto Label, Flag docs affected. Skipped by design: Console Pin Gate (its path filter keys on the console scripts and the pin, not packages/spec; an additive optional member cannot break the pinned objectui build, but no CI build of objectui against this head exists), Packed-tarball smoke (opt-in). NOT concluded, named rather than presumed green: Test Core 1/6 to 6/6 (where the three new suites and the service-automation package run), Lint & Repo Gates (eslint, check:doc-authoring), Type Check · workspace (per-package tsc, service-automation's included), Type Check · consumer gates (check:api-surface, check:exported-any, check:entry-nameability, check:dual-source-exports, check:skill-examples), Dogfood Regression Gate 1/3 to 3/3, Dogfood Verify CLI, Temporal Conformance. The regenerated automation-api.mdx has no concluded re-derivation in CI (check:docs runs under no PR job I could name); it rests on the dev's check:generated --fix and matches the zod describe text. This record's PASS is on the contract; per AGENTS.md feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 the enqueue still waits for every check to be green.

Implemented-by: claude/issue-20318-flow-label-on-result
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 12:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 5363e2d Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20318-flow-label-on-result branch September 29, 2026 12:25
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 protocol:system size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants