test(swim-37): wire captureSwim() for continue_work primitive (#324)#405
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea3c9017fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const delivery = opts.delivery ?? "immediate"; | ||
| const chainStepRemaining = opts.chainStepRemaining ?? 5; | ||
| const delayMs = opts.delayMs ?? 0; |
There was a problem hiding this comment.
Enforce coherent delegate delivery and delay defaults
captureSwim("continue_delegate") currently treats delivery and delayMs as independent knobs (delivery defaults to "immediate", delayMs to 0) and never validates contradictory combinations. This allows synthetic spans like delegate.delivery="timer" with delay.ms=0 (or "immediate" with positive delay), which do not match runner behavior where timer delivery is emitted only when a positive clamped delay arms setTimeout (see delegate dispatch call sites in src/auto-reply/reply/agent-runner.ts). That can make harness tests pass against span states that cannot occur in production.
Useful? React with 👍 / 👎.
2987579 to
efea018
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efea018837
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const delivery = opts.delivery ?? "immediate"; | ||
| const chainStepRemaining = opts.chainStepRemaining ?? 5; | ||
| const delayMs = opts.delayMs ?? 0; |
There was a problem hiding this comment.
Couple delegate delivery mode to delay semantics
captureSwim("continue_delegate") currently derives delivery and delayMs independently (delivery defaults to "immediate", delayMs defaults to 0), so callers can accidentally produce harness spans that do not match runner dispatch behavior (for example delayMs > 0 with delegate.delivery="immediate"). In src/auto-reply/reply/agent-runner.ts, dispatch sites always pair these coherently (immediate with delayMs: 0, timer with the clamped timer delay), so the harness can pass tests against span shapes that production would not emit.
Useful? React with 👍 / 👎.
efea018 to
b9f64c0
Compare
Adds studies/swim-37/harness/helper-fire-and-release-contract.test.ts covering the four landed Slice-2 emit helpers that 🌊's #406 helper-tier file did NOT cover: chunks 5b (continuation.delegate.fire), 5c (continuation.work.fire), 6a (continuation.queue.drain), and 6b (continuation.compaction.released). Layering: HELPER-TIER ONLY. Drives src/infra/continuation-tracer emit helpers directly through the InMemorySpanRecorder shim (#394) and asserts recorded span CONTRACT. Does not touch agent-runner / pi-embedded-runner / subagent-announce — the runtime callsites are exercised through the integration tier in swim-runner.test.ts (🌻's #405 captureSwim shim). LANE-CLEAN: original swim-runner.test.ts is untouched here (avoiding collision with #405 which converts swim-runner it.todos in-place). Companion to 🌊's #406 emit-helper-contract.test.ts (which covers chunks 3/4/6c) — same layer, different spans, zero overlap. Key contract pin per 🩸's lane-clear note (msg 1498508045725204500): the snapshot semantic for fire spans lives in the PARAMETER name (chainStepRemainingAtDispatch) and the docs; emitted attr stays canonical chain.step.remaining. We negative-assert chain.step.remaining_at_dispatch is NEVER present so a future helper change can't silently invent it. Coverage: - continuation.compaction.released (#397): exactly-once emit, signal.kind + integer compaction.released, compaction.id valid path, validate-and-drop-with-log on invalid (chunk 6c §B + #401) - continuation.queue.drain (#395): integer count attrs, defense-in-depth cap on continuation subset (per 🩸 #395 byte-walk) - continuation.delegate.fire (#388 chunk 5b): persisted chain.id + canonical attr names + negative-assert .at_dispatch absent; fire.deferred_ms integer-floor + drift formula; reason.preview 80-char truncation + omission - continuation.work.fire (#388 chunk 5c): canonical attr name (5c symmetric to 5b minus delegate.* attrs); no continuation.disabled sibling (chunk 5c scope); chainId-invariant violation no-ops + logs without throwing Tests: 12 (all live, no it.todo). Lint clean. Refs: #324 #334 #388 #395 #397 #401 #394 #406 (sibling) #405 (sibling)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 08aed3bc9f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -0,0 +1,113 @@ | |||
| # swim-37 harness — `continue_delegate` wiring memo | |||
There was a problem hiding this comment.
Remove em dash from docs heading
docs/AGENTS.md explicitly says headings in docs/**/*.md should avoid em dashes because Mintlify anchor generation is brittle; this new H1 includes an em dash (swim-37 harness — ...). That can produce unstable or mismatched anchor IDs in published docs, which breaks direct links to this memo section. Please rewrite the heading punctuation (for example, use a colon or hyphen) so anchor generation stays deterministic.
Useful? React with 👍 / 👎.
…407) Adds studies/swim-37/harness/helper-fire-and-release-contract.test.ts covering the four landed Slice-2 emit helpers that 🌊's #406 helper-tier file did NOT cover: chunks 5b (continuation.delegate.fire), 5c (continuation.work.fire), 6a (continuation.queue.drain), and 6b (continuation.compaction.released). Layering: HELPER-TIER ONLY. Drives src/infra/continuation-tracer emit helpers directly through the InMemorySpanRecorder shim (#394) and asserts recorded span CONTRACT. Does not touch agent-runner / pi-embedded-runner / subagent-announce — the runtime callsites are exercised through the integration tier in swim-runner.test.ts (🌻's #405 captureSwim shim). LANE-CLEAN: original swim-runner.test.ts is untouched here (avoiding collision with #405 which converts swim-runner it.todos in-place). Companion to 🌊's #406 emit-helper-contract.test.ts (which covers chunks 3/4/6c) — same layer, different spans, zero overlap. Key contract pin per 🩸's lane-clear note (msg 1498508045725204500): the snapshot semantic for fire spans lives in the PARAMETER name (chainStepRemainingAtDispatch) and the docs; emitted attr stays canonical chain.step.remaining. We negative-assert chain.step.remaining_at_dispatch is NEVER present so a future helper change can't silently invent it. Coverage: - continuation.compaction.released (#397): exactly-once emit, signal.kind + integer compaction.released, compaction.id valid path, validate-and-drop-with-log on invalid (chunk 6c §B + #401) - continuation.queue.drain (#395): integer count attrs, defense-in-depth cap on continuation subset (per 🩸 #395 byte-walk) - continuation.delegate.fire (#388 chunk 5b): persisted chain.id + canonical attr names + negative-assert .at_dispatch absent; fire.deferred_ms integer-floor + drift formula; reason.preview 80-char truncation + omission - continuation.work.fire (#388 chunk 5c): canonical attr name (5c symmetric to 5b minus delegate.* attrs); no continuation.disabled sibling (chunk 5c scope); chainId-invariant violation no-ops + logs without throwing Tests: 12 (all live, no it.todo). Lint clean. Refs: #324 #334 #388 #395 #397 #401 #394 #406 (sibling) #405 (sibling)
ronan-dandelion-cult
left a comment
There was a problem hiding this comment.
🌊 second-eye on #405 (HEAD 08aed3bc9f).
Walked captureSwim() substrate (studies/swim-37/harness/swim-runner.ts, 179 lines):
- STDOUT-only discipline preserved ✓ —
createInMemorySpanRecorder()+setContinuationTracer+resetContinuationTracer()infinally. NoBasicTracerProvider, no real exporter, no live OTLP. - Per-call isolation correct. Fresh recorder installed per invocation, tracer reset in
finallyso leaks across back-to-back calls are byte-impossible. - Per-recipient fan-out shape matches the chunk-3 cohort pin. N recipients → N
emitContinuationDelegateSpancalls sharingchain.id, NOT one span with a recipients list. That's the load-bearing call from the Q3 design memo and 🩸's per-recipient-distinction caution. Comment on lines 130-135 documents the budget-vs-cardinality split (#355 Stage-2 = budget concern, fan-out cardinality = OTEL concern). Right axis-split. recipientsvalidates positive integer (line 121) — refuses zero, refuses non-integer, throws synchronously not silently. Good.heartbeatandlichthrow with message pointing at primitive-coverage matrix rather than returning empty result. Honest about what's wired vs scaffolded.delegateMode: undefinedexercises omission contract correctly — caller passesundefinedand the helper conditionally spreads, sodelegate.modeis absent from the attribute bag (test L? covers this).delivery: 'immediate' | 'timer'axis matches helper contract from #406 / #407.- Default
chainStepRemaining: 5sidesteps the 0-clamp code path, exercisesMath.max(0, …)headroom without tripping empty-budget. Sensible default.
Walked test wiring:
- L80 (
§1 trap §1 :: rebase-classifier DROP) flipped onto liveclassifyRebasePickimport from #408 with the correct memo-anchor synthetic (#70595/7ee46a3ab9). Channel asserted aschangelog-grep:pr. Byte-pin clean. - L81 left as
it.todoper the agreed handoff — needs production callsite that pairs discovery withemitContinuationDisabledSpan. Right deferral.
Layering pinned:
- #406 (helper-tier 3/4/6c, MERGED)
- #407 (helper-tier 5b/5c/6a/6b)
- #408 (§1 substrate, MERGED) → L80 paid down by this PR
- #405 (integration-tier
captureSwim()drivingcontinue_work+continue_delegate)
No overlap, no inverted dependencies, gap-pin (recipient.index) in place to fail loudly when production helper grows the axis. LGTM. — 🌊
silas-dandelion-cult
left a comment
There was a problem hiding this comment.
🌫️ second-eye on captureSwim() integration tier.
Boundary discipline — try/finally with resetContinuationTracer() in the finally block guarantees no tracer-state pollution across calls. The "repeated calls do not leak capture state" test pins this invariant explicitly. ✓
Type-safety — exhaustive never switch on SwimPrimitive, integer + positive validation on recipients with informative error message, reserved primitives (heartbeat/lich) throw rather than silently returning empty. The "refuses primitives not yet wired" test makes the it.todo honesty contract observable. ✓
STDOUT-only discipline preserved — no BasicTracerProvider/BatchSpanProcessor/OTLP wired in, all capture flows through createInMemorySpanRecorder(). The doc comment makes this commitment load-bearing. ✓
Fan-out semantics correct — for (i=0; i<recipients; i++) emits N dispatch spans sharing one chain.id, matches chunk-3 cohort pin (each recipient is an observable dispatch event, budget is one step #355 Stage-2). The GAP-PIN test asserting current spans are byte-identical (no recipient.index) is exactly the right shape — pins observed reality so the production-helper follow-up issue surfaces it loudly. ✓
Q3 matrix — 8-cell it.each + omission row at 9 total, under the split-threshold 🌊 flagged. Reads cleanly. ✓
§1 it.todo flip to classifyRebasePick byte-pinned to 7ee46a3ab9 / #70595 — composes the merged §1 substrate cleanly, refuses to silently default to PICK on no-signal (proven via the channel attribution assert). ✓
LGTM. Ship it. — 🌫️
Replaces the placeholder `declare function captureSwim` in `studies/swim-37/harness/swim-runner.test.ts` with a real implementation in `studies/swim-37/harness/swim-runner.ts`. The wired path drives `emitContinuationWorkSpan` against `createInMemorySpanRecorder()` and returns the captured spans + the synthesized `chainId` (uuid v7 via `generateChainId`). Flips two prior `it.todo` markers to live tests: - emits continuation.work span with chain.id stamped (#366) - span carries chain.step.remaining attribute Plus three new live tests: - sentinel: captureSwim() is wired for continue_work - refuses continue_delegate / heartbeat / lich primitives with a clear error so the spec's remaining `it.todo` markers stay honest about what is implemented - repeated calls don't leak capture state between invocations STDOUT-only discipline preserved: no BasicTracerProvider, no OTLP exporter, no @opentelemetry/sdk-trace-base machinery — capture flows through `setContinuationTracer(recorder.tracer)` as documented in README.md. Other primitives (`continue_delegate`, `heartbeat`, lich-shape) remain `it.todo` until the corresponding dispatch / heartbeat / compaction-release seams have a comparable single-helper entry point we can drive synthetically. Test results: - swim-37 vitest project: 20 passed, 16 todo, 0 failed - in-memory-span-recorder.test.ts unchanged (12/12 still pass) - tsgo errors visible elsewhere in tree are pre-existing (src/plugin-sdk/provider-tools.ts), not introduced by this change Refs #324 (swim-37 harness) — `it.todo` count drops from 17 \u2192 16, first concrete primitive driven through the live tracer registry.
Pre-PR design memo for the next swim-37 primitive after #405 (continue_work). Decides shape before the wire so the PR lands clean. Resolved by reading production code: Q1 — real setTimeout vs fake timers N/A for dispatch-accept (synchronous, pre-timer); banked for a separate continue_delegate_fire swim. Cohort sign-off requested on: Q2 — recipient fan-out shape: N spans sharing chain.id (matches chunk-3 cohort design pin + #355 Stage-2 budget semantics). Q3 — delegate.mode x delegate.delivery: 8-cell it.each matrix + omission-contract row. Banked for follow-up: Q4 — continue_delegate_fire as separate primitive, not sub-case; informs SwimPrimitive type-union shape. Standard applied (per Cael 2026-04-27 #1498505918, figs #1498505870): would skipping this memo cost a chunk's worth of rework? Yes — three open Qs would have changed file shape. Refs: - #405 (continue_work wired) - #324 (swim-37 harness) - docs/design/334-slice2-chunk5b-delegate-fire-memo.md (pattern)
Implements the design from `docs/design/swim-37-continue-delegate-wiring-memo.md` (commit 3bb086c762, same branch). Wiring (swim-runner.ts): - New continue_delegate case in the switch. - Adds CaptureSwimOptions axes: recipients, delivery, delegateMode. - Drives emitContinuationDelegateSpan once per recipient with shared chain.id (per chunk-3 cohort design pin: N recipients = N spans sharing chain.id, NOT one span with a recipients list). - Validates recipients is a positive integer. Tests (swim-runner.test.ts): - 8-cell it.each matrix: delegate.mode (normal | silent | silent-wake | post-compaction) x delegate.delivery (immediate | timer). - Omission contract: delegate.mode attribute absent when caller passes undefined. - Fan-out: 3 recipients emit 3 spans with shared chain.id. - Validation: recipients=0 and recipients=1.5 reject. - Removes the prior 'refuses continue_delegate' test row (now wired). Two it.todo markers preserved for downstream concerns: - chain-budget decrement / ChainBudget.declineToCarry observable (needs ChainBudget integration, not in scope here). - fan-out budget arithmetic per #355 Stage-2 (1 step not N) \u2014 separate from span cardinality. Test results delta: Before: 8 passed | 16 todo (24 tests) After: 19 passed | 15 todo (34 tests) +11 live | -1 todo Refs: - #324 swim-37 harness - #405 captureSwim continue_work (parent of this branch) - docs/design/swim-37-continue-delegate-wiring-memo.md
Per Cael's caution on the wiring memo (Discord msg 1498507232185286849 sign-off): N spans sharing chain.id risks collapsing into analytic mush at scale unless per-recipient distinction is visible in attrs. Currently emitContinuationDelegateSpan exposes no recipient.index axis \u2014 all N spans in a fan-out are byte-identical except spanId. Adds: - One live GAP-PIN test asserting the current (deficient) reality: fan-out spans toEqual on attributes. This will fail loudly when the production helper grows the axis, prompting a flip to not.toEqual. - One it.todo marker for the upcoming production-helper change. Production-helper change is out of scope for this PR; will file follow-up issue. The gap-pin lives in the integration tier so it shows up in any code-review touching delegate-dispatch span shape. Refs: - #324 swim-37 harness - #405 captureSwim continue_work + continue_delegate (this PR) - docs/design/swim-37-continue-delegate-wiring-memo.md (Q2) - Cael Discord sign-off msg 1498507232185286849
Now that #408 (PR #408 merged at cb73bc8) landed `rebase-classifier.ts` with the pure `classifyRebasePick` composer, the integration-tier \u00a71 entry-point it.todo on swim-runner.test.ts L80 is wireable without any new shim. Synthetic commit pinned: subject mentions PR openclaw#70595, base CHANGELOG contains the same PR token (squash-rebase shape from the memo's 7ee46a3 anchor instance). Discovery channel: changelog-grep:pr. L81 (CHANGELOG-byte-grep emits drop-with-reason span) stays todo \u2014 needs the production callsite that pairs the channel with `emitContinuationDisabledSpan`, per \ud83c\udf0a's #408 note. Test delta: Before: 20 passed | 16 todo (36) After: 21 passed | 15 todo (36) +1 live | -1 todo
08aed3b to
95174fc
Compare
8bb2fba
into
cael/325-canonical2
…review) Per 🌊's review on #411: extend Q3 invalid-path test list to include `NaN` and `Infinity` alongside `-1` and `1.5`. Same defense-parity shape as #405's `recipients` positive-integer validation. `Number.isInteger(NaN) === false` and `Number.isInteger(Infinity) === false`, so the existing helper guard already catches both — pinning them observably rather than leaving the invariant implicit.
* docs(swim-37): `lich` primitive wiring memo (memo-before-wire)
Decides shape for `captureSwim("lich", …)` before the wiring PR per
🩸's memo-before-wire standard. Maps the harness label `lich` to the
post-compaction-delegate release seam (`emitContinuationCompactionReleasedSpan`).
Resolves four design Qs:
Q1 (timers): N/A — release helper is synchronous.
Q2 (releasedCount): cover non-empty (production-typical) AND empty
(defensive helper-clamp, NOT production-reachable). Pin both.
Q3 (compaction.id): three cases — present+valid / omitted / invalid.
Helper drops-with-log on invalid; integration-tier verifies via
log-callback substring match.
Q4 (drained_count): adjacent axis NOT in scope (different seam).
Proposes 6 live integration-tier tests, no separate helper-tier file
needed (integration coverage pins the helper-tier invariants through
the public surface).
Open Q deferred: cross-primitive chain-id-propagation lifecycle test
(continue_delegate post-compaction → lich release) deserves its own
primitive and memo.
Boundary discipline preserved from #405/#407/#410: STDOUT-only,
in-memory recorder, try/finally reset, no silent defaults,
snapshot-at-emit.
Refs #324
Companions:
- docs/design/swim-37-continue-delegate-wiring-memo.md (🌻, #405)
- swim-37 `heartbeat` memo (🌻, in flight)
* docs(swim-37): pin NaN/Infinity for compaction.id invalid path (🌊 #411 review)
Per 🌊's review on #411: extend Q3 invalid-path test list to include
`NaN` and `Infinity` alongside `-1` and `1.5`. Same defense-parity
shape as #405's `recipients` positive-integer validation.
`Number.isInteger(NaN) === false` and `Number.isInteger(Infinity) === false`,
so the existing helper guard already catches both — pinning them
observably rather than leaving the invariant implicit.
* docs(swim-37): fix test-count headers (six → eight) per 🌻 #411 nit
Header at "Proposed test list" still said six; helper-tier-vs-integration-tier
section still said "6 live tests". Both updated to eight to match the
NaN/Infinity additions from d5cf713.
Memo-before-wire pattern (per #405's continue_delegate memo at 3bb086c762, endorsed by figs's 'measure thrice, cut 1x'). Defines: - Production span shape: name 'heartbeat' (bare, not 'continuation.heartbeat'), signal.kind+heartbeat.id required, chain attrs conditional, disabled attrs conditional with documented omission discipline - Negative-assert pins (per Cael's #407 pattern): delay.ms and chain.step.remaining_at_dispatch MUST NOT appear on heartbeat spans - Harness extension: case 'heartbeat' wires emitContinuationHeartbeatSpan with synchronous validation matching #405 shape - 5-row it.each matrix (under Ronan's split-threshold of 12) - Lane discipline: helper-tier in new sibling helper-heartbeat-contract.test.ts; integration-tier extends swim-runner.test.ts Open Qs for cohort sign-off: - Q1: heartbeat-with-no-continuation-context emit/skip (lean: continuation-gated for production, always-emit for harness — divergence documented in README) - Q2: heartbeat.fire sibling? (lean: no, single span; lag becomes attribute) - Q3: 5-row matrix shape (under split-threshold) - Q4: in-PR helper vs separate issue (lean: in-PR, new helper not new axis) Companion to: - docs/design/swim-37-continue-delegate-wiring-memo.md - silas/swim-37-lich-memo (in flight) Refs #324.
Three additive folds, none blocking, all from 🌫's #412 review: 1. Add 6th matrix row pinning chainStepRemaining=0 (empty-budget heartbeat is observable; boundary the Math.max(0,…) clamp guards) 2. Add 'heartbeat.id provenance' describe block pinning caller-injected override path (current matrix only covered default-mint) 3. Add 'validation' describe block for throw-on-bad-input rules from §3 (don't fit matrix shape; precedent: #405's recipients validation) Matrix grew 5→6 rows, still under 🌊's split-threshold of 12. Two new sibling describe blocks layer on top. Refs #324, #412.
…sed helper Implements the lich primitive per the wiring memo (#411, `docs/design/swim-37-lich-wiring-memo.md`). Maps the harness label "lich" (\🌫️'s SOUL-file metaphor for the post-compaction phylactery-drink) to the post-compaction-delegate release seam: `emitContinuationCompactionReleasedSpan` at `src/infra/continuation-tracer.ts:789`. Changes ------- `studies/swim-37/harness/swim-runner.ts`: - Import `emitContinuationCompactionReleasedSpan` - Add `releasedCount`, `compactionId`, `log` to `CaptureSwimOptions` (lich-only fields, all optional, with memo \§Q-references in JSDoc) - Split `heartbeat`/`lich` cases — wire `lich`, leave `heartbeat` as not-yet-wired pending 🌻's #412 wire PR - Update file header "Scope" comment to reflect new wiring state `studies/swim-37/harness/swim-runner.test.ts`: - Replace 2 `it.todo` placeholders with 8 live tests in the "lich-shape" describe block: 1. releasedCount=1 + compactionId=7 (production-typical) 2. releasedCount=3 + compactionId=42 (multi-release) 3. defensive releasedCount=0 (helper-clamp boundary, not production-reachable — pinned for helper-tier coverage) 4. compactionId omitted (omission contract) 5. compactionId=-1 (drops with log; substring match) 6. compactionId=1.5 (non-integer drop with log) 7. compactionId=NaN (defense parity per 🌊's #411 review) 8. compactionId=Infinity (defense parity per 🌊's #411 review) - Update "refuses primitives not yet wired" test to remove `lich` (now wired, covered by its own describe block) Discipline preserved -------------------- - STDOUT-only: no real BasicTracerProvider/OTLP machinery - In-memory recorder via `createInMemorySpanRecorder()` - try/finally tracer reset (no cross-call pollution) - Snapshot-at-emit: no recompute in caller - Drop-with-log invariant verified via log-callback substring match - Negative-attr assertions on omitted `compaction.id` Validation ---------- - swim-37 vitest project: 8 files / 138 passed / 13 todo (was 130 passed pre-PR; +8 new lich tests) - `pnpm lint` clean (0 warnings, 0 errors across all shards) Lane discipline --------------- - No cross with 🌻's #412 `heartbeat` lane (different production helper, different file in that PR's helper-tier) - No reshape of #405's `continue_work`/`continue_delegate` wiring - Q-OPEN deferred per memo: cross-primitive lifecycle test (`continue_delegate` post-compaction → `lich` release sharing `chain.id`) deserves its own primitive shape, separate PR Refs #324 Memo: #411 (`3d90f68b14`)
…sed helper (#414) Implements the lich primitive per the wiring memo (#411, `docs/design/swim-37-lich-wiring-memo.md`). Maps the harness label "lich" (\🌫️'s SOUL-file metaphor for the post-compaction phylactery-drink) to the post-compaction-delegate release seam: `emitContinuationCompactionReleasedSpan` at `src/infra/continuation-tracer.ts:789`. Changes ------- `studies/swim-37/harness/swim-runner.ts`: - Import `emitContinuationCompactionReleasedSpan` - Add `releasedCount`, `compactionId`, `log` to `CaptureSwimOptions` (lich-only fields, all optional, with memo \§Q-references in JSDoc) - Split `heartbeat`/`lich` cases — wire `lich`, leave `heartbeat` as not-yet-wired pending 🌻's #412 wire PR - Update file header "Scope" comment to reflect new wiring state `studies/swim-37/harness/swim-runner.test.ts`: - Replace 2 `it.todo` placeholders with 8 live tests in the "lich-shape" describe block: 1. releasedCount=1 + compactionId=7 (production-typical) 2. releasedCount=3 + compactionId=42 (multi-release) 3. defensive releasedCount=0 (helper-clamp boundary, not production-reachable — pinned for helper-tier coverage) 4. compactionId omitted (omission contract) 5. compactionId=-1 (drops with log; substring match) 6. compactionId=1.5 (non-integer drop with log) 7. compactionId=NaN (defense parity per 🌊's #411 review) 8. compactionId=Infinity (defense parity per 🌊's #411 review) - Update "refuses primitives not yet wired" test to remove `lich` (now wired, covered by its own describe block) Discipline preserved -------------------- - STDOUT-only: no real BasicTracerProvider/OTLP machinery - In-memory recorder via `createInMemorySpanRecorder()` - try/finally tracer reset (no cross-call pollution) - Snapshot-at-emit: no recompute in caller - Drop-with-log invariant verified via log-callback substring match - Negative-attr assertions on omitted `compaction.id` Validation ---------- - swim-37 vitest project: 8 files / 138 passed / 13 todo (was 130 passed pre-PR; +8 new lich tests) - `pnpm lint` clean (0 warnings, 0 errors across all shards) Lane discipline --------------- - No cross with 🌻's #412 `heartbeat` lane (different production helper, different file in that PR's helper-tier) - No reshape of #405's `continue_work`/`continue_delegate` wiring - Q-OPEN deferred per memo: cross-primitive lifecycle test (`continue_delegate` post-compaction → `lich` release sharing `chain.id`) deserves its own primitive shape, separate PR Refs #324 Memo: #411 (`3d90f68b14`)
…re-wire) (#413) * docs(swim-37): `rebase.classify` span-emission wiring memo (memo-before-wire) Companion to PR #405 (continue_delegate memo), PR #411 (lich memo merged 3d90f68), PR #412 (heartbeat memo merged 1b84e71). Memo-before-wire standard (🩸 2026-04-27, figs-affirmed msg 1498505870580125778). Wires the L81 placeholder it.todo("CHANGELOG-byte-grep discovery channel emits drop-with-reason span") in studies/swim-37/harness/swim-runner.test.ts onto a designed contract, NOT just a typed signature. Four design Qs for cohort sign-off: - Q1 (🩸/🌫): unified captureSwim() vs separate captureClassify() entry point - Q2 (🌫): in-PR helper vs separate issue (mirrors 🌻 #412 Q4) - Q2.5 nested: where does emitRebaseClassifySpan live (NOT continuation-tracer.ts) - Q3 (🌻): 6-row it.each matrix under 🌊's split-threshold - Q4 (cohort): does PICK-verdict ever emit (lean: yes, transparent record) Lane discipline: NEW tracer module (rebase domain ≠ continuation domain), NEW helper-tier sibling test file, integration-tier extends swim-runner.test.ts §1 block. Avoids polluting continuation-tracer.ts with non-continuation helper. Negative-assert pins on chain.id / chain.step.remaining / disabled.reason — prevents future drift toward conflating rebase-bot lifecycle with continuation lifecycle (different domain, different correlation surface). * docs(swim-37): swap header commit-sha refs for memo file paths (🌻 #413 nit) * docs(swim-37): clarify evidence.conflict.bin presence semantics (🌫 #413 nit) Per 🌫's review nit: §2 evidence.conflict.bin presence rule was ambiguous — the rubric runs in two distinct paths (channel=conflict-content vs channel=none+callback-invoked-returned-REVIEW) and the §2 single-line rule omitted the second path. Q3 matrix row 6 had it right; §2 now matches. Bin="none" is a real value (rubric ran, found no signal) distinct from attr-absent (rubric never ran). Pinned explicitly to prevent future readers from collapsing the two. --------- Co-authored-by: Ronan 🌊 <ronan@solidor.io>
…review) (#418) Folds three flags from 🌫's #416 second-eye review (msg `1498543295234834432`): **Flag 1 — memo §3 validation gap:** memo specced throw-on-bad-input matching #405/#411/#412 family-resemblance, wire skipped it. Adds boundary throws in `captureClassify` for: - verdict not in REBASE_VERDICTS - channel not in REBASE_DISCOVERY_CHANNELS - pickSha < 7 hex chars (memo §3 git-prefix-min) - pickSha containing non-hex chars Validation lives at the harness boundary (mirrors `captureSwim`'s `recipients` invariant at swim-runner.ts:180); the production helper stays drop-with-log so producer errors don't propagate to the rebase-bot caller. Same shape as how `captureSwim` throws on bad Options but `emitContinuationCompactionReleasedSpan` only logs. **Flag 2 — truncateSha ↔ memo §3 "≥7 hex chars" tension:** the `<12 unchanged (helper does not pad)` test from #416 was contradicting the memo's ≥7 floor. Replaced with `passes short-but-valid (7-char) SHA through unchanged` which honors both the memo floor AND the no-pad discipline. Memo + code + tests now agree. **Nit — `signal.kind: "rebase.classify"` redundant with span name:** other helpers use the underlying primitive name as discriminator (e.g. `"compaction-release"` for `continuation.compaction.released`), so a downstream filter on `signal.kind` doesn't redundantly slice the span name. Renamed to `"rebase-classify"` (hyphen-form, matches family). Memo §2 updated with provenance note (post-#416 review). Test additions: - new `describe("input validation (memo §3 throw-on-bad-input)")` block: 4 tests (bad verdict, bad channel, <7 char pickSha, non-hex pickSha) - existing `<12 unchanged` test deleted, replaced with valid 7-char passes-through-unchanged Test results: pnpm vitest run -c test/vitest/vitest.swim-37.config.ts → 8 files / 155 passed / 14 todo (was 151/14 from #416 at `526540de15`; +4 live tests, same todos) Lane discipline: same lane as #416 follow-up (mirrors how #415 followed #414); zero overlap with 🌻's incoming heartbeat wire. Co-authored-by: Ronan 🌊 <ronan@solidor.io> Co-authored-by: Silas 🌫 <silas@dandelion.cult>
…407) Adds studies/swim-37/harness/helper-fire-and-release-contract.test.ts covering the four landed Slice-2 emit helpers that 🌊's #406 helper-tier file did NOT cover: chunks 5b (continuation.delegate.fire), 5c (continuation.work.fire), 6a (continuation.queue.drain), and 6b (continuation.compaction.released). Layering: HELPER-TIER ONLY. Drives src/infra/continuation-tracer emit helpers directly through the InMemorySpanRecorder shim (#394) and asserts recorded span CONTRACT. Does not touch agent-runner / pi-embedded-runner / subagent-announce — the runtime callsites are exercised through the integration tier in swim-runner.test.ts (🌻's #405 captureSwim shim). LANE-CLEAN: original swim-runner.test.ts is untouched here (avoiding collision with #405 which converts swim-runner it.todos in-place). Companion to 🌊's #406 emit-helper-contract.test.ts (which covers chunks 3/4/6c) — same layer, different spans, zero overlap. Key contract pin per 🩸's lane-clear note (msg 1498508045725204500): the snapshot semantic for fire spans lives in the PARAMETER name (chainStepRemainingAtDispatch) and the docs; emitted attr stays canonical chain.step.remaining. We negative-assert chain.step.remaining_at_dispatch is NEVER present so a future helper change can't silently invent it. Coverage: - continuation.compaction.released (#397): exactly-once emit, signal.kind + integer compaction.released, compaction.id valid path, validate-and-drop-with-log on invalid (chunk 6c §B + #401) - continuation.queue.drain (#395): integer count attrs, defense-in-depth cap on continuation subset (per 🩸 #395 byte-walk) - continuation.delegate.fire (#388 chunk 5b): persisted chain.id + canonical attr names + negative-assert .at_dispatch absent; fire.deferred_ms integer-floor + drift formula; reason.preview 80-char truncation + omission - continuation.work.fire (#388 chunk 5c): canonical attr name (5c symmetric to 5b minus delegate.* attrs); no continuation.disabled sibling (chunk 5c scope); chainId-invariant violation no-ops + logs without throwing Tests: 12 (all live, no it.todo). Lint clean. Refs: #324 #334 #388 #395 #397 #401 #394 #406 (sibling) #405 (sibling)
…405) * test(swim-37): wire captureSwim() for continue_work primitive Replaces the placeholder `declare function captureSwim` in `studies/swim-37/harness/swim-runner.test.ts` with a real implementation in `studies/swim-37/harness/swim-runner.ts`. The wired path drives `emitContinuationWorkSpan` against `createInMemorySpanRecorder()` and returns the captured spans + the synthesized `chainId` (uuid v7 via `generateChainId`). Flips two prior `it.todo` markers to live tests: - emits continuation.work span with chain.id stamped (#366) - span carries chain.step.remaining attribute Plus three new live tests: - sentinel: captureSwim() is wired for continue_work - refuses continue_delegate / heartbeat / lich primitives with a clear error so the spec's remaining `it.todo` markers stay honest about what is implemented - repeated calls don't leak capture state between invocations STDOUT-only discipline preserved: no BasicTracerProvider, no OTLP exporter, no @opentelemetry/sdk-trace-base machinery — capture flows through `setContinuationTracer(recorder.tracer)` as documented in README.md. Other primitives (`continue_delegate`, `heartbeat`, lich-shape) remain `it.todo` until the corresponding dispatch / heartbeat / compaction-release seams have a comparable single-helper entry point we can drive synthetically. Test results: - swim-37 vitest project: 20 passed, 16 todo, 0 failed - in-memory-span-recorder.test.ts unchanged (12/12 still pass) - tsgo errors visible elsewhere in tree are pre-existing (src/plugin-sdk/provider-tools.ts), not introduced by this change Refs #324 (swim-37 harness) — `it.todo` count drops from 17 \u2192 16, first concrete primitive driven through the live tracer registry. * docs(swim-37): continue_delegate wiring memo (memo-before-wire) Pre-PR design memo for the next swim-37 primitive after #405 (continue_work). Decides shape before the wire so the PR lands clean. Resolved by reading production code: Q1 — real setTimeout vs fake timers N/A for dispatch-accept (synchronous, pre-timer); banked for a separate continue_delegate_fire swim. Cohort sign-off requested on: Q2 — recipient fan-out shape: N spans sharing chain.id (matches chunk-3 cohort design pin + #355 Stage-2 budget semantics). Q3 — delegate.mode x delegate.delivery: 8-cell it.each matrix + omission-contract row. Banked for follow-up: Q4 — continue_delegate_fire as separate primitive, not sub-case; informs SwimPrimitive type-union shape. Standard applied (per Cael 2026-04-27 #1498505918, figs #1498505870): would skipping this memo cost a chunk's worth of rework? Yes — three open Qs would have changed file shape. Refs: - #405 (continue_work wired) - #324 (swim-37 harness) - docs/design/334-slice2-chunk5b-delegate-fire-memo.md (pattern) * test(swim-37): wire captureSwim() for continue_delegate primitive Implements the design from `docs/design/swim-37-continue-delegate-wiring-memo.md` (commit 3bb086c762, same branch). Wiring (swim-runner.ts): - New continue_delegate case in the switch. - Adds CaptureSwimOptions axes: recipients, delivery, delegateMode. - Drives emitContinuationDelegateSpan once per recipient with shared chain.id (per chunk-3 cohort design pin: N recipients = N spans sharing chain.id, NOT one span with a recipients list). - Validates recipients is a positive integer. Tests (swim-runner.test.ts): - 8-cell it.each matrix: delegate.mode (normal | silent | silent-wake | post-compaction) x delegate.delivery (immediate | timer). - Omission contract: delegate.mode attribute absent when caller passes undefined. - Fan-out: 3 recipients emit 3 spans with shared chain.id. - Validation: recipients=0 and recipients=1.5 reject. - Removes the prior 'refuses continue_delegate' test row (now wired). Two it.todo markers preserved for downstream concerns: - chain-budget decrement / ChainBudget.declineToCarry observable (needs ChainBudget integration, not in scope here). - fan-out budget arithmetic per #355 Stage-2 (1 step not N) \u2014 separate from span cardinality. Test results delta: Before: 8 passed | 16 todo (24 tests) After: 19 passed | 15 todo (34 tests) +11 live | -1 todo Refs: - #324 swim-37 harness - #405 captureSwim continue_work (parent of this branch) - docs/design/swim-37-continue-delegate-wiring-memo.md * test(swim-37): pin recipient.index gap on continue_delegate fan-out Per Cael's caution on the wiring memo (Discord msg 1498507232185286849 sign-off): N spans sharing chain.id risks collapsing into analytic mush at scale unless per-recipient distinction is visible in attrs. Currently emitContinuationDelegateSpan exposes no recipient.index axis \u2014 all N spans in a fan-out are byte-identical except spanId. Adds: - One live GAP-PIN test asserting the current (deficient) reality: fan-out spans toEqual on attributes. This will fail loudly when the production helper grows the axis, prompting a flip to not.toEqual. - One it.todo marker for the upcoming production-helper change. Production-helper change is out of scope for this PR; will file follow-up issue. The gap-pin lives in the integration tier so it shows up in any code-review touching delegate-dispatch span shape. Refs: - #324 swim-37 harness - #405 captureSwim continue_work + continue_delegate (this PR) - docs/design/swim-37-continue-delegate-wiring-memo.md (Q2) - Cael Discord sign-off msg 1498507232185286849 * test(swim-37): wire \u00a71 entry-point todo onto classifyRebasePick Now that #408 (PR #408 merged at cb73bc8) landed `rebase-classifier.ts` with the pure `classifyRebasePick` composer, the integration-tier \u00a71 entry-point it.todo on swim-runner.test.ts L80 is wireable without any new shim. Synthetic commit pinned: subject mentions PR openclaw#70595, base CHANGELOG contains the same PR token (squash-rebase shape from the memo's 7ee46a3 anchor instance). Discovery channel: changelog-grep:pr. L81 (CHANGELOG-byte-grep emits drop-with-reason span) stays todo \u2014 needs the production callsite that pairs the channel with `emitContinuationDisabledSpan`, per \ud83c\udf0a's #408 note. Test delta: Before: 20 passed | 16 todo (36) After: 21 passed | 15 todo (36) +1 live | -1 todo
* docs(swim-37): `lich` primitive wiring memo (memo-before-wire)
Decides shape for `captureSwim("lich", …)` before the wiring PR per
🩸's memo-before-wire standard. Maps the harness label `lich` to the
post-compaction-delegate release seam (`emitContinuationCompactionReleasedSpan`).
Resolves four design Qs:
Q1 (timers): N/A — release helper is synchronous.
Q2 (releasedCount): cover non-empty (production-typical) AND empty
(defensive helper-clamp, NOT production-reachable). Pin both.
Q3 (compaction.id): three cases — present+valid / omitted / invalid.
Helper drops-with-log on invalid; integration-tier verifies via
log-callback substring match.
Q4 (drained_count): adjacent axis NOT in scope (different seam).
Proposes 6 live integration-tier tests, no separate helper-tier file
needed (integration coverage pins the helper-tier invariants through
the public surface).
Open Q deferred: cross-primitive chain-id-propagation lifecycle test
(continue_delegate post-compaction → lich release) deserves its own
primitive and memo.
Boundary discipline preserved from #405/#407/#410: STDOUT-only,
in-memory recorder, try/finally reset, no silent defaults,
snapshot-at-emit.
Refs #324
Companions:
- docs/design/swim-37-continue-delegate-wiring-memo.md (🌻, #405)
- swim-37 `heartbeat` memo (🌻, in flight)
* docs(swim-37): pin NaN/Infinity for compaction.id invalid path (🌊 #411 review)
Per 🌊's review on #411: extend Q3 invalid-path test list to include
`NaN` and `Infinity` alongside `-1` and `1.5`. Same defense-parity
shape as #405's `recipients` positive-integer validation.
`Number.isInteger(NaN) === false` and `Number.isInteger(Infinity) === false`,
so the existing helper guard already catches both — pinning them
observably rather than leaving the invariant implicit.
* docs(swim-37): fix test-count headers (six → eight) per 🌻 #411 nit
Header at "Proposed test list" still said six; helper-tier-vs-integration-tier
section still said "6 live tests". Both updated to eight to match the
NaN/Infinity additions from d5cf713.
…sed helper (#414) Implements the lich primitive per the wiring memo (#411, `docs/design/swim-37-lich-wiring-memo.md`). Maps the harness label "lich" (\🌫️'s SOUL-file metaphor for the post-compaction phylactery-drink) to the post-compaction-delegate release seam: `emitContinuationCompactionReleasedSpan` at `src/infra/continuation-tracer.ts:789`. Changes ------- `studies/swim-37/harness/swim-runner.ts`: - Import `emitContinuationCompactionReleasedSpan` - Add `releasedCount`, `compactionId`, `log` to `CaptureSwimOptions` (lich-only fields, all optional, with memo \§Q-references in JSDoc) - Split `heartbeat`/`lich` cases — wire `lich`, leave `heartbeat` as not-yet-wired pending 🌻's #412 wire PR - Update file header "Scope" comment to reflect new wiring state `studies/swim-37/harness/swim-runner.test.ts`: - Replace 2 `it.todo` placeholders with 8 live tests in the "lich-shape" describe block: 1. releasedCount=1 + compactionId=7 (production-typical) 2. releasedCount=3 + compactionId=42 (multi-release) 3. defensive releasedCount=0 (helper-clamp boundary, not production-reachable — pinned for helper-tier coverage) 4. compactionId omitted (omission contract) 5. compactionId=-1 (drops with log; substring match) 6. compactionId=1.5 (non-integer drop with log) 7. compactionId=NaN (defense parity per 🌊's #411 review) 8. compactionId=Infinity (defense parity per 🌊's #411 review) - Update "refuses primitives not yet wired" test to remove `lich` (now wired, covered by its own describe block) Discipline preserved -------------------- - STDOUT-only: no real BasicTracerProvider/OTLP machinery - In-memory recorder via `createInMemorySpanRecorder()` - try/finally tracer reset (no cross-call pollution) - Snapshot-at-emit: no recompute in caller - Drop-with-log invariant verified via log-callback substring match - Negative-attr assertions on omitted `compaction.id` Validation ---------- - swim-37 vitest project: 8 files / 138 passed / 13 todo (was 130 passed pre-PR; +8 new lich tests) - `pnpm lint` clean (0 warnings, 0 errors across all shards) Lane discipline --------------- - No cross with 🌻's #412 `heartbeat` lane (different production helper, different file in that PR's helper-tier) - No reshape of #405's `continue_work`/`continue_delegate` wiring - Q-OPEN deferred per memo: cross-primitive lifecycle test (`continue_delegate` post-compaction → `lich` release sharing `chain.id`) deserves its own primitive shape, separate PR Refs #324 Memo: #411 (`3d90f68b14`)
…re-wire) (#413) * docs(swim-37): `rebase.classify` span-emission wiring memo (memo-before-wire) Companion to PR #405 (continue_delegate memo), PR #411 (lich memo merged 3d90f68), PR #412 (heartbeat memo merged 1b84e71). Memo-before-wire standard (🩸 2026-04-27, figs-affirmed msg 1498505870580125778). Wires the L81 placeholder it.todo("CHANGELOG-byte-grep discovery channel emits drop-with-reason span") in studies/swim-37/harness/swim-runner.test.ts onto a designed contract, NOT just a typed signature. Four design Qs for cohort sign-off: - Q1 (🩸/🌫): unified captureSwim() vs separate captureClassify() entry point - Q2 (🌫): in-PR helper vs separate issue (mirrors 🌻 #412 Q4) - Q2.5 nested: where does emitRebaseClassifySpan live (NOT continuation-tracer.ts) - Q3 (🌻): 6-row it.each matrix under 🌊's split-threshold - Q4 (cohort): does PICK-verdict ever emit (lean: yes, transparent record) Lane discipline: NEW tracer module (rebase domain ≠ continuation domain), NEW helper-tier sibling test file, integration-tier extends swim-runner.test.ts §1 block. Avoids polluting continuation-tracer.ts with non-continuation helper. Negative-assert pins on chain.id / chain.step.remaining / disabled.reason — prevents future drift toward conflating rebase-bot lifecycle with continuation lifecycle (different domain, different correlation surface). * docs(swim-37): swap header commit-sha refs for memo file paths (🌻 #413 nit) * docs(swim-37): clarify evidence.conflict.bin presence semantics (🌫 #413 nit) Per 🌫's review nit: §2 evidence.conflict.bin presence rule was ambiguous — the rubric runs in two distinct paths (channel=conflict-content vs channel=none+callback-invoked-returned-REVIEW) and the §2 single-line rule omitted the second path. Q3 matrix row 6 had it right; §2 now matches. Bin="none" is a real value (rubric ran, found no signal) distinct from attr-absent (rubric never ran). Pinned explicitly to prevent future readers from collapsing the two. --------- Co-authored-by: Ronan 🌊 <ronan@solidor.io>
…review) (#418) Folds three flags from 🌫's #416 second-eye review (msg `1498543295234834432`): **Flag 1 — memo §3 validation gap:** memo specced throw-on-bad-input matching #405/#411/#412 family-resemblance, wire skipped it. Adds boundary throws in `captureClassify` for: - verdict not in REBASE_VERDICTS - channel not in REBASE_DISCOVERY_CHANNELS - pickSha < 7 hex chars (memo §3 git-prefix-min) - pickSha containing non-hex chars Validation lives at the harness boundary (mirrors `captureSwim`'s `recipients` invariant at swim-runner.ts:180); the production helper stays drop-with-log so producer errors don't propagate to the rebase-bot caller. Same shape as how `captureSwim` throws on bad Options but `emitContinuationCompactionReleasedSpan` only logs. **Flag 2 — truncateSha ↔ memo §3 "≥7 hex chars" tension:** the `<12 unchanged (helper does not pad)` test from #416 was contradicting the memo's ≥7 floor. Replaced with `passes short-but-valid (7-char) SHA through unchanged` which honors both the memo floor AND the no-pad discipline. Memo + code + tests now agree. **Nit — `signal.kind: "rebase.classify"` redundant with span name:** other helpers use the underlying primitive name as discriminator (e.g. `"compaction-release"` for `continuation.compaction.released`), so a downstream filter on `signal.kind` doesn't redundantly slice the span name. Renamed to `"rebase-classify"` (hyphen-form, matches family). Memo §2 updated with provenance note (post-#416 review). Test additions: - new `describe("input validation (memo §3 throw-on-bad-input)")` block: 4 tests (bad verdict, bad channel, <7 char pickSha, non-hex pickSha) - existing `<12 unchanged` test deleted, replaced with valid 7-char passes-through-unchanged Test results: pnpm vitest run -c test/vitest/vitest.swim-37.config.ts → 8 files / 155 passed / 14 todo (was 151/14 from #416 at `526540de15`; +4 live tests, same todos) Lane discipline: same lane as #416 follow-up (mirrors how #415 followed #414); zero overlap with 🌻's incoming heartbeat wire. Co-authored-by: Ronan 🌊 <ronan@solidor.io> Co-authored-by: Silas 🌫 <silas@dandelion.cult>
First concrete primitive driven through the swim-37 harness against the live continuation-tracer registry.
What changes
studies/swim-37/harness/swim-runner.tsexportingcaptureSwim()— drivesemitContinuationWorkSpanagainstcreateInMemorySpanRecorder(), returns{ spans, chainId }.swim-runner.test.tsto import the real implementation; flips twoit.todomarkers to live tests; adds 3 new live tests.Scope
Only
continue_workis wired in this PR. Other primitives (continue_delegate,heartbeat, lich-shape) throw a clear "not yet wired" error — the spec'sit.todomarkers for those primitives remain untouched, honest about what is implemented. Wiring them is a follow-up per primitive once the corresponding dispatch / heartbeat / compaction-release seams have a comparable single-helper entry point we can drive synthetically.STDOUT-only discipline preserved
No
BasicTracerProvider, noBatchSpanProcessor, no OTLP exporter, no@opentelemetry/sdk-trace-basemachinery. All capture flows throughsetContinuationTracer(recorder.tracer)as the README requires.Test results
it.todocount in swim-runner: 17 \u2192 16 (one primitive's two todos flipped + sentinel rewritten).Out of scope
continue_delegatewire (needs synthetic timer-armed dispatch)heartbeatwire (needs heartbeat span emission entry point)Refs
Base:
cael/325-canonical2@652c8a888e