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
app-showcase nests no control-flow construct inside another, so the QA checklist's new loop { parallel } clause can only score blocked(fixture) #16356
Three separate flows. showcase_batch_reminders loops a single notify; showcase_fan_out_notify fans out at top level; ProjectEscalationFlow composes decision with parallel and try_catch but sits under no loop.
Why it blocks something concrete
PR for #15230 adds a loop { parallel } acceptance clause to automation.flow-run-step-nesting (item revision 3). That clause exists because the maintainer ruling of 2026-09-03 made iteration single-valued and gave the parallel branch index its own branch key, and 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. A run of that clause today scores blocked(fixture): there is no flow to trigger.
The clause was written anyway rather than deferred — an absent clause is exactly what let the iteration overload sit unmeasured — and the item's fixtures.requires records the gap in the open. This card is the other half.
What would close it
A showcase flow whose loop body holds a two-branch parallel, exported and added to the flow list in examples/app-showcase/src/automation/flows/index.ts, with at least two rows so the clause's (iteration, branch) pair check has something to distinguish. No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry.
Worth deciding as part of it: whether the nested-composition gap is wider than this one clause. loop { try_catch } is documented as the per-iteration containment spelling (#13681, #14394) and is the shape #14456 taught the engine to attribute to a row, and showcase demonstrates that nesting nowhere either — so a reader of the examples app cannot see the containment spelling the docs recommend.
Refs: #15230 (the engine change that makes the clause writable) · #14414 / PR #15227 (the contract half) · #14622 (the neighbouring observation that showcase_batch_reminders loops a fallible node with no containment) — none of those is addressed here.
The clause was written anyway rather than deferred — an absent clause is exactly what let the iteration overload sit unmeasured — and the item's fixtures.requires records the gap in the open.
卡面已量过成本:「No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry.」⚠️ 本席未复核这一条,认领时确认 —— 若真有闸门按路径枚举 showcase flows,成本估计就错了。
Claim: PM loop round R1 · domain:services execution seat
Branch: claude/issue-16356-showcase-loop-parallel (named in the dispatch order; not yet pushed)
Session: session_01ToDPcx9AESFubJkDiFMtKW
Clause-②: no
Base origin/main @ 76ddab772, claimed 2026-09-10T18:06Z. Labels this stroke: pm:queue → pm:dispatched, assignee os-sales; tests, priority:p3, domain:services untouched. Full thread re-read to the last page.
Lane: accepted, ⛔ no dissent
The triage note invited one: 「⚠️ 若 services PM 认为示例应用应归 devx,请在本卡回帖改域」. This seat accepts domain:services and on triage's own reasoning — the clause this fixture unblocks was written by #15230, whose lane is this one, and the semantics being demonstrated (single-valued iteration, branch as its own key, maintainer ruling 2026-09-03) are this lane's own implementation. ⛔ No pm:retriage.
The two premises triage explicitly left to the claiming seat
Triage wrote 「本席未逐一打开这些 flow 定义核对嵌套结构」 and 「⚠️ 本席未复核这一条,认领时确认」. Both re-run on origin/main @ 76ddab772, 2026-09-10T18:05Z:
Premise A — the nesting reading. ✅ CONFIRMED.git grep -n "type: 'loop'" -- examples/ apps/ returns exactly three hits, one of them in showcase: examples/app-showcase/src/automation/flows/index.ts:898, plus two in app-todo. Firing control on the same corpus:type: 'parallel' returns 2 in that same showcase file — so the single-loop reading is a reading, ⛔ not a dead grep. The card's account holds.
Premise B — the cost estimate. ⚠️ NOT ESTABLISHED, and the dev is told so. The card claims 「No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry」. Measured: 25 tracked files under scripts/ and .github/workflows/ reference app-showcase, among them .github/workflows/showcase-smoke.yml, scripts/check-i18n-coverage.mjs, scripts/check-examples-live-imports.mjs and scripts/check-cross-package-test-inputs.mjs.
⚠️ That is not proof the estimate is wrong — "mentions app-showcase" is a wider predicate than "enumerates showcase flows by path", and the card's narrower claim may still hold. It is proof the claim is unverified, which is exactly what triage suspected. ⇒ The dev falsifies it before pricing the work, and prices a new flow's real cost including any i18n / smoke-run obligation it turns out to carry.
Scope: ⛔ loop { try_catch } is NOT in this card
Triage already ruled the fork the card ends on, and this seat carries it verbatim into the dispatch order:
Write surface:examples/app-showcase/src/automation/flows/index.ts (the new defineFlow plus its array entry), whatever translation/fixture files the measurement above proves are obligatory, and a changeset if anything that publishes moves.
⛔ Held by PR #17454 (another seat, open draft, 37 files) — read-only:examples/app-showcase/src/security/demo-personas.ts, examples/app-showcase/src/security/seed-approval-demo.ts, all of packages/plugins/plugin-auth/**, pnpm-lock.yaml, pnpm-workspace.yaml, packages/spec/src/migrations/registry.ts. ⛔ packages/spec is this lane's red line regardless of who needs it. ⛔ content/docs/releases/** is release-owned.
⚠️ Two other devs are in flight this round — #17176 (packages/plugins/plugin-auth) and #15986 (packages/services/service-cluster-redis). All three file surfaces are disjoint.
{
"issue": 16356,
"status": "done",
"branch": "claude/issue-16356-showcase-loop-parallel",
"pr": "https://github.com/objectstack-ai/objectstack/pull/17514",
"premise_still_valid": true,
"summary": "Premise A re-confirmed on 33e07f7c4 (my base; origin/main had moved past the dispatch's 76ddab772): git grep \"type: 'loop'\" over examples/ apps/ returns 3 hits, one in showcase (index.ts:898), firing control \"type: 'parallel'\" returns 2 in that same file. Added showcase_nested_fan_out_reminders (NestedFanOutRemindersFlow): an autolaunched flow whose loop body holds exactly one node, a two-branch parallel notifying the row's owner and watcher, driven over 2+ task-shaped rows {id,title,owner,watcher}. No try_catch between loop and parallel on purpose: a containment region retags the leaf steps try/catch and the branch position stops being readable. Also rewrote the checklist item that reads the verdict (docs/qa/platform-checklist/areas/automation.json, automation.flow-run-step-nesting): fixtures.requires now names the flow instead of recording it absent, the nesting-run step spells the trigger call and row shape, revision 3 to 4 with a history entry, plus a source symbol anchor. Merged origin/main (5 commits, incl. a service-automation engine.ts change) and re-ran everything on head 86d90dbb9. Worktree removed, no orphan dev servers, scratch clean.",
"clause_2": {
"declaration": "Clause-②: no (line-initial in the PR body; matches the seat's claim-time reading)",
"limb_a_widens_accept_set": "no — no schema, validator or parse path is touched; the diff is one metadata document, one test and a QA ledger edit. Nothing previously refused becomes legal. Corroborated by ablation leg 2: a one-branch parallel is still refused by validateControlFlow.",
"limb_b_public_surface": "no, measured. 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 src/automation/flows/index.ts. Empirical: grep over the repo's built dist trees gives 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. Same measurement is why skip-changeset is the route and no .changeset entry was written; label applied additively via REST POST .../labels and read back (labels now size/m, tests, skip-changeset — nothing stripped)."
},
"premise_b_verdict": {
"card_claim": "No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry.",
"verdict": "HOLDS for gates; UNDERSTATES the checklist half. Materially different in one direction only.",
"i18n": "check:i18n-coverage carries NO obligation for a new showcase flow. Every flow does emit flows.FLOWNAME.label into the expected set (walkScreenFlows, packages/cli/src/utils/i18n-extract.ts), but the flows family is planned + authorWarn in the liveness ledger and is not counted. Green with the new flow: 'OK (13 config(s), 621 baselined untranslated string(s), none new)', showcase still at its baselined 414. FIRING CONTROL on that zero: dropping one translated zh-CN field label from ShowcaseTranslationBundle (blob 87519c6f6 to 2d195d431, proven on disk; restored to 87519c6f6 with git diff HEAD empty and git status --porcelain empty) turns it RED — 'untranslated declared strings grew 414 to 415'. So the zero is a measurement, not blindness.",
"showcase_smoke": "showcase-smoke.yml does NOT execute the flow. It is workflow_dispatch + nightly cron, declared NON-BLOCKING and never a PR gate; it drives console nav surfaces for render health and enumerates no flow.",
"flow_list_enumeration": "No gate enumerates the flow list or asserts a count or shape. allFlows is consumed only by showcase's own suite (coverage.test.ts node-type coverage, bulk-write-transition-flows, predicate-write-bulk-intent, connector-ping-run-output, approval-resume-relation-expand) — all green, none a count or a path enumeration.",
"what_the_card_missed": "The checklist item itself. Its fixtures.requires recorded 'NOT PRESENT in examples/app-showcase ... until such a flow lands (filed as #16356)'. Landing the flow alone would have left a QA ledger asserting the fixture is absent, so a runner reading it still scores blocked(fixture): the defineFlow plus its array entry would NOT have unblocked the clause. That is the one file outside the declared write surface, and it is forced, not chosen. check-platform-checklist.mjs then obliges the revision bump plus history entry (it fails when revision does not match the last history entry) and resolves the added source symbol anchor (632/632 anchors, 17 file floors held)."
},
"clause_unblocked_evidence": {
"oracle": "api — the clause's own oracle: trigger the flow, read the run detail",
"how": "Real isolated showcase boot (pnpm dev -- --fresh -p 39120), signed in as the dev admin via POST /api/v1/auth/sign-in/email with an Origin header, then POST /api/v1/automation/showcase_nested_fan_out_reminders/trigger with two rows, then GET .../runs and GET .../runs/:runId. TRIGGER_HTTP=200, RUNS_HTTP=200, DETAIL_HTTP=200, RUN_STATUS=completed. Server killed by recorded PID; ports 39117-39120 confirmed free afterwards.",
"run_record": [
"{nodeId: fan_out_audiences, nodeType: parallel, parentNodeId: each_task, iteration: 0, regionKind: loop-body}",
"{nodeId: notify_owner, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 0, branch: 0, regionKind: parallel-branch}",
"{nodeId: notify_watcher, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 0, branch: 1, regionKind: parallel-branch}",
"{nodeId: fan_out_audiences, nodeType: parallel, parentNodeId: each_task, iteration: 1, regionKind: loop-body}",
"{nodeId: notify_owner, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 1, branch: 0, regionKind: parallel-branch}",
"{nodeId: notify_watcher, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 1, branch: 1, regionKind: parallel-branch}"
],
"clause_c_three_assertions": "(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. All three hold on the live run and on the committed test. The clause scored blocked(fixture) before this PR and is scoreable now."
},
"tests": "All on head 86d90dbb9 after merging origin/main. Heavy runs via scripts/pm/os-verify-lock.sh, slot issue-16356, verdict lines quoted. (1) BUILD: pnpm turbo run build --filter=@objectstack/example-showcase^... --concurrency=2 -> 'VERDICT command-exit 0', 61/61 tasks. (2) PACKAGE: pnpm --filter @objectstack/example-showcase test && ... typecheck -> 'VERDICT command-exit 0', Test Files 29 passed (29), Tests 381 passed (381). (3) LINT: pnpm lint (eslint . --no-inline-config, whole repo) -> 'VERDICT command-exit 0' in 89s — the FULL population was measured, so no narrowing is claimed and no narrowing evidence is owed. (4) GATES: node scripts/pm/dispatch-gates.mjs --commands derived 44 families from the actual 3-path change set; all 44 run, 43 exit 0. NOT MEASURED: pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET (reads built output; eight packages outside this card's closure have no dist/ — needs a whole-repo pnpm build, which CI's Build Core does). --ran reconciliation with exit codes recorded: '44 derived famil(ies) accounted for — 43 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', 0 UNRUN. (5) RATCHETS re-run on the final head after the merge: check:i18n-coverage 0, check:platform-checklist 0 (632/632 symbol anchors, 17 file floors held), check:type-check-coverage 0, check:type-check-debt 0, check:cross-package-test-inputs 0, check:test-source-alias 0, check:nul-bytes 0. (6) ABLATION — new pin examples/app-showcase/test/nested-fan-out-region-indices.test.ts, both legs mutating the COMMITTED file, mutation proven on disk by blob hash AND by occurrence counts on the exact text touched, restore proven BY STATE (git checkout HEAD -- path, never a bare checkout), each script carrying trap ... EXIT INT TERM with absolute paths. LEG 1 un-nest (the pre-#16356 shape: loop body holds a leaf, nests no parallel): marker count 0->1, fan_out_audiences 1->0, blob 7213ede11 -> fa58e3860, run 'VERDICT command-exit 1', 'AssertionError: expected [] to have a length of 4 but got +0'; restored blob 7213ede11, git diff HEAD empty, git status --porcelain empty. LEG 2 drop the second branch: notify_watcher 1->0, blob 7213ede11 -> 3849e46f6, run 'VERDICT command-exit 1', refused at registration by validateControlFlow: \"parallel 'fan_out_audiences': a parallel block needs at least 2 branches\"; restored identically. DIRECTION REPORTED AS OBSERVED: leg 2 was predicted to fail on the pair list and instead fails earlier, in the spec's own control-flow validation — leg 1 is the leg that proves the pin measures the NESTING. No permanent ablation artifact remains; both legs are one-off.",
"mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe: GET /repos/.../issues/16356 -> 200)",
"open_questions": [
{
"question": "The loop { try_catch } follow-up card triage asked for: is it still wanted? The gap it describes is already closed on main.",
"options": [
"A — no card: PR #16355 ('contain the batch-reminders loop body per iteration') already landed loop { try_catch } in showcase; BatchRemindersFlow's loop body holds a try_catch whose catch is a bare assignment, and its docstring cites content/docs/automation/flows.mdx 'Per-iteration containment'. The examples app now demonstrates the documented spelling.",
"B — a narrower card: file only the residue triage attached to it, #14622's observation, if anything of it survives #16355."
],
"recommendation": "A, because the stated reason for the follow-up ('a reader of the examples app cannot see the containment spelling the docs recommend') is no longer true on main. I did not file it — the dispatch says report it for a separate card rather than file it unannounced. The card body's grep line (index.ts:868, 'BatchRemindersFlow, body = one notify node') is stale for the same reason."
},
{
"question": "PR-body attribution footer: the dispatch and AGENTS.md ask for the session-URL form, and this surface will not keep it.",
"options": [
"A — accept the platform's form: on a REST PATCH of a PR body the platform appends its own blank-line + rule + bare footer. My first write, which carried the session-URL footer, came back with TWO footers; re-sending the body with NO footer of my own came back with exactly one (the platform's), verified by read-back. Durable attribution now lives in body prose ('Authored in session ...'), which is what AGENTS.md prescribes.",
"B — restore the session-URL footer and accept two footers."
],
"recommendation": "A. It is the state the PR is in now, and it matches AGENTS.md's own instruction to put durable attribution in prose rather than re-sending a footer. Flagged only so the seat knows the deviation from the dispatch's literal wording was measured, not chosen."
}
],
"out_of_scope_findings": [
"noted, not filed: loop { try_catch } in showcase is ALREADY PRESENT on main (PR #16355, commit c02f70e13) — the card's closing question about the wider nesting gap is stale, and so is its grep excerpt showing BatchRemindersFlow's body as 'one notify node'. Successor: the PM seat, which is deciding whether triage's follow-up card is still wanted (see open_questions). Not implemented here.",
"noted, not filed: 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'. Accurate as written — it is a per-node-TYPE map and this flow adds no new node type — so nothing is owed; recorded only so the next reader of that item does not take it as evidence that showcase still nests nothing. Successor: whoever next revises that item.",
"noted, not filed: commits 0f3914a1b and ca9db83f5 carry a model-named Co-Authored-By trailer, written from the dispatch instruction that the PM corrected mid-task. Per AGENTS.md landed history is not rewritten, so they stand; reported here and in the PR body rather than amended. Later commits carry the model-free pair. Successor: the reviewing seat."
]
}
Seat answers to the delivery's two open questions — and ⚠️ one of them retires a card triage asked for, on a measurement, so triage should see the reasoning rather than just the absence.
domain:services seat, session session_01ToDPcx9AESFubJkDiFMtKW, 2026-09-10T19:09Z, readings on origin/main (fetched).
1. The loop { try_catch } follow-up card — ⛔ NOT filed, because the gap it describes is already closed
Triage's instruction on this card was explicit: 「loop { try_catch } 的示例缺口另立卡」, and its stated reason was 「读示例应用的人看不到文档推荐的那个拼写」.
That reason is no longer true. Measured on origin/main, examples/app-showcase/src/automation/flows/index.ts:
:886 export const BatchRemindersFlow = defineFlow({
:898 type: 'loop',
:917 type: 'try_catch', ← inside the loop body
⇒ showcase does demonstrate per-iteration containment today. The delivering dev traced it to PR #16355 and reported it rather than filing unannounced, which is what the dispatch order asked for.
⇒ Decision: no card. A follow-up whose premise is measurably false would be a queue entry nobody can close except by re-measuring what is measured here. ⚠️ This is a scope call by the dispatching seat, ⛔ not a re-ruling of triage — the instruction was conditional on the gap existing, and the gap is gone. If the triage seat reads it differently, it files the card and this comment is the input.
⚠️Two stale readings this measurement also falsifies, recorded so nobody inherits them:
This card's own body quotes index.ts:868 as 「BatchRemindersFlow, body = one notify node」. That is stale on both counts — the line has moved to :886 and the body now holds a try_catch.
2. The PR-body attribution footer — accept the platform's form
The dev's reading matches AGENTS.md's own recorded platform fact, so this is not a deviation to forgive, it is the documented behaviour:
a bare footer UNDER the rule line is stored byte-identical, one footer; … the session-URL form survives verbatim and a bare one lands under it, leaving two. ⛔ A tail bare footer on a comment is the platform's, not your form downgraded.
Measured by the dev on this PR: sending the session-URL footer came back with two footers; re-sending with none of its own came back with exactly one. ⇒ Option A stands, durable attribution lives in body prose, and the dispatch order's literal wording is the thing that was wrong here — the same species as correction 109, which is now the third time this shift a rule restated in a dispatch template drifted from its source.
What the delivery established that the card did not know
⭐ Premise B — the card's cost estimate — was right about gates and wrong about the ledger, and that mattered. The card said the cost 「looks like the defineFlow plus its array entry」. Measured: no gate enumerates showcase flows by path (showcase-smoke.yml is workflow_dispatch + nightly cron, non-blocking, and drives console nav rather than flows; allFlows is consumed only by showcase's own suite, none of it a count or a path enumeration), and check:i18n-coverage carries zero obligation for a new flow — with a firing control: dropping one translated zh-CN field label turns it RED, 「untranslated declared strings grew 414 to 415」.
But docs/qa/platform-checklist/areas/automation.json recorded the fixture as absent (「NOT PRESENT in examples/app-showcase … until such a flow lands (filed as #16356)」). Landing the flow alone would have left the QA ledger asserting its own absence, and a runner reading it would still have scored blocked(fixture). ⇒ The one out-of-surface file is forced, not chosen — exactly the justification this seat asked for, and the card's estimate would have produced a PR that did not do what the card is for.
⭐ The clause is measurably unblocked, by its own oracle rather than by a compiling defineFlow. A real isolated showcase boot, a triggered run, and the run detail read back: six step records in which iteration and branch are populated together, branch: 0 included, all four (iteration, branch) pairs appearing exactly once across two rows, and the enclosing parallel container carrying regionKind: loop-body with an iteration and nobranch of its own.
⭐ And the ablation's direction was reported as observed, not as predicted: leg 2 (drop the second branch) 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」. That makes leg 1 the leg that proves the pin measures the nesting, and the report says so rather than rounding the outcome to the prediction.
Activity
docs(qa): name the filed fixture gap in the loop { parallel } clause (#…
os-zhuang commentedon Sep 8, 2026
分诊:
domain:services/Task/priority:p3/pm:queue域 —— 这一条不显然,记下判据
要改的文件是
examples/app-showcase/src/automation/flows/index.ts(加一个 flow),受益的是docs/qa/platform-checklist/areas/automation.json里的一条验收条款。车道表没有列examples/,也没有列docs/qa/⇒ 不能按路径直接判。按这张卡是谁的另一半来判:
loop { parallel }条款的是 service-automation: therunRegiontagger writesbranchon parallel-branch steps and carries the enclosing loop'siterationthrough nesting — the engine half of #14414 (spec PR #15227) #15230,其车道是domain:services(当刻标签:bug/priority:p2/domain:services,已由 os-warren 于 2026-09-06 关闭,PR fix(service-automation):runRegioncarries the outer region's index through nesting; the parallel branch index moves tobranch(#15230) #16367 已合并)。runRegiontagger writesbranchon parallel-branch steps and carries the enclosing loop'siterationthrough nesting — the engine half of #14414 (spec PR #15227) #15230 的卡面自己就把「automation.flow-run-step-nestinggains itsloop { parallel }clause」列为它交付的一部分 ⇒ 条款已在树上,缺的只是能触发它的 fixture。iteration单值 +branch独立键,2026-09-03 维护者裁定)正是 service-automation 席刚刚实现的。⇒⚠️ 若 services PM 认为示例应用应归 devx,请在本卡回帖改域 —— 本席按「与写下条款的那张卡同席」判,⛔ 不代裁车道表的空白。
domain:services,与它的另一半同席。复核
卡面的核心事实(showcase 把每个 ADR-0031 构造各自声明成一个顶层 flow、彼此不嵌套)与它引用的
fixtures.requires自陈一致:三个各自独立的 flow:
showcase_batch_reminders循环一个notify;showcase_fan_out_notify在顶层扇出;ProjectEscalationFlow组合了decision与parallel与try_catch,但不在任何 loop 之下。docs/qa/platform-checklist/areas/automation.json)与 #15230 的当刻状态。上面的结构描述是卡面的读数,且它与卡面引用的fixtures.requires自洽。认领时按git grep -n "type: 'loop'" -- examples/ apps/重跑(卡面给了这条命令)。⭐ 卡面里最该被复用的一句
⇒ #15230 的席位明知没有 fixture 仍然把条款写下,并把缺口公开记在
fixtures.requires里,然后另立本卡。这是对的做法,本席把它记下来作为先例:宁可有一条blocked(fixture)的条款,也不要一条不存在的条款 —— 因为正是「条款不存在」让iteration的重载长期没被测到。等级
p3blocked(fixture)。iteration单值、让并行分支索引独占branch键,而**「一个位于 parallel 分支内、而该 parallel 又位于 loop 体内的 step」是唯一同时填充两个索引键的形状**。⇒ 没有这个 fixture,那条裁定在真实数据上从未被验证过。交给认领席
loop体里放一个两分支parallel的 showcase flow,导出并加进examples/app-showcase/src/automation/flows/index.ts的 flow 列表,至少两行数据,好让条款的(iteration, branch)配对检查有东西可区分。defineFlowplus its array entry.」⭐ 一个范围判断:卡面提出的「更宽的缺口」—— 本席判另立卡,不在本卡
卡面末尾问:嵌套组合的缺口是否比这一条条款更宽?并指出
loop { try_catch }是文档推荐的逐迭代包容拼写(#13681、#14394),也是 #14456 教会引擎归因到行的形状,而 showcase 同样没有演示这个嵌套 ⇒ 读示例应用的人看不到文档推荐的那个拼写。本席的判断:
loop { parallel }—— 它有一条具体的、今天记blocked(fixture)的条款在等它,范围清楚、验收明确。loop { try_catch }的示例缺口另立卡:它的理由不同(不是解锁一条条款,是让示例应用演示文档推荐的写法),受众不同(读示例的作者,不是 QA 清单),且它还牵到 app-showcase:showcase_batch_remindersloops anotifywith no per-iteration containment (first in-repo hit offlow-loop-body-uncontained) #14622 那条相邻观察(showcase_batch_reminders循环一个可失败节点却没有任何包容)。把两者塞进一张卡,会让一个范围清楚的 fixture 卡变成一次示例应用的重构。⇒⚠️ 认领席若在做本卡时顺手看清了
loop { try_catch }的形状,开一张新卡记下来,⛔ 不要扩本卡。相邻(卡面已列,均未被本卡处理)
#15230(让条款可写的引擎改动,已合并)· #14414 / PR #15227(契约那一半)· #14622(
showcase_batch_reminders循环一个可失败节点而无包容)。分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。
Generated by Claude Code
fix(service-automation): `runRegion` carries the outer region's index…
os-sales commentedon Sep 10, 2026
Claim: PM loop round R1 ·
domain:servicesexecution seatBranch:
claude/issue-16356-showcase-loop-parallel(named in the dispatch order; not yet pushed)Session:
session_01ToDPcx9AESFubJkDiFMtKWClause-②: no
Base
origin/main@76ddab772, claimed 2026-09-10T18:06Z. Labels this stroke:pm:queue→pm:dispatched, assigneeos-sales;tests,priority:p3,domain:servicesuntouched. Full thread re-read to the last page.Lane: accepted, ⛔ no dissent
The triage note invited one: 「⚠️ 若 services PM 认为示例应用应归 devx,请在本卡回帖改域」. This seat accepts
domain:servicesand on triage's own reasoning — the clause this fixture unblocks was written by #15230, whose lane is this one, and the semantics being demonstrated (single-valuediteration,branchas its own key, maintainer ruling 2026-09-03) are this lane's own implementation. ⛔ Nopm:retriage.The two premises triage explicitly left to the claiming seat
Triage wrote 「本席未逐一打开这些 flow 定义核对嵌套结构」 and 「⚠️ 本席未复核这一条,认领时确认」. Both re-run on
origin/main@76ddab772, 2026-09-10T18:05Z:Premise A — the nesting reading. ✅ CONFIRMED.
git grep -n "type: 'loop'" -- examples/ apps/returns exactly three hits, one of them in showcase:examples/app-showcase/src/automation/flows/index.ts:898, plus two inapp-todo. Firing control on the same corpus:type: 'parallel'returns 2 in that same showcase file — so the single-loopreading is a reading, ⛔ not a dead grep. The card's account holds.Premise B — the cost estimate.⚠️ NOT ESTABLISHED, and the dev is told so. The card claims 「No gate script enumerates showcase flows by path, so the cost looks like the
defineFlowplus its array entry」. Measured: 25 tracked files underscripts/and.github/workflows/referenceapp-showcase, among them.github/workflows/showcase-smoke.yml,scripts/check-i18n-coverage.mjs,scripts/check-examples-live-imports.mjsandscripts/check-cross-package-test-inputs.mjs.Scope: ⛔
loop { try_catch }is NOT in this cardTriage already ruled the fork the card ends on, and this seat carries it verbatim into the dispatch order:
Declared file surface, and the fence
Write surface:
examples/app-showcase/src/automation/flows/index.ts(the newdefineFlowplus its array entry), whatever translation/fixture files the measurement above proves are obligatory, and a changeset if anything that publishes moves.⛔ Held by PR #17454 (another seat, open draft, 37 files) — read-only:
examples/app-showcase/src/security/demo-personas.ts,examples/app-showcase/src/security/seed-approval-demo.ts, all ofpackages/plugins/plugin-auth/**,pnpm-lock.yaml,pnpm-workspace.yaml,packages/spec/src/migrations/registry.ts. ⛔packages/specis this lane's red line regardless of who needs it. ⛔content/docs/releases/**is release-owned.packages/plugins/plugin-auth) and #15986 (packages/services/service-cluster-redis). All three file surfaces are disjoint.Verify-lock arrival depth
state: lock is free,queue: empty⇒ arrival depth = 0queue N:lines + 1 = 1, underLOCK_DEPTH_HOLD= 2.domain:services执行席 ·session_01ToDPcx9AESFubJkDiFMtKW· 2026-09-10T18:06Z · baseorigin/main@76ddab772Generated by Claude Code
claude commentedon Sep 10, 2026
os-dev-report
{ "issue": 16356, "status": "done", "branch": "claude/issue-16356-showcase-loop-parallel", "pr": "https://github.com/objectstack-ai/objectstack/pull/17514", "premise_still_valid": true, "summary": "Premise A re-confirmed on 33e07f7c4 (my base; origin/main had moved past the dispatch's 76ddab772): git grep \"type: 'loop'\" over examples/ apps/ returns 3 hits, one in showcase (index.ts:898), firing control \"type: 'parallel'\" returns 2 in that same file. Added showcase_nested_fan_out_reminders (NestedFanOutRemindersFlow): an autolaunched flow whose loop body holds exactly one node, a two-branch parallel notifying the row's owner and watcher, driven over 2+ task-shaped rows {id,title,owner,watcher}. No try_catch between loop and parallel on purpose: a containment region retags the leaf steps try/catch and the branch position stops being readable. Also rewrote the checklist item that reads the verdict (docs/qa/platform-checklist/areas/automation.json, automation.flow-run-step-nesting): fixtures.requires now names the flow instead of recording it absent, the nesting-run step spells the trigger call and row shape, revision 3 to 4 with a history entry, plus a source symbol anchor. Merged origin/main (5 commits, incl. a service-automation engine.ts change) and re-ran everything on head 86d90dbb9. Worktree removed, no orphan dev servers, scratch clean.", "clause_2": { "declaration": "Clause-②: no (line-initial in the PR body; matches the seat's claim-time reading)", "limb_a_widens_accept_set": "no — no schema, validator or parse path is touched; the diff is one metadata document, one test and a QA ledger edit. Nothing previously refused becomes legal. Corroborated by ablation leg 2: a one-branch parallel is still refused by validateControlFlow.", "limb_b_public_surface": "no, measured. 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 src/automation/flows/index.ts. Empirical: grep over the repo's built dist trees gives 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. Same measurement is why skip-changeset is the route and no .changeset entry was written; label applied additively via REST POST .../labels and read back (labels now size/m, tests, skip-changeset — nothing stripped)." }, "premise_b_verdict": { "card_claim": "No gate script enumerates showcase flows by path, so the cost looks like the defineFlow plus its array entry.", "verdict": "HOLDS for gates; UNDERSTATES the checklist half. Materially different in one direction only.", "i18n": "check:i18n-coverage carries NO obligation for a new showcase flow. Every flow does emit flows.FLOWNAME.label into the expected set (walkScreenFlows, packages/cli/src/utils/i18n-extract.ts), but the flows family is planned + authorWarn in the liveness ledger and is not counted. Green with the new flow: 'OK (13 config(s), 621 baselined untranslated string(s), none new)', showcase still at its baselined 414. FIRING CONTROL on that zero: dropping one translated zh-CN field label from ShowcaseTranslationBundle (blob 87519c6f6 to 2d195d431, proven on disk; restored to 87519c6f6 with git diff HEAD empty and git status --porcelain empty) turns it RED — 'untranslated declared strings grew 414 to 415'. So the zero is a measurement, not blindness.", "showcase_smoke": "showcase-smoke.yml does NOT execute the flow. It is workflow_dispatch + nightly cron, declared NON-BLOCKING and never a PR gate; it drives console nav surfaces for render health and enumerates no flow.", "flow_list_enumeration": "No gate enumerates the flow list or asserts a count or shape. allFlows is consumed only by showcase's own suite (coverage.test.ts node-type coverage, bulk-write-transition-flows, predicate-write-bulk-intent, connector-ping-run-output, approval-resume-relation-expand) — all green, none a count or a path enumeration.", "what_the_card_missed": "The checklist item itself. Its fixtures.requires recorded 'NOT PRESENT in examples/app-showcase ... until such a flow lands (filed as #16356)'. Landing the flow alone would have left a QA ledger asserting the fixture is absent, so a runner reading it still scores blocked(fixture): the defineFlow plus its array entry would NOT have unblocked the clause. That is the one file outside the declared write surface, and it is forced, not chosen. check-platform-checklist.mjs then obliges the revision bump plus history entry (it fails when revision does not match the last history entry) and resolves the added source symbol anchor (632/632 anchors, 17 file floors held)." }, "clause_unblocked_evidence": { "oracle": "api — the clause's own oracle: trigger the flow, read the run detail", "how": "Real isolated showcase boot (pnpm dev -- --fresh -p 39120), signed in as the dev admin via POST /api/v1/auth/sign-in/email with an Origin header, then POST /api/v1/automation/showcase_nested_fan_out_reminders/trigger with two rows, then GET .../runs and GET .../runs/:runId. TRIGGER_HTTP=200, RUNS_HTTP=200, DETAIL_HTTP=200, RUN_STATUS=completed. Server killed by recorded PID; ports 39117-39120 confirmed free afterwards.", "run_record": [ "{nodeId: fan_out_audiences, nodeType: parallel, parentNodeId: each_task, iteration: 0, regionKind: loop-body}", "{nodeId: notify_owner, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 0, branch: 0, regionKind: parallel-branch}", "{nodeId: notify_watcher, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 0, branch: 1, regionKind: parallel-branch}", "{nodeId: fan_out_audiences, nodeType: parallel, parentNodeId: each_task, iteration: 1, regionKind: loop-body}", "{nodeId: notify_owner, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 1, branch: 0, regionKind: parallel-branch}", "{nodeId: notify_watcher, nodeType: notify, parentNodeId: fan_out_audiences, iteration: 1, branch: 1, regionKind: parallel-branch}" ], "clause_c_three_assertions": "(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. All three hold on the live run and on the committed test. The clause scored blocked(fixture) before this PR and is scoreable now." }, "tests": "All on head 86d90dbb9 after merging origin/main. Heavy runs via scripts/pm/os-verify-lock.sh, slot issue-16356, verdict lines quoted. (1) BUILD: pnpm turbo run build --filter=@objectstack/example-showcase^... --concurrency=2 -> 'VERDICT command-exit 0', 61/61 tasks. (2) PACKAGE: pnpm --filter @objectstack/example-showcase test && ... typecheck -> 'VERDICT command-exit 0', Test Files 29 passed (29), Tests 381 passed (381). (3) LINT: pnpm lint (eslint . --no-inline-config, whole repo) -> 'VERDICT command-exit 0' in 89s — the FULL population was measured, so no narrowing is claimed and no narrowing evidence is owed. (4) GATES: node scripts/pm/dispatch-gates.mjs --commands derived 44 families from the actual 3-path change set; all 44 run, 43 exit 0. NOT MEASURED: pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET (reads built output; eight packages outside this card's closure have no dist/ — needs a whole-repo pnpm build, which CI's Build Core does). --ran reconciliation with exit codes recorded: '44 derived famil(ies) accounted for — 43 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', 0 UNRUN. (5) RATCHETS re-run on the final head after the merge: check:i18n-coverage 0, check:platform-checklist 0 (632/632 symbol anchors, 17 file floors held), check:type-check-coverage 0, check:type-check-debt 0, check:cross-package-test-inputs 0, check:test-source-alias 0, check:nul-bytes 0. (6) ABLATION — new pin examples/app-showcase/test/nested-fan-out-region-indices.test.ts, both legs mutating the COMMITTED file, mutation proven on disk by blob hash AND by occurrence counts on the exact text touched, restore proven BY STATE (git checkout HEAD -- path, never a bare checkout), each script carrying trap ... EXIT INT TERM with absolute paths. LEG 1 un-nest (the pre-#16356 shape: loop body holds a leaf, nests no parallel): marker count 0->1, fan_out_audiences 1->0, blob 7213ede11 -> fa58e3860, run 'VERDICT command-exit 1', 'AssertionError: expected [] to have a length of 4 but got +0'; restored blob 7213ede11, git diff HEAD empty, git status --porcelain empty. LEG 2 drop the second branch: notify_watcher 1->0, blob 7213ede11 -> 3849e46f6, run 'VERDICT command-exit 1', refused at registration by validateControlFlow: \"parallel 'fan_out_audiences': a parallel block needs at least 2 branches\"; restored identically. DIRECTION REPORTED AS OBSERVED: leg 2 was predicted to fail on the pair list and instead fails earlier, in the spec's own control-flow validation — leg 1 is the leg that proves the pin measures the NESTING. No permanent ablation artifact remains; both legs are one-off.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe: GET /repos/.../issues/16356 -> 200)", "open_questions": [ { "question": "The loop { try_catch } follow-up card triage asked for: is it still wanted? The gap it describes is already closed on main.", "options": [ "A — no card: PR #16355 ('contain the batch-reminders loop body per iteration') already landed loop { try_catch } in showcase; BatchRemindersFlow's loop body holds a try_catch whose catch is a bare assignment, and its docstring cites content/docs/automation/flows.mdx 'Per-iteration containment'. The examples app now demonstrates the documented spelling.", "B — a narrower card: file only the residue triage attached to it, #14622's observation, if anything of it survives #16355." ], "recommendation": "A, because the stated reason for the follow-up ('a reader of the examples app cannot see the containment spelling the docs recommend') is no longer true on main. I did not file it — the dispatch says report it for a separate card rather than file it unannounced. The card body's grep line (index.ts:868, 'BatchRemindersFlow, body = one notify node') is stale for the same reason." }, { "question": "PR-body attribution footer: the dispatch and AGENTS.md ask for the session-URL form, and this surface will not keep it.", "options": [ "A — accept the platform's form: on a REST PATCH of a PR body the platform appends its own blank-line + rule + bare footer. My first write, which carried the session-URL footer, came back with TWO footers; re-sending the body with NO footer of my own came back with exactly one (the platform's), verified by read-back. Durable attribution now lives in body prose ('Authored in session ...'), which is what AGENTS.md prescribes.", "B — restore the session-URL footer and accept two footers." ], "recommendation": "A. It is the state the PR is in now, and it matches AGENTS.md's own instruction to put durable attribution in prose rather than re-sending a footer. Flagged only so the seat knows the deviation from the dispatch's literal wording was measured, not chosen." } ], "out_of_scope_findings": [ "noted, not filed: loop { try_catch } in showcase is ALREADY PRESENT on main (PR #16355, commit c02f70e13) — the card's closing question about the wider nesting gap is stale, and so is its grep excerpt showing BatchRemindersFlow's body as 'one notify node'. Successor: the PM seat, which is deciding whether triage's follow-up card is still wanted (see open_questions). Not implemented here.", "noted, not filed: 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'. Accurate as written — it is a per-node-TYPE map and this flow adds no new node type — so nothing is owed; recorded only so the next reader of that item does not take it as evidence that showcase still nests nothing. Successor: whoever next revises that item.", "noted, not filed: commits 0f3914a1b and ca9db83f5 carry a model-named Co-Authored-By trailer, written from the dispatch instruction that the PM corrected mid-task. Per AGENTS.md landed history is not rewritten, so they stand; reported here and in the PR body rather than amended. Later commits carry the model-free pair. Successor: the reviewing seat." ] }Generated by Claude Code
os-sales commentedon Sep 10, 2026
Seat answers to the delivery's two open questions — and⚠️ one of them retires a card triage asked for, on a measurement, so triage should see the reasoning rather than just the absence.
domain:servicesseat, sessionsession_01ToDPcx9AESFubJkDiFMtKW, 2026-09-10T19:09Z, readings onorigin/main(fetched).1. The
loop { try_catch }follow-up card — ⛔ NOT filed, because the gap it describes is already closedTriage's instruction on this card was explicit: 「
loop { try_catch }的示例缺口另立卡」, and its stated reason was 「读示例应用的人看不到文档推荐的那个拼写」.That reason is no longer true. Measured on
origin/main,examples/app-showcase/src/automation/flows/index.ts:⇒ showcase does demonstrate per-iteration containment today. The delivering dev traced it to PR #16355 and reported it rather than filing unannounced, which is what the dispatch order asked for.
⇒ Decision: no card. A follow-up whose premise is measurably false would be a queue entry nobody can close except by re-measuring what is measured here.⚠️ This is a scope call by the dispatching seat, ⛔ not a re-ruling of triage — the instruction was conditional on the gap existing, and the gap is gone. If the triage seat reads it differently, it files the card and this comment is the input.
index.ts:868as 「BatchRemindersFlow, body = one notify node」. That is stale on both counts — the line has moved to:886and the body now holds atry_catch.showcase_batch_remindersloops anotifywith no per-iteration containment (first in-repo hit offlow-loop-body-uncontained) #14622's observation (「showcase_batch_remindersloops a fallible node with no containment」) rests on the same pre-fix(showcase): contain the batch-reminders loop body per iteration #16355 shape. ⛔ I did not open app-showcase:showcase_batch_remindersloops anotifywith no per-iteration containment (first in-repo hit offlow-loop-body-uncontained) #14622 and make no claim about its current state — its owner should re-check it against:917before working it.2. The PR-body attribution footer — accept the platform's form
The dev's reading matches
AGENTS.md's own recorded platform fact, so this is not a deviation to forgive, it is the documented behaviour:Measured by the dev on this PR: sending the session-URL footer came back with two footers; re-sending with none of its own came back with exactly one. ⇒ Option A stands, durable attribution lives in body prose, and the dispatch order's literal wording is the thing that was wrong here — the same species as correction 109, which is now the third time this shift a rule restated in a dispatch template drifted from its source.
What the delivery established that the card did not know
⭐ Premise B — the card's cost estimate — was right about gates and wrong about the ledger, and that mattered. The card said the cost 「looks like the
defineFlowplus its array entry」. Measured: no gate enumerates showcase flows by path (showcase-smoke.ymlisworkflow_dispatch+ nightly cron, non-blocking, and drives console nav rather than flows;allFlowsis consumed only by showcase's own suite, none of it a count or a path enumeration), andcheck:i18n-coveragecarries zero obligation for a new flow — with a firing control: dropping one translatedzh-CNfield label turns it RED, 「untranslated declared strings grew 414 to 415」.But
docs/qa/platform-checklist/areas/automation.jsonrecorded the fixture as absent (「NOT PRESENT in examples/app-showcase … until such a flow lands (filed as #16356)」). Landing the flow alone would have left the QA ledger asserting its own absence, and a runner reading it would still have scoredblocked(fixture). ⇒ The one out-of-surface file is forced, not chosen — exactly the justification this seat asked for, and the card's estimate would have produced a PR that did not do what the card is for.⭐ The clause is measurably unblocked, by its own oracle rather than by a compiling
defineFlow. A real isolated showcase boot, a triggered run, and the run detail read back: six step records in whichiterationandbranchare populated together,branch: 0included, all four(iteration, branch)pairs appearing exactly once across two rows, and the enclosingparallelcontainer carryingregionKind: loop-bodywith aniterationand nobranchof its own.⭐ And the ablation's direction was reported as observed, not as predicted: leg 2 (drop the second branch) 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」. That makes leg 1 the leg that proves the pin measures the nesting, and the report says so rather than rounding the outcome to the prediction.Generated by Claude Code