Skip to content

test(showcase): add the loop { parallel } fixture that unblocks the flow-run-step-nesting clause - #17514

Merged
os-sales merged 3 commits into
mainfrom
claude/issue-16356-showcase-loop-parallel
Sep 10, 2026
Merged

os-sales merged 3 commits into
mainfrom
claude/issue-16356-showcase-loop-parallel

Conversation

@claude

@claude claude Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #16356

Clause-②: no

What this adds

examples/app-showcase declared each ADR-0031 structured construct as its own top-level flow and nested none inside another. Because of that, the loop { parallel } acceptance clause on automation.flow-run-step-nesting (item revision 3, docs/qa/platform-checklist/areas/automation.json) could only ever score blocked(fixture) — there was no flow to trigger.

This adds showcase_nested_fan_out_reminders (NestedFanOutRemindersFlow): an autolaunched flow whose loop body holds exactly one node, a two-branch parallel that notifies the row's owner and its watcher concurrently. Rows are task-shaped {id, title, owner, watcher} and the clause is driven over at least two of them.

Why that exact shape, so nobody simplifies it away. The maintainer ruling of 2026-09-03 made ExecutionStepLog.iteration single-valued (the enclosing loop's row, carried through nesting) and gave the parallel branch position its own branch key. A step inside a parallel branch that is itself inside a loop body is the ONLY shape where both index keys are populated on one record. One row, or one branch, and the clause cannot discriminate. There is deliberately no try_catch between the loop and the parallel: a containment region retags the leaf steps try / catch and the branch position stops being readable off them — per-iteration containment stays demonstrated by BatchRemindersFlow.

The clause is unblocked — driven on its own oracle

The clause's oracle is api: trigger the flow, then read the run detail. Driven on a real isolated showcase boot (pnpm dev -- --fresh), signed in as the dev admin, POST /api/v1/automation/showcase_nested_fan_out_reminders/trigger with two rows, then GET .../runs/:runId:

TRIGGER_HTTP=200   RUNS_HTTP=200   DETAIL_HTTP=200   RUN_STATUS=completed
{"nodeId":"start","nodeType":"start","status":"success"}
{"nodeId":"each_task","nodeType":"loop","status":"success"}
{"nodeId":"fan_out_audiences","nodeType":"parallel","status":"success","parentNodeId":"each_task","iteration":0,"regionKind":"loop-body"}
{"nodeId":"notify_owner","nodeType":"notify","status":"success","parentNodeId":"fan_out_audiences","iteration":0,"branch":0,"regionKind":"parallel-branch"}
{"nodeId":"notify_watcher","nodeType":"notify","status":"success","parentNodeId":"fan_out_audiences","iteration":0,"branch":1,"regionKind":"parallel-branch"}
{"nodeId":"fan_out_audiences","nodeType":"parallel","status":"success","parentNodeId":"each_task","iteration":1,"regionKind":"loop-body"}
{"nodeId":"notify_owner","nodeType":"notify","status":"success","parentNodeId":"fan_out_audiences","iteration":1,"branch":0,"regionKind":"parallel-branch"}
{"nodeId":"notify_watcher","nodeType":"notify","status":"success","parentNodeId":"fan_out_audiences","iteration":1,"branch":1,"regionKind":"parallel-branch"}

All three things the clause's verify asks for hold: (a) every parallel-branch step carries a branch equal to its position, branch: 0 included; (b) all four (iteration, branch) pairs appear exactly once across the two rows; (c) the enclosing parallel container step reads regionKind: 'loop-body' with the row on iteration and no branch of its own. That run was scored blocked(fixture) before this PR and is scoreable now.

RED before GREEN

examples/app-showcase/test/nested-fan-out-region-indices.test.ts pins the same three assertions by RUNNING the real flow (read out of src/automation/flows/index.ts, not a copy) on a real AutomationEngine with the real built-in executors, and cross-checks every recorded step under ExecutionStepLogSchema. Two ablations, each mutating the committed file, proving the mutation reached disk by blob hash, and restoring by state:

leg mutation on disk result restore
un-nest (the pre-#16356 shape: loop body holds a leaf, nests no parallel) marker count 0 to 1, fan_out_audiences 1 to 0 7213ede11 to fa58e3860 RED — VERDICT command-exit 1; AssertionError: expected [] to have a length of 4 but got +0 blob back to 7213ede11, git diff HEAD empty, git status --porcelain empty
drop the second branch notify_watcher 1 to 0 7213ede11 to 3849e46f6 RED — VERDICT command-exit 1; refused at registration: parallel 'fan_out_audiences': a parallel block needs at least 2 branches same

Direction reported as observed, not as predicted: leg 2 was predicted to fail on the pair list and instead fails earlier, in validateControlFlow — the spec refuses a one-branch parallel outright. Leg 1 is the one that proves the pin measures the NESTING.

Premise check: the card's cost estimate

The card claimed "No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry." Triage flagged it unverified; 25 tracked files under scripts/ and .github/workflows/ mention app-showcase. Measured: the estimate holds for gates, and understates the checklist half.

  • pnpm check:i18n-coverage — no obligation. MEASURED, with a firing control. Every flow does emit flows.FLOWNAME.label into the expected-translation set (walkScreenFlows, packages/cli/src/utils/i18n-extract.ts), but the flows family is planned + authorWarn in the liveness ledger and is not counted. Gate green with the new flow: OK (13 config(s), 621 baselined untranslated string(s), none new) — showcase still at its baselined 414. Control: dropping one translated zh-CN field label from ShowcaseTranslationBundle (blob 87519c6f6 to 2d195d431, restored to 87519c6f6 with git diff HEAD empty) turns it RED — "untranslated declared strings grew 414 to 415". So the zero is a measurement, not blindness.
  • .github/workflows/showcase-smoke.yml — does not execute it. Nightly + workflow_dispatch, explicitly NON-BLOCKING and never a PR gate; it drives console nav surfaces for render health, and enumerates no flow.
  • No gate enumerates the flow list or asserts a count. allFlows is consumed by showcase's own suite (coverage.test.ts node-type coverage, bulk-write-transition-flows, predicate-write-bulk-intent, connector-ping-run-output) — all green, and none is a count or path enumeration.
  • What the card DID understate: the checklist item itself. Its fixtures.requires recorded "NOT PRESENT in examples/app-showcase … until such a flow lands (filed as app-showcase nests no control-flow construct inside another, so the QA checklist's new loop { parallel } clause can only score blocked(fixture) #16356)". Landing the flow without rewriting that leaves a QA ledger asserting the fixture is absent — a runner reading it still scores blocked(fixture), so the code change alone would not have unblocked the clause.

Out of the declared write surface: docs/qa/platform-checklist/areas/automation.json

Forced, not chosen, for the reason directly above: the deliverable is a clause that can score something other than blocked(fixture), and that verdict is read out of this file. Three edits, each obliged by scripts/check-platform-checklist.mjs or by the runner:

  1. fixtures.requires — replaces the "NOT PRESENT" record with the flow that closes it, plus the row shape and the two-row minimum a runner needs.
  2. steps — the nesting-run step said "needs the fixture above"; it now names the trigger call and the row shape.
  3. revision 3 to 4 plus a history entry — the gate fails when revision does not match the last history entry, so a semantic edit cannot be made without it. A source symbol anchor for the new flow is added alongside; the gate resolves it (632/632 anchors, 17 file floors held).

Clause-②, both limbs measured

  • Limb A — widens the accept set? No. Nothing here touches a schema, a validator or a parse path; the diff adds one metadata document plus a test plus a QA ledger edit. Nothing previously refused becomes legal. (The one-branch ablation shows the accept set is unchanged and still enforced by validateControlFlow.)
  • Limb B — adds or changes a public surface? No, measured. NestedFanOutRemindersFlow is a new exported symbol, so the question is whether that module is a published surface. examples/app-showcase/package.json declares "private": true and no files field, and its exports map does not include src/automation/flows/index.ts at all. Empirically, over the built dist/ trees of the repo's packages: NestedFanOutRemindersFlow 0 hits, showcase_nested_fan_out_reminders 0 hits, positive control ExecutionStepLogSchema 14 hits. Firing control on the manifest read: packages/services/service-analytics/package.json carries no private field, so the private: true reading discriminates.

Same measurement is why this PR carries skip-changeset and no .changeset/*.md: nothing publishable moves.

Verification

Everything below on head 86d90dbb9, after merging origin/main. Heavy runs went through scripts/pm/os-verify-lock.sh; the lock's own VERDICT command-exit line is quoted.

run result
pnpm turbo run build --filter=@objectstack/example-showcase^... --concurrency=2 VERDICT command-exit 0 · 61/61 tasks
pnpm --filter @objectstack/example-showcase test && ... typecheck VERDICT command-exit 0 · 29 files, 381 tests passed
pnpm lint (whole repo, eslint . --no-inline-config) VERDICT command-exit 0 — full population, no narrowing claimed
derived gates (node scripts/pm/dispatch-gates.mjs --commands) 44 derived, 43 exit 0
pnpm check:dual-build-cjs-loads exit 3 = PREREQUISITE NOT MET = NOT MEASURED — reads built output and eight packages outside this card's closure have no dist/; needs a whole-repo pnpm build, which Build Core does
--ran reconciliation 44 derived, 44 accounted, 43 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN

Acceptance notes

  • loop { try_catch } — the wider gap the card's last section asks about — is already closed on main and was not when the card was filed. PR fix(showcase): contain the batch-reminders loop body per iteration #16355 ("contain the batch-reminders loop body per iteration") landed it: BatchRemindersFlow's loop body now holds a try_catch whose catch is a bare assignment. Noted, not filed; reported to the PM so the seat decides whether the follow-up card triage asked for is still wanted. Nothing about it is implemented here.
  • automation.flow-node-type-matrix's fixtures.requires still reads loop=showcase_batch_reminders · parallel+http=showcase_fan_out_notify · try_catch=showcase_resilient_sync. That map is per node TYPE and stays accurate — this flow adds no new node type. Noted, not filed.
  • The two commits 0f3914a1b and ca9db83f5 carry a model-named Co-Authored-By trailer, written from a dispatch instruction that was corrected mid-task; per AGENTS.md landed history is not rewritten, so they stand and are reported rather than amended. Later commits carry the model-free pair.

Authored in session session_01ToDPcx9AESFubJkDiFMtKW.


Generated by Claude Code

WIP: the `defineFlow` and its `allFlows` entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW
Adds the run pin over `showcase_nested_fan_out_reminders` and records the
fixture in the platform checklist item it unblocks (revision 4).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026

Copy link
Copy Markdown
Collaborator

ACCEPT — 2026-09-10T19:11Z, head 86d90dbb9db3d4010aa1716ad8ce30e2f2e375fb, mergeable_state: clean.

Landing pre-checks, each measured:

  1. In-seat Clause-② review — not owed. Clause-②: no, no needs:contract-review on either carrier, and Limb B is measured rather than assumed: NestedFanOutRemindersFlow is a new exported symbol, but examples/app-showcase/package.json declares private: true with no files field and its exports map does not include the flows index. Empirically, the symbol and showcase_nested_fan_out_reminders each get 0 hits across the repo's built dist trees, positive control ExecutionStepLogSchema 14. ⇒ nothing publishable moves, which is also why skip-changeset is the route.
  2. Carriers — node scripts/pm/check-clause2-carriers.mjs --pair 17514 ⇒ EXIT 0.
  3. Every check green on this head, not a required subset — distinct: 32 | {success: 26, skipped: 6}, NOT GREEN: none.

What this seat checked beyond green:

  • ⭐ The clause is unblocked by its own oracle, not by a compiling defineFlow. A real isolated showcase boot, a triggered run, the run detail read back: six step records where iteration and branch are populated together, branch: 0 included, all four (iteration, branch) pairs appearing exactly once across two rows, and the enclosing parallel carrying regionKind: loop-body with an iteration and no branch of its own. That is the shape the 2026-09-03 ruling made the only one that populates both keys.
  • ⭐ The out-of-surface file is forced, not chosen, and the card's cost estimate was wrong. docs/qa/platform-checklist/areas/automation.json recorded the fixture as absent; landing the flow alone would have left the QA ledger asserting its own absence and a runner would still have scored blocked(fixture). The estimate 「the defineFlow plus its array entry」 would have produced a PR that did not do what the card is for. Everything else in premise B held — no gate enumerates showcase flows by path, showcase-smoke.yml is workflow_dispatch + nightly and non-blocking, and check:i18n-coverage owes nothing, that last one with a firing control: dropping one translated zh-CN label reddens it, 「untranslated declared strings grew 414 to 415」.
  • The ablation reports its direction as observed, not as predicted. Leg 2 was expected to fail on the pair list and instead failed earlier, refused at registration by validateControlFlow (「a parallel block needs at least 2 branches」). Leg 1 is therefore the leg that proves the pin measures the nesting, and the report says so instead of rounding the outcome to the prediction. Both legs mutate committed state, prove it on disk by blob hash and by occurrence counts, and restore by state (git diff HEAD empty, git status --porcelain empty).
  • Scope held. No try_catch was added between the loop and the parallel — deliberately, since a containment region retags the leaf steps and the branch position stops being readable. And the loop { try_catch } follow-up triage asked for was reported, not filed: measured already closed on main (BatchRemindersFlow, type: 'loop' at :898 with type: 'try_catch' at :917). The seat's decision and its reasoning are on card app-showcase nests no control-flow construct inside another, so the QA checklist's new loop { parallel } clause can only score blocked(fixture) #16356.

⚠️ Two commits on this branch, 0f3914a1b and ca9db83f5, carry a model-named Co-Authored-By trailer. That is this seat's defect, not the dev's — my dispatch order specified it, contradicting AGENTS.md 440–444, which requires the pair to stay model-free. Per that same passage landed history is not rewritten, so they stand; the dev reported them rather than amending, which is correct. Recorded as correction 109 on seat post #6021, and the wider measurement (73 of the last 200 landed commits on main carry a model identifier, control 193) is on #17280, which already owns that gap.

Flipped ready and armed. ⛔ Not approved and ⛔ not merged by this seat; the merge queue lands it.


Generated by Claude Code

@os-sales
os-sales enabled auto-merge September 10, 2026 19:11
@os-sales
os-sales added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit cac62d5 Sep 10, 2026
45 of 46 checks passed
@os-sales
os-sales deleted the claude/issue-16356-showcase-loop-parallel branch September 10, 2026 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app-showcase nests no control-flow construct inside another, so the QA checklist's new loop { parallel } clause can only score blocked(fixture)

2 participants