# JIT plan — generic envelope-attribute pass-through in the re-render

**Status: BUILT AND SHIPPED as adapter v0.31.0 (2026-08-26).** The gate is discharged: core
v0.63.0 landed on this node with its docs bundle, and the clause plus both riders were READ on this
node's own book at `localhost:5474` before any code was written. The instrument was validated on the
same fetch rather than trusted — control terms present in the served page, and the pre-update
negative taken with the server confirmed up (HTTP 200, 657KB), so "no such section on 0.62.0" was a
real absence and not a dead endpoint answering quietly.

Re-read against the published page, the plan below needed no correction — recorded because "the
plan survived the page" is only worth anything if the check actually happened. Two things the page
supplied that the routed quotation had not: the attribute codec's newline entity is `&#10;` (which
is why a raw carried span stays line-safe on a line-framed surface), and `<EVENT-PART>` reassembly
is a LISTENER-stream concern that `api poll` never produces — so it is not this seam's problem, a
fact now established from the page rather than assumed.

## Why (the recurrence class, not a fourth bug)

`hook::render_frames` keeps `from` + body and drops every other attribute. Each new envelope
attribute has therefore had to be caught BY NAME after a field loss:

| attribute | lost in | caught in |
|---|---|---|
| `mnemonics-json` | v0.26.2 | `REQ-MONIC-REVEAL` |
| `trust-warning` | v0.29.x | `REQ-TRUST-WARNING-CARRY` |
| `seal` | v0.30.0 | `REQ-SEAL-ATTR-SURFACE` |

Three losses at one seam is a recurrence class. A by-name fix cannot close it: the next attribute
is unnamed by construction, and the deletion is invisible at the site that does the dropping —
nothing there ever names what it dropped.

## The obligation (core `REQ-EVENT-ATTR-PASSTHROUGH`, v0.63.0)

Binding on the ADAPTER RE-RENDER — answered YES by the filer to the discriminating question, with
this surface named as the clause's audience. Two rules:

1. **Carry through every attribute you do not yourself consume.** The only closed list in the
   contract is the receiver-composed STRIP class (`trust-warning`, `mnemonics-json`) — a strip
   list, not a render list. Everything else rides.
2. **Keep the value in attribute position with its wire escaping intact.** Decode only where the
   final consumer parses attributes; unescaping into body or frame context hands a hostile sender a
   forgery seam.

Read onto our three cases by the filer: `trust-warning` and `mnemonics-json` are CONSUMED (we
render their content into our own blocks) — discharged. `seal` is surfaced — discharged. Everything
else, including attributes that do not exist yet, must ride.

**Our v0.30.0 mechanism already generalizes**: the raw attr-escaped carry built for `seal`
(claude-spt-bs#20, now the contract's mirror clause) is the correct handling for any unknown
attribute. This is a widening, not an invention.

**Timing, stated by the filer:** a NEW obligation entering the contract at v0.63.0, not a
retroactive defect in v0.30.0. Nothing to file against what shipped; the generalization is our
release on our schedule.

## The amended clause (rider @ f7137293, same v0.63.0 publish)

Our `type` question exposed that "consume" was reader-derived, so the filer amended the PAGE rather
than answering only us — the difference between a ruling and a contract. Two bullets, verbatim:

> - **"Consume" means the attribute's distinction is re-expressed, not merely read.** A pipeline
>   consumes an attribute when its handling puts the value's meaning back in front of the agent —
>   content re-rendered into the pipeline's own surface, or a dispatch whose *agent-visible outcome*
>   differs per value (routing on `type` consumes it only while distinct types yield distinguishable
>   deliveries). Reading a value and then emitting output that two sender-distinct deliveries would
>   share is peeking, not consuming. The test is the drop test: if omitting the attribute makes two
>   deliveries the sender distinguished indistinguishable at the agent surface, it must ride.
> - **Attribute names are tokens, not text.** The envelope's name grammar is `[a-z0-9_-]+`. Names are
>   hostile-reachable — envelopes arrive from peer *nodes*, not only from this binary — so a
>   pass-through re-emits a name only when it matches that grammar; a tag-position span that does not
>   is framing damage, not an attribute. Refuse or drop it **loudly**, never re-emit it.

Neither of our two candidate definitions survived: not "the re-emitting site reads it" (peeking),
not "the agent still needs it" (untestable). Our names flag was taken INTO the contract rather than
left as adapter defense-in-depth.

## Census — CLOSED: `type` is not consumed, so it must ride

Run 2026-08-25 against our own code, complete rather than sampled:

- Every non-test attribute read in `hook.rs`: `from` (re-emitted), `seal`, `trust-warning`,
  `mnemonics-json`, and `parse_update_notify_version`, whose lookup keys on `from == "spt-update"` —
  **not** on `type`.
- A second sweep for type-keyed literals (`notify` / `alarm` / `user-msg` / `"msg"`) in non-test code
  found only doc comments and CC hook-payload types, a different vocabulary entirely.
- Therefore `render_frames` never reads the envelope's `type`, and
  `<EVENT type="notify" from="x">b</EVENT>` and `<EVENT type="msg" from="x">b</EVENT>` render
  **byte-identically**. That is the drop test failing exactly as written.
- The idle/live arm is separately type-agnostic *by design* (`REQ-STUB-GENERAL-EVENT`: notify,
  user-msg and alarm stub and park identically to a peer msg). `type` survives there only because
  `frame_envelope` retypes the whole opening tag verbatim — **carried, never consumed**.

Two sweeps rather than one because a single grep that covers half a population reads exactly like one
that covers all of it — the trap this project has already paid for.

## Build sketch (to be re-derived against the published page)

- `render_frames`: after the consumed attributes are handled, collect the REMAINING attributes from
  the EVENT opening tag as raw `name="rawvalue"` spans and re-emit them on the `<sptc_messages>` tag,
  order preserved, values untouched. `type` is in that set, per the census above.
- Consumed set (excluded from the carry): `from` (already re-emitted), `trust-warning`,
  `mnemonics-json`, `seal`. Re-derive this list against the drop test at build time rather than
  trusting this line — a set that was right when written is the exact thing this clause exists to
  catch.
- Parsing: today's `opening_attr` is a single-name lookup. This needs an attribute ITERATOR over the
  opening tag. Not a general XML parser — the envelope grammar is fixed and line-framed — and it must
  be total: a malformed opening tag yields whatever parses and never panics, since the dangling-frame
  arm already owns the truncated case.
- **Name grammar is now contract, not hardening:** re-emit a name only when it matches `[a-z0-9_-]+`.
  A tag-position span that does not match is framing damage — refuse or drop it LOUDLY, never
  re-emit. Loudly means visible to the agent, in the `<sptc_frame_integrity>` family, not a log line
  nobody reads (§2.17's lesson: evidence that reaches nobody is a silence).
- Values ride raw and attr-escaped, so a hostile VALUE cannot close our tag — the v0.30.0 `seal`
  mechanism, unchanged.
- Digest: `frame_spans` carries whole spans already, so a widened tag rides for free — assert it
  rather than assume it.

## Gate

`sh ci/run-gates.sh` PASS + `traceable-reqs check` exit 0. Activate
`REQ-EVENT-ATTR-PASSTHROUGH-RENDER` with `doc`/`impl`/`unit` at that point (it sits at
`required_stages = []` until then, per the activate-don't-pre-fail rule).

Units to write: an unknown attribute rides through verbatim; several ride in order; `type` rides (the
census verdict, pinned as a test so a future refactor that starts consuming it has to say so); the
consumed set does NOT double-appear; a hostile unknown VALUE cannot break framing; a name matching
`[a-z0-9_-]+` rides; a tag-position span that does NOT match is refused loudly and never re-emitted;
an envelope with only known attributes renders byte-identically to today (the no-regression control);
the digest span carries the widened tag. Mutation-check the suite before calling it green, as
v0.30.0's was.

## Built — what actually shipped (v0.31.0)

- `opening_attrs` — a total attribute ITERATOR over the opening tag returning `(name, RAW value)` in
  wire order, plus the refused spans. Never panics on a damaged tag; the dangling-frame arm still
  owns truncation.
- `CONSUMED_ATTRS = [from, seal, trust-warning, mnemonics-json]`, RE-DERIVED against the drop test at
  build time rather than trusted from the sketch above. Everything else rides, `type` included.
- `is_attr_name` pins `[a-z0-9_-]+`; a non-matching tag-position span is REFUSED, never re-emitted,
  and reported in a `<sptc_frame_integrity>` block — loud IN THE AGENT'S CONTEXT, with the damaged
  span shown escaped. Parsing continues past the damage.
- Ordering: framing damage leads even the trust warning — every other block is derived FROM that
  opening tag, `from` included, so a caution read out of a damaged frame must not be believed ahead
  of the notice that the frame is damaged.
- The digest arm needed no change and it is ASSERTED rather than assumed (`frame_spans` matches by
  tag NAME and takes the whole span).

## Gate result

`sh ci/run-gates.sh` PASS, `traceable-reqs check` exit 0, 524 units green — and MUTATION-CHECKED
before being called green, per the v0.30.0 lesson. Seven mutations, each caught by its intended test:
carry-nothing (29 fail), carry-everything (27), sort-the-attributes (1), accept-any-name (2),
refuse-quietly (1), stop-at-first-damage (1), unescape-the-value (1).

**Note the deliberate byte change:** `type` now rides, so EVERY delivery renders differently than it
did in v0.30.0 — 30 existing expectations were updated. That is the census verdict landing, not
collateral. The no-regression control is therefore written on an envelope of ONLY consumed
attributes; a byte-identity control written on a `type`-bearing envelope would assert the defect back.

## Field — partially verified, same night, without staging it

Seconds after this node updated to 0.31.0, an unrelated peer answer arrived in the agent's context
rendered as `<sptc_messages from="alchemy-1" type="msg">`. Pre-0.31.0 that same delivery read
`<sptc_messages from="alchemy-1">` and nothing else. So the carry is live on the real path — spool
→ poll drain → `render_frames` → agent context — for a genuine peer delivery, OBSERVED rather than
expected, and on traffic nobody arranged for the purpose.

**What that measures, and no more:** `type` survives the re-render on a real same-node delivery. It
does NOT discharge the unknown-attribute arm (which needs a name this binary has never heard of, and
which by construction this binary cannot mint for itself), and it says nothing about a cross-node
arrival.

**Owed:** field proof that a genuinely unknown attribute minted by a PEER NODE rides through. No CI
arm can produce one (the attribute has to be unknown to this binary), so it lands with the next
sealed/attributed send doyle coordinates.
