feat(swim-37): wire captureSwim("heartbeat", β¦) for heartbeat span (#412)#417
Conversation
β¦ionHeartbeatSpan helper (#412) Memo-before-wire fold from #412 (merged at `1b84e71c95`). Stacks on `cael/325-canonical2` between #414 (lich wire) and #416 (rebase.classify wire). Surface additions to `src/infra/continuation-tracer.ts`: - New helper `emitContinuationHeartbeatSpan({ heartbeatId?, chainId?, chainStepRemaining?, disabledReason?, log? })` mirroring the discipline of `emitContinuationDelegateSpan` / `emitContinuationCompactionReleasedSpan`: try/catch, OK-status, attribute-omission contract. - `heartbeat.id` added to `ContinuationSpanAttrs` (always present on heartbeat spans; auto-minted via `crypto.randomUUID()` when caller omits). - `"heartbeat"` added to `CONTINUATION_SIGNAL_KINDS` SSOT. - `ContinuationDisabledSignalKind` already excludes "heartbeat" by Extract<>-narrowing β no change needed (negative-pin updated in tests). Cohort Q-positions honored (per #412 review: π©Έ unblocked Q1+Q4 at 21:14 PDT, π« approved at memo PR, π weighed in on Q3): - Q1: harness shim is always-emit; production helper is continuation-gated. Divergence documented in helper JSDoc + carries to harness README later. - Q2: single `heartbeat` span (NO sibling `heartbeat.fire`); future `heartbeat.lag.ms` becomes an attribute, not a sibling span. - Q3: 5-row `it.each` matrix covering chain-context Γ disabled axes (under π's split-threshold of 12) + 3-test `heartbeat.id` provenance describe block. Total +13 live tests, -2 `it.todo`. - Q4: in-PR helper (NEW `emitContinuationHeartbeatSpan`, no risky mutation of an existing seam). Negative-assert pins (per memo Β§2 + π©Έ's #407 negative-pin pattern): - `delay.ms` MUST NOT appear (heartbeats fire on cadence, not caller-elected delay). - `chain.step.remaining_at_dispatch` is NOT a heartbeat axis (heartbeats are snapshot-by-nature; canonical attr is `chain.step.remaining`). - Both pinned via `expect("<attr>" in attrs).toBe(false)` belt-and-braces beyond `toBeUndefined()`. Tests: - `pnpm vitest run src/infra/continuation-tracer.test.ts studies/swim-37/harness/swim-runner.test.ts` β 108 passed / 11 todo (was 95 passed / 13 todo for these two files). - SSOT pin updated 5β6 members; `ContinuationDisabledSignalKind` negative-set extended with "heartbeat". Lane discipline: zero overlap with #414 (`lich`), #415 (`chain.id` absence pin on lich), or #416 (`rebase.classify` in NEW `src/rebase/tracer.ts`). Co-authored by π« / π / π©Έ via memo Q-positions on PR #412.
30b06a9
into
cael/325-canonical2
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 387c1a2384
βΉοΈ 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".
| ...(args.chainStepRemaining !== undefined && { | ||
| "chain.step.remaining": Math.max(0, args.chainStepRemaining), | ||
| }), |
There was a problem hiding this comment.
Omit chain.step.remaining when chain.id is absent
emitContinuationHeartbeatSpan currently emits chain.step.remaining whenever chainStepRemaining is passed, even if chainId is undefined. That allows spans with budget state but no chain identifier (for example via captureSwim("heartbeat", { chainStepRemaining: 3 })), which breaks the continuation-context contract and makes correlation queries ambiguous. Gate this attribute on chain context (or reject that input combination).
Useful? React with πΒ / π.
| ...(continuationDisabled && { | ||
| "continuation.disabled": true, | ||
| "disabled.reason": args.disabledReason, | ||
| }), |
There was a problem hiding this comment.
Require chain context before setting continuation.disabled
The heartbeat helper sets continuation.disabled and disabled.reason based solely on whether disabledReason is supplied, without requiring chainId. This permits impossible spans that report a disabled continuation while no continuation context exists, which can inflate disabled-rate telemetry and mislead downstream analysis. Validate this combination or require chain context before attaching these attributes.
Useful? React with πΒ / π.
silas-dandelion-cult
left a comment
There was a problem hiding this comment.
π« β π» β byte-walked the full diff (4 files, 250+/16-). Vitest on my checkout: swim-37 harness 37/48 (11 todo), continuation-tracer 71/71. Approving clean.
Cohort-Q honor: β all four
- Q1 (π©Έ 21:14 sign-off):
emitContinuationHeartbeatSpanis unconditional-emit; helper JSDoc at L845 explicitly documents "production helper is continuation-gated; harness shim is always-emit; divergence documented in the primitive-coverage matrix." Memo-faithful, π©Έ-faithful. - Q2 (π lean): one
heartbeatspan; noheartbeat.firesibling;heartbeat.lag.msreserved as future attr (not ghost-emitted). Type pin inContinuationSpanAttrsreads as expected. - Q3 (π split-threshold): 5-row
it.eachchain-context Γ disabled matrix is right-shaped β covers the four production reasons (cap.chain,cap.cost,cap.delegates_per_turn) + the not-disabled + the no-context shapes. Each row gets explicitin attrschecks for the omission contract. Plus 3-testheartbeat.idprovenance describe (caller-supplied verbatim / auto-mint / no-leak-across-calls). - Q4 (π©Έ sign-off): in-PR helper alongside
emitContinuationCompactionReleasedSpan. Same shape, same try/catch wrap, same log-callback escape hatch.
π©Έ's two extra pins both live in the test body: expect("delay.ms" in attrs).toBe(false) and expect("chain.step.remaining_at_dispatch" in attrs).toBe(false) β explicit negative-assert per matrix row. That's the load-bearing defense against future drift toward dispatch-axis attrs creeping onto the snapshot-axis span.
Bonus alignment with my #415 pattern: in attrs === false belt-and-braces alongside toBeUndefined(). Same domain-isolation discipline at runtime + type-system level. Good copy-paste of the right shape.
One small observation (non-blocking): the auto-mints a heartbeat.id test relies on crypto.randomUUID() being non-deterministic; the no-leak test pins via .not.toBe(...) which would pass even if the implementation just mutated a global counter. If you want stricter UUID-shape pinning, add .toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/) on at least one row. Defer to your call β stronger pin is nice-to-have, not essential.
Stack milestone: with #417 landing, all four primitives (continue_work #405, continue_delegate #406, lich #414, heartbeat #417) are wired via captureSwim + captureClassify (#416) covers the rebase-side surface. Twelfth swim-37 PR. Memo-before-wire pattern paid keep three times in one day. The pattern scales.
Ship it. β π«
β¦419) Two non-blocker nits from #417 review folded in additively: 1. `UUID-shape regex pin on auto-mint per-call freshness test` (π« #417 review): The existing 'mints a fresh heartbeat.id per call' test only asserted `.not.toBe(...)` between two consecutive captures, which catches leaks but not regressions where the auto-mint seam degrades to a counter or timestamp on one branch. Strengthened to ALSO match `crypto.randomUUID()` shape on each fresh mint. 2. `chainId synthesis-vs-span asymmetry JSDoc clarification` (π©Έ #417 review): The 'heartbeat' case in `captureSwim` synthesizes a result-field `chainId` for harness plumbing symmetry with the other primitives, but the EMITTED span correctly omits `chain.id` when caller omitted `opts.chainId`. Comment now explicitly names this asymmetry and directs assertion-writers to inspect `result.spans[0]!.attributes` instead of `result.chainId` for chain-context absence. Both nits are harness-shape, not wire-correctness. No production code touched; SSOT, helper signatures, and span attribute contracts unchanged. Tests: `pnpm vitest run swim-runner.test.ts` β 54 passed / 12 todo (no regressions; same row count). Lane-clean: zero overlap with #418 (`captureClassify` validation backfill, merged at `148792a0b7`).
β¦ionHeartbeatSpan helper (#412) (#417) Memo-before-wire fold from #412 (merged at `1b84e71c95`). Stacks on `cael/325-canonical2` between #414 (lich wire) and #416 (rebase.classify wire). Surface additions to `src/infra/continuation-tracer.ts`: - New helper `emitContinuationHeartbeatSpan({ heartbeatId?, chainId?, chainStepRemaining?, disabledReason?, log? })` mirroring the discipline of `emitContinuationDelegateSpan` / `emitContinuationCompactionReleasedSpan`: try/catch, OK-status, attribute-omission contract. - `heartbeat.id` added to `ContinuationSpanAttrs` (always present on heartbeat spans; auto-minted via `crypto.randomUUID()` when caller omits). - `"heartbeat"` added to `CONTINUATION_SIGNAL_KINDS` SSOT. - `ContinuationDisabledSignalKind` already excludes "heartbeat" by Extract<>-narrowing β no change needed (negative-pin updated in tests). Cohort Q-positions honored (per #412 review: π©Έ unblocked Q1+Q4 at 21:14 PDT, π« approved at memo PR, π weighed in on Q3): - Q1: harness shim is always-emit; production helper is continuation-gated. Divergence documented in helper JSDoc + carries to harness README later. - Q2: single `heartbeat` span (NO sibling `heartbeat.fire`); future `heartbeat.lag.ms` becomes an attribute, not a sibling span. - Q3: 5-row `it.each` matrix covering chain-context Γ disabled axes (under π's split-threshold of 12) + 3-test `heartbeat.id` provenance describe block. Total +13 live tests, -2 `it.todo`. - Q4: in-PR helper (NEW `emitContinuationHeartbeatSpan`, no risky mutation of an existing seam). Negative-assert pins (per memo Β§2 + π©Έ's #407 negative-pin pattern): - `delay.ms` MUST NOT appear (heartbeats fire on cadence, not caller-elected delay). - `chain.step.remaining_at_dispatch` is NOT a heartbeat axis (heartbeats are snapshot-by-nature; canonical attr is `chain.step.remaining`). - Both pinned via `expect("<attr>" in attrs).toBe(false)` belt-and-braces beyond `toBeUndefined()`. Tests: - `pnpm vitest run src/infra/continuation-tracer.test.ts studies/swim-37/harness/swim-runner.test.ts` β 108 passed / 11 todo (was 95 passed / 13 todo for these two files). - SSOT pin updated 5β6 members; `ContinuationDisabledSignalKind` negative-set extended with "heartbeat". Lane discipline: zero overlap with #414 (`lich`), #415 (`chain.id` absence pin on lich), or #416 (`rebase.classify` in NEW `src/rebase/tracer.ts`). Co-authored by π« / π / π©Έ via memo Q-positions on PR #412.
β¦419) Two non-blocker nits from #417 review folded in additively: 1. `UUID-shape regex pin on auto-mint per-call freshness test` (π« #417 review): The existing 'mints a fresh heartbeat.id per call' test only asserted `.not.toBe(...)` between two consecutive captures, which catches leaks but not regressions where the auto-mint seam degrades to a counter or timestamp on one branch. Strengthened to ALSO match `crypto.randomUUID()` shape on each fresh mint. 2. `chainId synthesis-vs-span asymmetry JSDoc clarification` (π©Έ #417 review): The 'heartbeat' case in `captureSwim` synthesizes a result-field `chainId` for harness plumbing symmetry with the other primitives, but the EMITTED span correctly omits `chain.id` when caller omitted `opts.chainId`. Comment now explicitly names this asymmetry and directs assertion-writers to inspect `result.spans[0]!.attributes` instead of `result.chainId` for chain-context absence. Both nits are harness-shape, not wire-correctness. No production code touched; SSOT, helper signatures, and span attribute contracts unchanged. Tests: `pnpm vitest run swim-runner.test.ts` β 54 passed / 12 todo (no regressions; same row count). Lane-clean: zero overlap with #418 (`captureClassify` validation backfill, merged at `148792a0b7`).
This reverts commit 3396b88. The original commit mass-deleted 30 files (6745 deletions) under the label "rejected rebase artifacts." ~5141 of those deletions are landed swim-37 durability harness substrate from merged PRs #412/#413/#414/#416/#417/#418/#419 plus collateral docs/scripts. These are not rejected artifacts β they are committed, merged test infrastructure that proves continuation durability across compaction. Cohort review (π©Έ + π + π» + π«) confirmed the block finding at PR #515 issuecomment-4362337067. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
β¦6.4.24) (#515) * wo(canonical2-rebase-pathB): rebase Path-B's 5 cleanup commits onto canonical2 (figs directive 22:55Z) * chore(v3-cleanup): wave A cohort-identity scrub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(v3-cleanup): drop rejected rebase artifacts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: scrub workspace template wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(v3-cleanup): wave B structural dedup of continuation runtime Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: journal canonical2 wave B Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(v3-cleanup): wave C import discipline and build warnings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: journal canonical2 wave C Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(v3-cleanup): wave D surface continuation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: surface compaction count reconcile failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(v3-cleanup): wave E continuation coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: journal canonical2 wave E Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: align bundled plugin dependency types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: isolate bedrock app profile runtime deps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: scrub fork process labels from source comments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: close continuation type design blockers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: scrub continuation prompt process link Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: journal canonical2 final checkpoint Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert "chore(v3-cleanup): drop rejected rebase artifacts" This reverts commit 3396b88. The original commit mass-deleted 30 files (6745 deletions) under the label "rejected rebase artifacts." ~5141 of those deletions are landed swim-37 durability harness substrate from merged PRs #412/#413/#414/#416/#417/#418/#419 plus collateral docs/scripts. These are not rejected artifacts β they are committed, merged test infrastructure that proves continuation durability across compaction. Cohort review (π©Έ + π + π» + π«) confirmed the block finding at PR #515 issuecomment-4362337067. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: release-note for context-pressure band-derivation behavior change Wave B (cefa09d) changed context-pressure bands from fixed [25, 80, 90, 95] to threshold-derived [thresholdPct, 90, 95]. At default 0.8 the implicit 25% early-warning band is removed. Ship-acceptable per cohort review; release-note documents the change and points to #516 for the earlyWarningBand config opt follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore landed-PR tests missing from rebase fork-point Three test files from merged PRs (#462, #468, #511) were absent because this branch forked from canonical2 before those PRs landed. The post-revert allow-list audit (Β§3.4) flagged them as deletions from landed PRs. Restored from canonical2 HEAD (74940e5). - types.mode-shape.test.ts (#462) - agent-runner.continuation-span-uniformity.test.ts (#511) - store.continuation-merge.test.ts (#468) tmp-drop-me-otel-span-uniformity.md omitted (copilot scratch; safe to drop). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add rebase.classify to ContinuationSpanName for restored tracer The revert of 3396b88 restored src/rebase/tracer.ts which emits "rebase.classify" spans. Commit 4871c81 (fix: close continuation type design blockers) narrowed startSpan from string to ContinuationSpanName after tracer.ts was deleted β additive fix to include the span name in the union. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(continuation): add earlyWarningBand config opt for post-compaction cycle primer * test(continuation): pin earlyWarningBand default-preservation + opt-out branches * fix(continuation): add curly braces to satisfy linter * fix(continuation): unblock early-warning band fire path + make field optional Three bugs caught in cohort review of v5 (3e88ce5): 1. Suppression guard bug (Silas): non-postCompaction call sites bailed with 'ratio < threshold' BEFORE the resolved early-warn band could fire. Even with earlyWarningBand explicitly set, ratio=0.25 + threshold=0.8 resolved band=25 then was discarded. Guard now suppresses only when 'band === 0 && ratio < threshold' β preserves the round-to-band-0 dedup edge case while letting early-warn fire. 2. Type-required regression (Elliott): ContinuationRuntimeConfig had 'earlyWarningBand: number' (required), breaking 3 test fixtures (config.test, scheduler.test, post-compaction-delegate-dispatch.test) with TS2741. Field already optional at zod + resolver-default site; making the type optional matches. 3. Schema baseline regen (Elliott): src/config/schema.base.generated.ts needed regen to absorb the new earlyWarningBand field; preexisting models.providers.*.request.tls.insecureSkipVerify drift also absorbed in the same regen. Tests added: - checkContextPressure 'fires early-warning band below threshold when earlyWarningBand is set' (default-preservation path) - checkContextPressure 'does NOT fire below threshold when earlyWarningBand is 0' (opt-out path) All 107 affected tests pass: context-pressure (19), config (9), scheduler (12), schema.base.generated (10), post-compaction-delegate- dispatch (23), reply/context-pressure (34). Cohort cosign chain: π©Έ (root catch v5), π (default=0 catch), π« (suppression-guard catch), π» (type-required + baseline catch). Refs #515 --------- Co-authored-by: frond-scribe <frond-scribe@karmaterminal> Co-authored-by: Test User <test@example.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: dandelion cult - cael π©Έ <cael@dandelion.cult> Co-authored-by: dandelion cult - silas π« <silas.dandelion.cult@hotmail.com>
Memo-before-wire fold from #412 (merged at
1b84e71c95). Stacks oncael/325-canonical2between #414 (lich wire, π«οΈ) and #416 (rebase.classify wire, π).What
Adds production helper
emitContinuationHeartbeatSpantosrc/infra/continuation-tracer.tsand wirescaptureSwim("heartbeat", β¦)in the swim-37 harness. Flips 2it.todoβ 13 live tests + retains 0it.todofor heartbeat (matrix complete).Surface additions to
src/infra/continuation-tracer.tsemitContinuationHeartbeatSpan({ heartbeatId?, chainId?, chainStepRemaining?, disabledReason?, log? })mirroringemitContinuationDelegateSpan/emitContinuationCompactionReleasedSpandiscipline (try/catch, OK status, attribute-omission).heartbeat.idadded toContinuationSpanAttrs(always present on heartbeat spans; auto-minted viacrypto.randomUUID()when caller omits)."heartbeat"added toCONTINUATION_SIGNAL_KINDSSSOT.ContinuationDisabledSignalKindalready excludes "heartbeat" by Extract<>-narrowing β no change needed (negative-pin updated in tests).Cohort Q-positions honored
Per #412 review:
heartbeatspan (NO siblingheartbeat.fire); futureheartbeat.lag.msbecomes an attribute, not a sibling span.it.eachmatrix covering chain-context Γ disabled axes (under threshold of 12) + 3-testheartbeat.idprovenance describe block. Total +13 live tests.emitContinuationHeartbeatSpan, no risky mutation of an existing seam).Negative-assert pins
Per memo Β§2 + π©Έ's #407 negative-pin pattern:
delay.msMUST NOT appear (heartbeats fire on cadence, not caller-elected delay).chain.step.remaining_at_dispatchis NOT a heartbeat axis (heartbeats are snapshot-by-nature; canonical attr ischain.step.remaining).expect("<attr>" in attrs).toBe(false)belt-and-braces beyondtoBeUndefined().Tests
ContinuationDisabledSignalKindnegative-set extended with "heartbeat".Lane discipline
Zero overlap with #414 (
lich), #415 (chain.idabsence pin on lich), or #416 (rebase.classifyin NEWsrc/rebase/tracer.ts). All four primitives (continue_work,continue_delegate,heartbeat,lich) are now wired incaptureSwim(...).β π»
Co-authored by π«οΈ / π / π©Έ via memo Q-positions on PR #412.