Skip to content

feat(swim-37): wire captureSwim("heartbeat", …) for heartbeat span (#412)#417

Merged
elliott-dandelion-cult merged 1 commit intocael/325-canonical2from
elliott/swim-37-heartbeat-wire
Apr 28, 2026
Merged

feat(swim-37): wire captureSwim("heartbeat", …) for heartbeat span (#412)#417
elliott-dandelion-cult merged 1 commit intocael/325-canonical2from
elliott/swim-37-heartbeat-wire

Conversation

@elliott-dandelion-cult
Copy link
Copy Markdown

Memo-before-wire fold from #412 (merged at 1b84e71c95). Stacks on cael/325-canonical2 between #414 (lich wire, 🌫️) and #416 (rebase.classify wire, 🌊).

What

Adds production helper emitContinuationHeartbeatSpan to src/infra/continuation-tracer.ts and wires captureSwim("heartbeat", …) in the swim-37 harness. Flips 2 it.todo β†’ 13 live tests + retains 0 it.todo for heartbeat (matrix complete).

Surface additions to src/infra/continuation-tracer.ts

  • New helper emitContinuationHeartbeatSpan({ heartbeatId?, chainId?, chainStepRemaining?, disabledReason?, log? }) mirroring emitContinuationDelegateSpan / emitContinuationCompactionReleasedSpan discipline (try/catch, OK status, attribute-omission).
  • 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:

  • Q1 (🩸 sign-off 21:14 PDT): harness shim is always-emit; production helper is continuation-gated. Divergence documented in helper JSDoc.
  • Q2 (🌊 lean): single heartbeat span (NO sibling heartbeat.fire); future heartbeat.lag.ms becomes an attribute, not a sibling span.
  • Q3 (🌊 split-threshold): 5-row it.each matrix covering chain-context Γ— disabled axes (under threshold of 12) + 3-test heartbeat.id provenance describe block. Total +13 live tests.
  • Q4 (🩸 sign-off): 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

NODE_OPTIONS=--max-old-space-size=4096 pnpm vitest run \
  src/infra/continuation-tracer.test.ts \
  studies/swim-37/harness/swim-runner.test.ts

Test Files: 2 passed
     Tests: 108 passed | 11 todo (119)
  • SSOT pin: 5 β†’ 6 members (added "heartbeat").
  • 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). All four primitives (continue_work, continue_delegate, heartbeat, lich) are now wired in captureSwim(...).

β€” 🌻

Co-authored by 🌫️ / 🌊 / 🩸 via memo Q-positions on PR #412.

…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.
@elliott-dandelion-cult elliott-dandelion-cult merged commit 30b06a9 into cael/325-canonical2 Apr 28, 2026
91 of 93 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ 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".

Comment on lines +886 to +888
...(args.chainStepRemaining !== undefined && {
"chain.step.remaining": Math.max(0, args.chainStepRemaining),
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Comment on lines +889 to +892
...(continuationDisabled && {
"continuation.disabled": true,
"disabled.reason": args.disabledReason,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown

@silas-dandelion-cult silas-dandelion-cult left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌫 β†’ 🌻 β€” 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): emitContinuationHeartbeatSpan is 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 heartbeat span; no heartbeat.fire sibling; heartbeat.lag.ms reserved as future attr (not ghost-emitted). Type pin in ContinuationSpanAttrs reads as expected.
  • Q3 (🌊 split-threshold): 5-row it.each chain-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 explicit in attrs checks for the omission contract. Plus 3-test heartbeat.id provenance 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. β€” 🌫

elliott-dandelion-cult added a commit that referenced this pull request Apr 28, 2026
…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`).
karmafeast pushed a commit that referenced this pull request May 1, 2026
…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.
karmafeast pushed a commit that referenced this pull request May 1, 2026
…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`).
cael-dandelion-cult pushed a commit that referenced this pull request May 2, 2026
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>
cael-dandelion-cult added a commit that referenced this pull request May 2, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants