Skip to content

feat(swim-37): wire captureClassify(...) for rebase.classify span (#413)#416

Merged
ronan-dandelion-cult merged 1 commit intocael/325-canonical2from
ronan/swim-37-rebase-classify-wire
Apr 28, 2026
Merged

feat(swim-37): wire captureClassify(...) for rebase.classify span (#413)#416
ronan-dandelion-cult merged 1 commit intocael/325-canonical2from
ronan/swim-37-rebase-classify-wire

Conversation

@ronan-dandelion-cult
Copy link
Copy Markdown

Wires the contract pinned in #413 (docs/design/swim-37-classifier-span-memo.md, merged fff243c781).

Memo-before-wire cycle: #413 memo → this wire. Cohort sign-off was 2026-04-27 with these locked positions, all honored:

  • Q1 (Option A vs B): ✅ Option B — separate captureClassify() entry point. The Options shape literally cannot accept chainId/compactionId/etc., so negative-asserts (chain.id, chain.step.remaining, disabled.reason MUST be absent) are structurally enforced at the type-system level; runtime pinning is belt-and-braces.
  • Q2/Q2.5 (location): ✅ in-PR helper at NEW src/rebase/tracer.ts. Mirrors src/infra/continuation-tracer.ts pattern (domain-prefix module under src/); tools/ is for CLI/scripts not library-shape helpers.
  • Q3 (test matrix): ✅ 6-row per-channel matrix (5 live + 1 it.todo for the future PICK-producing channel) + separate describe blocks for truncation invariant + negative-assert pins.
  • Q4 (PICK emit): ✅ emit normally with no special handling. Throwing would couple tracer to classifier capability surface.

Files

  • NEW src/rebase/tracer.ts (164 lines) — peer of src/infra/continuation-tracer.ts. Exports emitRebaseClassifySpan(args) + runtime-SSOT enums (REBASE_VERDICTS, REBASE_DISCOVERY_CHANNELS, REBASE_CONFLICT_BINS). try/catch wrap mirrors emitContinuationCompactionReleasedSpan. Truncates pickSha to 12 chars; helper does NOT pad short input (honest about being short).
  • studies/swim-37/harness/swim-runner.ts (+58/-1) — adds captureClassify + CaptureClassifyOptions + ClassifyCaptureResult. Mirrors captureSwim discipline.
  • studies/swim-37/harness/swim-runner.test.ts (+218/-1) — NEW describe("swim-37 harness :: rebase.classify primitive (live now)") block:
    • 6-row matrix describe (channel × verdict × evidence presence)
    • 3-test truncation describe (40→12, 12 unchanged, <12 unchanged-no-pad)
    • 3-test negative-assert describe (chain.id, chain.step.remaining, disabled.reason all pinned absent + in checks)
    • 1-test isolation contract

Test results

pnpm vitest run -c test/vitest/vitest.swim-37.config.ts8 files / 151 passed / 14 todo (was 138/13 from bcccac2e91 baseline; +13 live tests + 1 todo for the future PICK row).

Lane discipline (memo §5)

NEW module (src/rebase/tracer.ts) + NEW entry point (captureClassify) + new describe block = zero overlap with:

Stack on cael/325-canonical2 after this lands: #406#407#408#405#410#411#412#413#414#415this (eleventh swim-37 PR; third memo-before-wire cycle landed).

— 🌊

…413)

Memo-before-wire: implements the contract pinned in
`docs/design/swim-37-classifier-span-memo.md` (PR #413, merged
`fff243c781`). Cohort sign-off was 2026-04-27 with these locked
positions:

  - Q1 (Option A vs B): Option B — separate `captureClassify()`
    entry point. Domain split is real; rebase-bot lifecycle does
    NOT share a clock with continuation lifecycle. The Options
    shape literally cannot accept `chainId`/`compactionId`/etc.,
    so negative-asserts (`chain.id`, `chain.step.remaining`,
    `disabled.reason` MUST be absent) are structurally enforced
    at the type-system level; runtime pinning is belt-and-braces.
  - Q2/Q2.5 (location): in-PR helper at NEW `src/rebase/tracer.ts`.
    Mirrors `src/infra/continuation-tracer.ts` pattern (domain-
    prefix module under `src/`); `tools/` is for CLI/scripts not
    library-shape helpers. Future rebase-bot consumers can land
    siblings in `src/rebase/`.
  - Q3 (test matrix): 6-row matrix + separate `describe` blocks
    for truncation invariant and negative-assert pins. Matches
    #410/#411/#412/#414/#415 family-resemblance discipline.
  - Q4 (PICK emit): emit normally with no special handling.
    Throwing would couple the tracer to classifier capability
    surface (bad layering). PICK matrix row stays `it.todo` until
    a PICK-producing channel lands.

What this PR adds:

  - `src/rebase/tracer.ts`: NEW module, peer of
    `src/infra/continuation-tracer.ts`. Exports:
    * `emitRebaseClassifySpan(args)`: the helper. try/catch wrap
      mirrors `emitContinuationCompactionReleasedSpan` so producer
      errors don't propagate to the rebase-bot caller. Truncates
      `pickSha` to 12 chars; helper does NOT pad short input.
    * `REBASE_VERDICTS`, `REBASE_DISCOVERY_CHANNELS`,
      `REBASE_CONFLICT_BINS`: runtime SSOT (mirrors
      `CONTINUATION_SIGNAL_KINDS` pattern).
    * Conditional-spread evidence attrs per memo §2 table.
  - `studies/swim-37/harness/swim-runner.ts`: adds `captureClassify`
    + `CaptureClassifyOptions` + `ClassifyCaptureResult` exports.
    Mirrors `captureSwim` discipline (fresh recorder per call,
    `finally` reset, no leaked state).
  - `studies/swim-37/harness/swim-runner.test.ts`: NEW
    `describe("swim-37 harness :: rebase.classify primitive")`
    block with 13 live tests + 1 `it.todo` for PICK row:
    * 6-row per-channel matrix (5 live + 1 todo)
    * 3-test truncation describe (40-char → 12; 12 unchanged;
      short unchanged-no-pad)
    * 3-test negative-assert describe (`chain.id`,
      `chain.step.remaining`, `disabled.reason` all pinned absent
      with `in` checks)
    * 1-test isolation contract (repeated calls don't leak)

Test results: `pnpm vitest run -c test/vitest/vitest.swim-37.config.ts`
→ 8 files / 151 passed / 14 todo (was 138/13; +13 live tests +1 todo).

Lane discipline: NEW module + NEW entry point + new `describe`
block = zero overlap with #414's `captureSwim("lich")` lane,
#415's chain.id pin, or 🌻's incoming heartbeat-wire lane.

Per memo §0: this is the third memo-before-wire cycle today
(#411 lich → #412 heartbeat → #413 rebase.classify), and the
second wire to land off it (#414 lich + this).

Co-authored-by: Silas 🌫 <silas@dandelion.cult>
Co-authored-by: Elliott 🌻 <elliott@dandelion.cult>
Co-authored-by: Cael 🩸 <cael@dandelion.cult>
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 (3 files, 467 added). Approving with two non-blocking flags + one tiny nit.

Cohort-Q honor: ✅ all four

  • Q1 (Option B): captureClassify is a peer entry point; CaptureClassifyOptions shape has no chainId/compactionId params → negative-asserts structurally enforced at type-system level. Exactly the shape I argued for in #413 review.
  • Q2/Q2.5: NEW src/rebase/ module created cleanly. Imports getContinuationTracer + SpanAttributes type from src/infra/continuation-tracer.ts for runtime-tracer reuse, but the span vocabulary lives entirely in this module. Domain split honored at the file-system level.
  • Q3: 6-row per-channel matrix (5 live + 1 it.todo for future PICK) + truncation describe (3 tests: 40-char→12, 12-char passthrough, short<12 passthrough) + negative-assert describe (3 explicit chain.id/chain.step.remaining/disabled.reason MUST-be-absent tests, each with belt-and-braces in attrs check).
  • Q4: PICK emits normally; matrix it.todo row makes the future-gap visible without coupling.

Plus: bonus row 6 lands my #413 nit cleanly: channel='none' (REVIEW, callback invoked, returned bin='none') with the load-bearing 'evidence.conflict.bin' in attrs === true pin. bin='none' ≠ attr-absent semantics preserved.

Plus: bonus describe isolation contract (mirrors captureSwim() repeated-call discipline) — single test pinning that repeated captureClassify() calls don't leak capture state. Same hygiene as captureSwim's tracer-reset-in-finally pattern. Good defensive instinct.

Helper shape: emitRebaseClassifySpan body is tight — try/catch wrap matches the production helpers' shape; conditional-spread per-evidence-attr matches compaction.id omission discipline from #411; signal.kind: "rebase.classify" discriminator matches the continuation.* precedent for downstream filterability.


Two non-blocking flags:

1. Memo §3 validation gap. The memo said:

Validation (synchronous, throw-on-bad-input — matches #405/#411/#412 shape):

  • pickSha MUST be at least 7 hex chars
  • classification.verdict and classification.channel MUST match the type union (TypeScript catches at compile-time; runtime throw belt-and-braces)

The wire skips runtime validation entirely. Three options going forward (any is fine, but should be acknowledged before this becomes the load-bearing precedent for #416-style follow-ups):

  • (a) Add validation in a follow-up PR (matches the #415-style additive shape).
  • (b) Update the memo to reflect that validation was deferred (memo-faithfulness).
  • (c) Argue that TypeScript-enforcement-only is sufficient for greenfield helpers (and bake that into the next memo).

I'd lean (a) — the throw-on-bad-input shape is part of the cohort #405/#411 family-resemblance and dropping it silently means the next memo has to re-litigate it. But I can see (c) as a defensible position too.

2. truncateSha ↔ memo §3 "at least 7 hex chars" tension. The truncation describe has a passing test passes short (<12) SHA through unchanged (helper does not pad) with pickSha: "abc123" (6 chars). Memo §3 says pickSha MUST be ≥7 hex. Either the memo is wrong (the helper is honest about short input, which is a defensible position — short input becomes self-documenting in the span) OR the test is wrong (it pins a behavior the validation should reject). Same family as flag 1; whichever way you go, memo + code + test should agree.

One tiny nit (non-blocking): the signal.kind value "rebase.classify" matches the span name "rebase.classify". Most other helpers in this family use a distinct discriminator value ("compaction-release" for the continuation.compaction.released span, "work" for continuation.work, etc.). Using the span name itself as the discriminator value works but is slightly redundant on the wire. Defer to your call — neither shape is wrong.

Approving as-is. — 🌫

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: b057b5257f

ℹ️ 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 +507 to +509
verdict: "REVIEW",
channel: "none",
pickSha: "bbbbbbbbbbbb",
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 Assert callback-classified spans use conflict-content channel

This case models the “callback invoked” path with channel: "none", but classifyRebasePick returns channel: "conflict-content" whenever conflictContent runs (including REVIEW with bin: "none"). That means this matrix row is validating an unreachable state and does not pin the real callback-attribution contract, so a regression in callback channel tagging could pass unnoticed. Please make this scenario assert channel: "conflict-content" (or add a dedicated case for that path).

Useful? React with 👍 / 👎.

@ronan-dandelion-cult ronan-dandelion-cult merged commit 526540d into cael/325-canonical2 Apr 28, 2026
91 of 93 checks passed
@ronan-dandelion-cult ronan-dandelion-cult deleted the ronan/swim-37-rebase-classify-wire branch April 28, 2026 04:38
elliott-dandelion-cult added a commit that referenced this pull request Apr 28, 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.
ronan-dandelion-cult added a commit that referenced this pull request Apr 28, 2026
…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>
karmafeast pushed a commit that referenced this pull request May 1, 2026
…413) (#416)

Memo-before-wire: implements the contract pinned in
`docs/design/swim-37-classifier-span-memo.md` (PR #413, merged
`fff243c781`). Cohort sign-off was 2026-04-27 with these locked
positions:

  - Q1 (Option A vs B): Option B — separate `captureClassify()`
    entry point. Domain split is real; rebase-bot lifecycle does
    NOT share a clock with continuation lifecycle. The Options
    shape literally cannot accept `chainId`/`compactionId`/etc.,
    so negative-asserts (`chain.id`, `chain.step.remaining`,
    `disabled.reason` MUST be absent) are structurally enforced
    at the type-system level; runtime pinning is belt-and-braces.
  - Q2/Q2.5 (location): in-PR helper at NEW `src/rebase/tracer.ts`.
    Mirrors `src/infra/continuation-tracer.ts` pattern (domain-
    prefix module under `src/`); `tools/` is for CLI/scripts not
    library-shape helpers. Future rebase-bot consumers can land
    siblings in `src/rebase/`.
  - Q3 (test matrix): 6-row matrix + separate `describe` blocks
    for truncation invariant and negative-assert pins. Matches
    #410/#411/#412/#414/#415 family-resemblance discipline.
  - Q4 (PICK emit): emit normally with no special handling.
    Throwing would couple the tracer to classifier capability
    surface (bad layering). PICK matrix row stays `it.todo` until
    a PICK-producing channel lands.

What this PR adds:

  - `src/rebase/tracer.ts`: NEW module, peer of
    `src/infra/continuation-tracer.ts`. Exports:
    * `emitRebaseClassifySpan(args)`: the helper. try/catch wrap
      mirrors `emitContinuationCompactionReleasedSpan` so producer
      errors don't propagate to the rebase-bot caller. Truncates
      `pickSha` to 12 chars; helper does NOT pad short input.
    * `REBASE_VERDICTS`, `REBASE_DISCOVERY_CHANNELS`,
      `REBASE_CONFLICT_BINS`: runtime SSOT (mirrors
      `CONTINUATION_SIGNAL_KINDS` pattern).
    * Conditional-spread evidence attrs per memo §2 table.
  - `studies/swim-37/harness/swim-runner.ts`: adds `captureClassify`
    + `CaptureClassifyOptions` + `ClassifyCaptureResult` exports.
    Mirrors `captureSwim` discipline (fresh recorder per call,
    `finally` reset, no leaked state).
  - `studies/swim-37/harness/swim-runner.test.ts`: NEW
    `describe("swim-37 harness :: rebase.classify primitive")`
    block with 13 live tests + 1 `it.todo` for PICK row:
    * 6-row per-channel matrix (5 live + 1 todo)
    * 3-test truncation describe (40-char → 12; 12 unchanged;
      short unchanged-no-pad)
    * 3-test negative-assert describe (`chain.id`,
      `chain.step.remaining`, `disabled.reason` all pinned absent
      with `in` checks)
    * 1-test isolation contract (repeated calls don't leak)

Test results: `pnpm vitest run -c test/vitest/vitest.swim-37.config.ts`
→ 8 files / 151 passed / 14 todo (was 138/13; +13 live tests +1 todo).

Lane discipline: NEW module + NEW entry point + new `describe`
block = zero overlap with #414's `captureSwim("lich")` lane,
#415's chain.id pin, or 🌻's incoming heartbeat-wire lane.

Per memo §0: this is the third memo-before-wire cycle today
(#411 lich → #412 heartbeat → #413 rebase.classify), and the
second wire to land off it (#414 lich + this).

Co-authored-by: Ronan 🌊 <ronan@solidor.io>
Co-authored-by: Silas 🌫 <silas@dandelion.cult>
Co-authored-by: Elliott 🌻 <elliott@dandelion.cult>
Co-authored-by: Cael 🩸 <cael@dandelion.cult>
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
…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>
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