---
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 → CLOSED 2026-08-30T09:19Z. FIELD-VERIFIED on a real `clear`.** An across-commune was used
as the deliberate test fixture (it fires a genuine `clear` boundary). Anchor captured BEFORE the
delta: io-events `MAX_SEQ 116`, pacer `0 boundary(ies), 0 refused`. After the clear, pacer `status`
read **`1 boundary(ies), 0 refused`** (18 frames, `COMMUNE 1`). The counter is the proof — pacer's
reminder *arriving* is not, because education also fires on first online, and that reminder did in
fact arrive post-clear (io seq 126) exactly as it would have on either path. Zero `refused` also
confirms the MAC-stamping doyle amended into the contract: boundary frames pass the stamp check.

Same fixture field-proved claude-spt's `REQ-HAZARD-ACROSS-CLEAR-DISPATCH-DROP` (v0.36.0, never
before exercised): io-events above 116 carry the arming turn's ordinary prose as `AGENT_OUTPUT`
rows with `mid: true` (seq 118, 121), a `COMMUNE` row for the file (seq 120), a `clear` row (seq
122) — and **no `AGENT_OUTPUT` containing the commune body**, so the funnel exclusion holds and a
delta-only seen-set is not poisoned by a file that names every peer. The endpoint was never marked
idle inside the quiet window. One fixture, both paths, each proved by a distinct observation.

**RESUME QUESTION — ANSWERED 2026-08-30 by doyle, measured at source; contract AMENDED.** The
dedup is against the session that is ALREADY CURRENT (the perch session-ledger's LAST row):

- `/resume` of the **current** session (the common case — harness restart, resume straight back) is
  a re-bind and **emits nothing**.
- `/resume` of an **older** session (a different session bound in between) is a real session-identity
  edge and **fires `boot`** — correctly, because the view of the endpoint genuinely changed sessions.

Build to: **`boot` means the endpoint's session identity CHANGED at bind; the ABSENCE of boot across
a resume means you are still looking at the session you were.** The frames page's "already current"
sentence was the precise contract all along, but the `boot` kind-table row said only "a cold start",
which under-described the resume arm — doyle amended the row to state both arms and the emits-nothing
case explicitly. No action owed either side. See [[resume-rebind-wedge]].

⭐ Second time this round a docs gap was closed AT THE SOURCE by asking rather than inferring (the
MAC-stamping row was the first). Asking a blind-build question is not a delay — it is how the public
contract gets fixed for the next builder.
