Repository navigation
test(service-automation): characterize a config-less wait / boundary_event node at run time - #17937
Conversation
…ry_event` node at run time
Converts a source reading into an observed run. `@objectstack/spec` accepts a
`wait` node whose `waitEventConfig` block is absent entirely — the state a
freshly created node is in — while refusing one whose block is present with
`eventType` omitted. What the executor then does with the accepted document had
never been measured.
Recorded, not endorsed:
- `wait`, no config block: returns `{ success: true, suspend: true,
correlation: 'timer:<nodeId>', output: undefined }` — the `'timer'` default
applies, the timer branch computes no deadline, so no wake-up job is armed
(with a job service present), no `waitUntil` is persisted, and not one log
line is emitted at any level. The run parks until an external `resume()`.
- CONTROL, the same node with `eventType: 'timer'` + `timerDuration: 'PT1H'`:
one `once` job armed, `waitUntil` written, correlation carrying the job's
name — every channel that read zero reads non-zero, so the zeros are a
reading of the absent-config path and not of an inert harness.
- `boundary_event`, no config block: a different execution branch entirely, so
measured separately rather than extrapolated. No executor is registered for
the type, so the run fails loudly — `success: false`, `status: 'failed'`,
`No executor registered for node type 'boundary_event'` — and the sealed
vocabulary names it before any run.
- CONTROL, a fully populated `boundaryConfig`: fails identically, proving the
block is never consulted; and the same flow with a registered type in that
slot runs to completion, proving the fixture is sound.
Executor behaviour is deliberately unchanged: the `?? 'timer'` fallback carries
a comment declaring it intentional, and whether a duration-less timer wait
should warn or refuse is a product question for a successor card.
Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewHead reviewed: Implemented-by: ① Clause-② — re-derived from the DELIVERED diff, ⛔ not inherited from the claim-time prediction and ⛔ not accepted from the reportMeasured in-seat at the reviewed head:
⇒ Clause-②: ② Scope fence — heldThe dispatch fenced this card to measure, ⛔ do not repair. Verified: the diff contains no non-test file, so no executor behaviour moved. The ⭐ And the fence earned its keep. The dispatch forbade extrapolating ③ Measurement discipline — the controls discriminate⛔ A zero whose control also reads zero is not evidence. Every channel that reads zero on the absent-config ⭐ The delivery also corrected the source reading it was sent to confirm: Gates53 families derived and reconciled with Verdict: PASS
Generated by Claude Code |
…erence (objectstack-ai#17943) Fixes objectstack-ai#17656 `skills/objectstack-query/rules/filters.md` taught the pre-ADR-0053 upper-bound rule: that the `$lte` form of `{current_year_end}` on a `datetime` column "stops at midnight on the 31st", and that an author should hand-write a strict-less-than `{next_year_start}` detour. `packages/spec/src/data/calendar-day.ts` (ADR-0053 D-D, its table row at line 20) states the opposite: `$lte`, a `$between` max and a `dateRange` end mean the WHOLE day and compile to the next calendar day, so the detour is unnecessary. Because this file is instructions an agent executes, the wrong rule was being copied into every query written from it. ## What changed One block, `skills/objectstack-query/rules/filters.md` lines 249-251, replaced by the same rule stated by REFERENCE to `calendar-day.ts` / ADR-0053 D-D, mirroring the vocabulary PR objectstack-ai#17651 landed in `packages/spec/src/data/date-macros.zod.ts` ("WHOLE day", "reaches the final instant of Dec 31", "⛔ Do NOT hand-write"). The compile rule is not restated here: restating it is how the three copies came about (triage ruling 5651107129 on this card). ## Acceptance greps (whitespace-flattened, content-anchored, on `be0de0b`) | anchor | before (`84e6b05`) | after (`be0de0b`) | |---|---|---| | `stops at midnight on the 31st` | 1 | 0 | | `half-open` | 1 | 0 | | `calendar-day.ts` or `ADR-0053` | 0 | 1 | | lit control `current_year_end` | 2 | 1 | ## Two readings for `skills/**` (governed; line budget net +0) | reading | before (`84e6b05`) | after (`be0de0b`) | |---|---|---| | `rules/filters.md` lines | 251 | 251 | | `rules/filters.md` bytes, and tokens as ceil(bytes/4) | 8582, 2146 | 8591, 2148 (ceiling 2149, headroom 1) | | catalog `skills/**/SKILL.md` line sum (11 files) | 6134 | 6134 | | `skills/objectstack-query/**` line sum (every file) | 1133 | 1133 | Re-wrap was not used as currency: the three old lines (206 bytes) became three new lines (215 bytes); the +9 bytes sit inside the 14-byte slack the ratchet left. `node scripts/check-skills-token-ratchet.mjs` before: `filters.md` 2146/2149; after: 2148/2149, exit 0 ("34 authored bundle file(s) within their ceilings"); `--self-test`: 65 cases pass. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set taken from git off merge base `84e6b05`; 1 path), run on `be0de0b`, every exit code captured before any pipe, reconciled with `--ran` in the `command :: exit N` form: 23 derived, 23 run, 0 unrun. | command | exit | |---|---| | `node scripts/check-ci-filter-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `node scripts/check-doc-route-spelling.mjs --advisory` | 0 | | `node scripts/check-doc-route-spelling.mjs --self-test` | 0 | | `node scripts/check-skills-token-ratchet.mjs` | 0 | | `node scripts/check-skills-token-ratchet.mjs --self-test` | 0 | | `node scripts/report-test-timings.mjs --self-test` | 0 | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 | | `pnpm check:agent-test-spelling` | 0 | | `pnpm check:corpus-claim-drift` | 0 | | `pnpm check:cross-package-test-inputs` | 0 | | `pnpm check:doc-authoring` | 0 | | `pnpm check:driver-memory-census` | 0 | | `pnpm check:nul-bytes` | 0 | | `pnpm check:pm-governed-merges` | 0 | | `pnpm check:refd-timer-probe` | 0 | | `pnpm check:role-word` | 0 | | `pnpm check:skill-compatibility` | 0 | | `pnpm check:skill-frame-sync` | 0 | | `pnpm check:skill-identifier-liveness` | 0 | | `pnpm check:watch-hint-literal` | 0 | `check:doc-formula-expressions` first exited 3, PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` not built; the gate says "nothing was measured", not a finding). After `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the verify lock (4 tasks, 2m49s, waited 0s) the re-run is the row above: exit 0, verdict line: `✓ check:doc-formula-expressions (field-level `*When`, objectstack-ai#11407): 14 predicate(s) on a statically determinable field layer judged clean; 6 skipped as undeterminable.` No package build or test is owed: the diff touches no package (`turbo ls --affected` would list none; the root-level gates above are outside that graph, which is why they were derived from the path instead). Repo-wide `pnpm lint` is CI's run. ## Changeset Measured: no package `files[]` names or globs `skills/`; the only consumer inside `packages/` is `create-objectstack`, which pulls skills into a scaffolded project at scaffold time via `npx skills add objectstack-ai/objectstack/skills` (from git, not from an npm tarball). Nothing any released npm package ships moves, so `skip-changeset` is applied as a label and read back. ## Premise checks - P1 to P5 of the dispatch held on `84e6b05`: the content of lines 249-251, `calendar-day.ts` line 20, the `next_year_start|midnight` grep hitting only :206 (alias list) and :250-:251 with `current_year_end` = 2 as lit control, PR objectstack-ai#17651's landed wording, and no open PR touching `skills/objectstack-query/**`. The one PR opened after 06:58Z, objectstack-ai#17937, touches a single test file under `packages/services/service-automation`. - One correction: `filters.md` itself carried no `⛔` (0 before the edit). The marker is in use in the same package (`rules/pagination.md`: 2) and in 6 catalog files, and it is the shape PR objectstack-ai#17651 and the triage ruling asked to mirror, so it is used here. - The decision-frame block on this tree matches the dispatch's md5 (`3327d02c56f8a0eca88569dad2270f32`); no fork was hit, so the frame was not needed. ## Acceptance notes No out-of-scope findings. Not filed: line 206 still lists `{next_year_start}` because that is the token alias list, not the rule (P3); and the file's widest line is 94 columns, a pre-existing style matter with no bearer. ## Governed surface `skills/**` is governed (Prime Directive objectstack-ai#14): this PR stays a DRAFT for the maintainer's hand merge. No seat flips it ready, queues it, arms auto-merge or submits a review on it. ## 维护者速读(草稿) **改了什么**:把 `skills/objectstack-query/rules/filters.md` 末尾三行的日期上界规则换掉。原文告诉 agent「`{current_year_end}` 在 datetime 列上只到 31 日零点,要改写成下一年一月一日的严格小于」;新文按 ADR-0053 D-D 引用 `packages/spec/src/data/calendar-day.ts`:上界就是整天,平台已自动补到当天最后一刻,⛔ 不要手写绕路。行数 251 → 251,token 2146 → 2148(上限 2149)。 **为什么改**:这个文件是 agent 照做的指令,不是给人读的散文;错规则会被逐条执行,每条查询都带一个多余且与平台行为相反的绕路。同一句错话已在 `date-macros.zod.ts`(PR objectstack-ai#17651)与 `packages/core` 头注释(objectstack-ai#17655,另一张卡)分别修正;本卡是三份拷贝里最后一份,改法按分诊裁决只引用、不重述。 **风险与代价(含回滚)**:纯文档面,不动任何运行时代码与测试;唯一代价是该文件的 token 余量从 3 降到 1。回滚 = revert 本 commit(单文件三行)。 **席位意见**: **你要做的**:确认措辞后人工直合(governed 面,⛔ 不入队、不 auto-merge);本 PR 首行的关单句会在合并时关闭 objectstack-ai#17656。 --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: os-project-manager <pm@objectstack.ai> Co-authored-by: Claude <noreply@anthropic.com>
…skill (objectstack-ai#17946) Fixes objectstack-ai#17787 ## What changed `skills/objectstack-i18n/SKILL.md` :196–:201 — one sentence, re-flowed within its own six lines. It grouped `translation-target-unknown` and `translation-option-key-unknown` under the single word "warnings"; on the tree the first is raised at `error` and fails the run, the second stays a warning. The sentence now names each id with its own severity. No other file is touched. Before (flattened): > `os validate` / `os lint` / `os compile` check this direction and report it as warnings (`translation-target-unknown`, `translation-option-key-unknown`): a key naming an object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen that does not exist is listed alongside the names that do. … After (flattened): > `os validate` / `os lint` / `os compile` check this direction: `translation-target-unknown` is an **error** and fails the run; `translation-option-key-unknown` is a warning. A key naming an object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen that does not exist is listed alongside the names that do. … The tail of the paragraph (「A bundle keyed to something since renamed still parses — …」) is content-identical; only line breaks moved. ## Severities re-measured on the tree (card acceptance item 2) Measured at `origin/main` `84e6b05` (this branch's base), not against the card: | id | severity on the tree | evidence | |---|---|---| | `translation-target-unknown` | `error` | `packages/lint/src/validate-translation-references.ts` :31 (severity note), :151 `TRANSLATION_TARGET_UNKNOWN_SEVERITY = 'error'`, :155–:156 (field docblock), emitted at :813 and :1265; test `validate-translation-references.test.ts` :306 「raises `translation-target-unknown` at `error`, selectable by its exact id」 | | `translation-option-key-unknown` | `warning` | same file :1210 and :1229 `severity: 'warning'` beside `rule: TRANSLATION_OPTION_KEY_UNKNOWN`; test :327 「leaves `translation-option-key-unknown` at `warning`」 | The card's wording is still current: the option-key rule has not moved since the triage grading. ## Acceptance greps (both directions, this branch at `1974cee`) - `grep -c 'report it as warnings' skills/objectstack-i18n/SKILL.md` → `0` - lit control: `grep -c 'translation-option-key-unknown' skills/objectstack-i18n/SKILL.md` → `1` - `error` inside the same sentence as `translation-target-unknown`: see the flattened "After" sentence above — 「… check this direction: `translation-target-unknown` is an **error** and fails the run; …」 ## Line and token readings for `skills/**` | reading | before (`84e6b05`) | after (`1974cee`) | delta | |---|---|---|---| | `skills/objectstack-i18n/SKILL.md` lines | 494 | 494 | +0 | | `skills/objectstack-i18n/SKILL.md` bytes | 18830 | 18850 | +20 | | `skills/objectstack-i18n/SKILL.md` ratchet tokens (ceil bytes/4) | 4708 (ceiling 6338, headroom 1630) | 4713 (ceiling 6338, headroom 1625) | +5 | | whole package: all `skills/*/SKILL.md` lines summed | 6134 | 6134 | +0 | | whole `skills/objectstack-i18n/` directory lines | 524 | 524 | +0 | | ratchet bundle total (whole shipped tree, tokens) | 139594 | 139599 | +5 | +0 lines is met by re-flowing only the six lines of the edited sentence (new widths 91/81/87/92/88/68; the sentence's own pre-edit maximum was 92, at line 200). Nothing outside the sentence was re-wrapped and no content was deleted to pay for anything. The ratchet prices tokens, not lines; the ceiling is untouched. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on `1974cee` (24 commands — the same 24 the dispatch clue list named), each run in the foreground with its exit code captured before any pipe, then reconciled with `--ran`: `24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN`. | command | exit | |---|---| | `node scripts/check-ci-filter-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `node scripts/check-doc-route-spelling.mjs --advisory` | 0 | | `node scripts/check-doc-route-spelling.mjs --self-test` | 0 | | `node scripts/check-skills-token-ratchet.mjs` | 0 | | `node scripts/check-skills-token-ratchet.mjs --self-test` | 0 | | `node scripts/report-test-timings.mjs --self-test` | 0 | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 | | `pnpm --filter @objectstack/spec run check:skill-docs` | 0 | | `pnpm check:agent-test-spelling` | 0 | | `pnpm check:corpus-claim-drift` | 0 | | `pnpm check:cross-package-test-inputs` | 0 | | `pnpm check:doc-authoring` | 0 | | `pnpm check:driver-memory-census` | 0 | | `pnpm check:nul-bytes` | 0 | | `pnpm check:pm-governed-merges` | 0 | | `pnpm check:refd-timer-probe` | 0 | | `pnpm check:role-word` | 0 | | `pnpm check:skill-compatibility` | 0 | | `pnpm check:skill-frame-sync` | 0 | | `pnpm check:skill-identifier-liveness` | 0 | | `pnpm check:watch-hint-literal` | 0 | - `check:doc-formula-expressions`: the first run exited 3 (`PREREQUISITE NOT MET` — `@objectstack/formula` and `@objectstack/lint` are not built in a fresh worktree), which measures nothing. After `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` (the remedy the gate itself prescribes; run under `scripts/pm/os-verify-lock.sh`, `VERDICT command-exit 0`) it exited 0. The build left the tree clean (`git status --porcelain` printed nothing). - `check:skill-docs` reads only the SKILL.md frontmatter (the catalog); this body edit moves no generated listing — it reports in sync, nothing regenerated. - Not owed, not run: no workspace package is touched (`skills/` is not one), so there is no dependency-closure build and no package `test` / `typecheck` for this diff. The 14 "pending-changeset" families the derivation lists apply only once a changeset exists; this PR ships none (measured below). ## Changeset: `skip-changeset`, measured `skills/**` reaches customer projects at scaffold time through `packages/create-objectstack/src/skills-install.ts` (`SKILLS_CATALOG = 'objectstack-ai/objectstack/skills'`, pulled from the git catalog by the skills CLI) — not inside any npm tarball. Measurement on `1974cee`: 70 non-private `package.json` manifests under `packages/`, `examples/` and `apps/` (node_modules excluded); 0 whose `files[]` mentions `skills`; positive control: 70 whose `files[]` mentions `dist`. `create-objectstack` itself ships `["dist", "README.md", "CHANGELOG.md"]`. No released package's `files[]` ships this path, so nothing published moves ⇒ `skip-changeset`, applied with the additive labels endpoint and read back. ## Governed surface `skills/**` is governed (AGENTS.md Prime Directive objectstack-ai#14). This PR stays a draft: no seat flips it ready, queues it, arms auto-merge or submits a review — the maintainer lands it by hand. ## Acceptance notes - Other carriers of the literal id, re-checked on `84e6b05` with `git grep -n 'translation-target-unknown' origin/main -- skills content docs examples`: `examples/app-showcase/test/seed.test.ts:94` (already says the rule fails) and `docs/audits/2026-07-app-metadata-reference-integrity-assessment.md:143` (a dated audit table of counts). Neither is false; nothing filed. - Open PRs: one was opened after the dispatch's serial reading (objectstack-ai#17937); its file list does not touch `skills/`. - noted, not filed: the skill nowhere else states what an `error`-severity finding does to a run, which is why the sentence spells out "fails the run" instead of relying on the severity word alone. No carrier. ## 维护者速读(草稿) **改了什么**:`skills/objectstack-i18n/SKILL.md` 里的一句话(第 196–201 行)。原句把 `translation-target-unknown` 与 `translation-option-key-unknown` 一起称为「warnings」;现在分别写明:前者是 **error**,会让 `os validate` / `os lint` / `os compile` 这次运行失败;后者仍是 warning。只改这一句,文件行数 494 → 494。 **为什么改**:PR objectstack-ai#17777 已把 `translation-target-unknown` 提到 error 级(`packages/lint/src/validate-translation-references.ts`),技能文件却还说它是 warning。技能是 AI 作者最先读、而且不会去复核的那份说明——写错等于教 AI 把一个会让构建失败的问题当成可忽略的提示。 **风险与代价(含回滚)**:纯文档改动,不发布任何 npm 包(实测 70 个非 private 包的 `files[]` 无一包含 `skills/`),无运行时影响。技能 token 棘轮 +5(上限 6338,余量 1625),不动上限。回滚 = revert 这一个 commit。 **席位意见**:(留空,待席位定稿) **你要做的**:确认这句表述与你对两个规则严重度的预期一致,然后人工合并(受管面 `skills/**`,不走队列)。若 `translation-option-key-unknown` 日后也提级,这句话需要再改一次。 --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: os-project-manager <pm@objectstack.ai> Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17843
Clause-②: no
Refs objectstack-ai/objectui#9109 (that card's grade depends on the reading below; nothing over there gates this one).
What this is
A measurement, converted from a source reading into an observed run. Executor behaviour is
deliberately unchanged — the card says so itself, and the
?? 'timer'fallback inwait-node.tscarries a comment declaring it intentional.The triage seat answered the card from source (comment 5651444086). This PR runs it. The run
agrees with that reading on the
waithalf, so the p1 escalation on objectui#9109 stands andneeds no rollback. The
boundary_eventhalf — which triage explicitly did not measure — comesout the opposite shape, which is exactly why the card refused to extrapolate.
The four readings, as recorded
All four come from real
engine.execute()runs throughAutomationEngine, with the node-typevocabulary sealed (what
AutomationServicePlugindoes atkernel:bootstrapped), a job servicepresent, an
InMemorySuspendedRunStoreattached, and one recording logger behind both the engineand the plugin context so "any log line at any level" is answerable.
1.
wait, no config block at all⇒ the
'timer'default applies, the timer branch computes no deadline, no wake-up job is armedeven though a job service was available, no
waitUntilis persisted, and not one log lineis emitted at any level. The run parks until something external calls
resume(runId).outputis apresent key carrying
undefined, not an absent key. A JSON dump of the return value drops itand reads as the second. The test pins it with
toStrictEqualfor that reason —toEqualcannottell those two apart.
2. CONTROL — the same node WITH
eventType: 'timer'andtimerDuration: 'PT1H'⇒ every channel that read zero above reads non-zero here, so the zeros are a reading of the
absent-config path and not of an inert harness. The one channel that does not discriminate is
status: 'paused'— both halves park the run — and the test records that too, so a later readerdoes not mistake it for a signal.
3.
boundary_event, no config block at all — measured separately, NOT extrapolated⇒ ⭐ the opposite of the
waithalf: loud, and loud twice. The type has no executor at all(
installBuiltinNodesseeds twelve packs, none of them this one; the package README filesboundary_eventas the BPMN interop representation rather than the native authoring model), sothe dispatch fails before any config block could be read. There is no silent suspension on this
branch to find.
4. CONTROL —
boundary_eventwith a fully populatedboundaryConfigFails identically (
success: false, sameerrorstring, same step failure) ⇒ the block isnever consulted. A third control puts a registered type in the same slot in the same flow and
the run completes (
ran: [ 'before', 'b', 'after' ]), so the failure above is the node type, notthe fixture.
Premise check
The card's premise holds on the tip, verified rather than assumed, and is pinned in the test:
FlowNodeSchemaaccepts awaitnode with nowaitEventConfigat all, refuses one whoseblock is present with
eventTypeomitted, and accepts aboundary_eventnode with noboundaryConfig. So the documents measured above are ones an author can really save.The durable form
packages/services/service-automation/src/builtin/absent-config-node-characterization.test.ts—named, titled and headed as a characterization, with the header stating in as many words that
every assertion is a photograph and not an endorsement, that a red here means "the recorded
behaviour moved" (possibly on purpose), and that whether a duration-less timer wait should warn or
refuse is a product question for a successor card rather than anything this file blesses.
Verification
pnpm --filter @objectstack/service-automation test— 133 files, 1571 tests, all pass (thenew file is 7 of them).
pnpm --filter @objectstack/service-automation typecheck— exit 0.pnpm --filter @objectstack/service-automation build— exit 0; dependency closure built firstvia
pnpm --filter '@objectstack/service-automation^...' build.scripts/pm/dispatch-gates.mjsand reconciled with--ran: 53 derived, 51 run green, 0 unrun, 2 NOT MEASURED —check:dual-build-cjs-loadsandcheck:type-check-debt, both exit 3(
PREREQUISITE NOT MET: each reads built output for the whole workspace, whichlint.ymlbuildsbefore invoking them). Neither is a finding, and this diff emits nothing into any
dist/forthem to read.
pnpm lint(the repo-wideeslint . --no-inline-config) — exit 0, whole population, nonarrowing claimed.
pnpm check:nul-bytesgreen, plus a direct control-character scan of the added file(
grep -naPover the C0 set plus DEL) — no matches.Gate log and reconciliation taken at
d5e7d32c3, the final commit.Changeset
skip-changeset, measured rather than assumed: the package publishesfiles: ["dist", "README.md", "CHANGELOG.md"], and after a real build a probe symbol unique to theadded file appears in 0 of those paths, while the positive control
rearmSuspendedWaitTimersappears in 2 files under
dist/. Nothing already published moves.Clause-② re-derived from the delivered diff:
no. The diff is exactly one added file, a*.test.tsundersrc/, outsidefiles[]and unreachable from the published entry(
exports: { "." }resolves intodist/). It adds no exported symbol reachable from a publishedentry and no key on an already-published payload — the file declares no exports at all, and in any
case the grading test is reachability, not the appearance of the word export in a diff.
Acceptance notes
Out of scope for this card, noted rather than filed:
The card states it is not a request to change anything, and the dispatching seat reserved the
successor question — should a duration-less timer wait warn or refuse? — for itself.
outputbeing a present-but-undefinedkey rather than an absent one is invisible to a JSONrendering of the return value. An observation about how the value reads, not a defect.
completedorfailedrun gets an[automation] run flow=... status=...line. Consistent with "paused is not terminal"; recordedbecause it is the other half of why the absent-config wait is silent end to end.
boundary_eventparses clean and then fails the run withNO_EXECUTOR. Not filed: the enginetreats "a node type nothing registered" as a declared, handled state with a startup warning that
names the type, and the package README declares this type interop-only. Loud by design, not a
trap.
Generated by Claude Code