---
name: pacer-0700-boundary-frames
description: "2026-08-30 SHIPPED pacer 0.7.0 — education moved off the digest poll onto type=\"boundary\" frames (releases#239); claude-spt needed NO change. Carries two mutation-caught test defects: a comment asserting a hazard that did not exist, and a test that reimplemented the code it checked."
metadata: 
  node_type: memory
  type: project
  originSessionId: c7810e19-679a-4f30-bebf-26af403899a0
  modified: 2026-08-30T09:12:44.920Z
---

**2026-08-30 — spt-pacer-tool v0.7.0 SHIPPED.** spt-core 0.67.0 (+ releases#239) surfaces session
boundaries to shells as `type="boundary"` frames.

## The judgement, both projects

- **claude-spt — NO CHANGE OWED, and that was the finding.** Boundary emission is core's, off seams
  we already drive (`spt api boundary clear|compact`, and perch bind for `boot`). doyle independently
  confirmed the same for `#254` (the SHELLS deprecation row at `/clear` under `[io] compliance`) —
  product-side, no adapter action. Resisting a change here was the right answer, not a lack of one.
- **spt-pacer-tool — a PLANNED migration whose trigger had already been written at the call site**:
  *"the operator has slated spt-bs-releases#239 to surface boundaries to shells as frames; when that
  lands this call goes with it."* It landed. ⭐ **Grep your own code for its pending triggers when a
  dependency ships** — the work was already specified, by me, months earlier.

## What changed

Education (the post-reset reminder) now runs off boundary FRAMES, not a digest poll. Frame = event;
digest = window. The window re-presented the same boundaries every poll, which is the *entire* reason
the old path carried a seen-set and a first-read-seeds flag — both deleted. Bonus fix: a reset that
happened while pacer was down used to be seeded away as history; it is now a spooled frame for a
genuinely amnesiac agent and earns its reminder.

The digest call is **gated, not deleted** — it is also the truncation-follow path. Deleting it
outright would have taken that silently. Floor 0.64.0 → **0.67.0** (too high refuses loudly; too low
is a feature that never fires).

## ⭐ I SHIPPED A COMMENT ASSERTING A HAZARD THAT DID NOT EXIST

I kept the digest's `BoundaryEntry` type with a confident comment: *"`KnownEntry` is an
externally-tagged enum, so serde must have a variant for every tag it can meet — delete this and a
window containing a boundary fails to deserialize as a whole."* **Mutation testing disproved it in
one run**: `Entry` is `#[serde(untagged)]` with an `Other(Value)` catch-all, so an unmodeled kind
already falls through. The type is now deleted and a test pins the fall-through.

A plausible mechanism, written confidently, that I never checked. Same family as
[[signature-match-is-not-root-cause]]. **A comment claiming "deleting this breaks X" is a claim, and
a claim needs a mutation, not confidence.**

## ⭐⭐ MY TEST REIMPLEMENTED THE CODE IT WAS TESTING

The drain-loop test helper re-stated the production `match` arm (the real loop needs a live poll to
reach). So a mutation that **disabled the refusal counter entirely left the suite green** — the test
was checking its own copy. Fixed by extracting the decision into `classify_boundary_line` →
`BoundaryLine`, which both production and the test now call.

**If a test can only reach the logic by restating it, extract the logic instead.** Sibling of
[[list-vs-predicate-assertions]] and the v0.35.0 fixture that could not reach the code.

## ⭐⭐⭐ A docs gap answered at the SOURCE

The frames page said boundary "takes the durable side" while the explicit MAC-stamped channel lists
omitted it — so a blind builder could not *confirm* stamping without inference. Flagged to doyle
mid-build rather than working around it; he confirmed AND **amended the public contract**
(`e643771f`) to state it. That is the AGENTS.md rule working: a docs gap is a durable-fix item, not a
workaround item. Design unchanged — require the stamp, and **count the refusals**, so a wrong
assumption lights a counter instead of silently dropping every boundary (which would look exactly
like a healthy build, because the thing that stops is a message sent to *somebody else*).

## State

Repo `93ecad5`, `v0.7.0` tagged/released on `BigscreenVR/spt-pacer-tool`, asset verified at the
consumer, installed (0.6.1 → 0.7.0, now-signal UPDATES confirms `PACER-0 adapter PACER 0.7.0`).
128 tests, `traceable-reqs` exit 0, all 4 mutations caught. New `REQ-PACER-BOUNDARY-FRAMES`.

**OWED:** field-verify the education path on a real `/clear` or `/compact` — needs an actual session
boundary, so it cannot be forced from inside a session. Watch pacer `status` for
`N boundary(ies), 0 refused` after the next reset; a nonzero *refused* would mean the stamping
assumption is wrong.

**OPEN QUESTION raised to doyle (unverified, not a defect claim):** the boundary vocabulary is
exactly `boot|clear|compact`, and claude-spt's `/resume` path takes a proofed `bind` rather than a
`boundary` — so it is unclear whether a resume emits `boot`, or nothing. A shell that reads these "to
stop attributing new output to the old session" would want to know. See [[resume-rebind-wedge]].
