docs: N-3 decision — take_halves stays raw-framed on datagram sessions

Review 001 N-3 (tasks/tunnels/decide-take-framed-halves), decided
before the first external consumer:

- Option 1 (keep as-is, docs carry it), recorded as ADR-005
  §Amendment. No external consumer exists (window open — the breaking
  reshape was available but undemonstrated); one take_halves semantic
  across substrates beats per-substrate divergence; pump_against
  composes the framed adapter (UdpHalf), not framed halves; and the
  take_framed_halves convenience is additive — it can land after the
  first consumer defines its shape.
- consumer.md's datagram section states the posture; review 001 §N-3
  resolution note landed; task marked completed.

Docs-only (no code change). Verified: fmt --check, doc --no-deps
warning-free, 63 default / 76 local tests green.
This commit is contained in:
glm-5.3-flash committed 2026-09-08 21:24:28 +00:00
1 parent 8cf4cc8bb5
commit 0bf4da3bb6
4 files changed
+105 -5

No files matched your search

+5 -1
View File
@@ -84,7 +84,11 @@ one API, `-L` and `-R` alike.
`Some(bytes)` per datagram — possibly empty (`len=0` is a legal
empty datagram) — and `None` only on stream EOF (the channels-level
sentinel; the codec never collides with it, F-2). A >65535-byte
send is rejected at frame time (`Oversize`).
send is rejected at frame time (`Oversize`). `take_halves()` on a
datagram session yields the RAW halves — the wire bytes carry the
codec framing (ADR-005's N-3 amendment: one `take_halves` semantic
for every substrate; keep the session for codec-applied I/O, or
compose the framed adapter at your own boundary).
## Lifecycle and Teardown (the point of ADR-005)
@@ -2,7 +2,13 @@
## Status
Accepted (2026-09-07; resolves the reverse POC W3 finding)
Accepted (2026-09-07; resolves the reverse POC W3 finding).
Amended (2026-09-08, review 001 N-3 decision): the datagram
`take_halves` posture is pinned — raw framed halves stay the only
owned-halves shape (`take_halves` keeps its semantics for every
substrate), the datagram accessors remain the codec path, and a
framed-halves convenience is deferred to the first consumer that
reaches for it. See §Amendment (2026-09-08, N-3).
## Context
@@ -86,6 +92,58 @@ its teardown:
## Consequences
### Amendment (2026-09-08 — the N-3 datagram `take_halves` posture, decided before the first external consumer)
Review 001 N-3: a datagram session exposes codec-applied accessors
(`send_datagram`/`recv_datagram`) until `take_halves` — then the
caller owns RAW halves whose wire bytes carry the `[len: u16 BE]`
framing. Documented, but a footgun asymmetry the API window
(this ADR's surface becomes ABI-stable at the first external
consumer) forced a decision on. Decided: **option 1 — keep as-is,
carried by docs**, with the rationale:
- **No external consumer exists** (verified 2026-09-08 — no crate in
the workspace depends on alktunnels); the window is open, so a
breaking reshape was *available* — but nothing yet demonstrates a
need for a different shape.
- **The framing asymmetry is ADR-003's spec, not drift.** The
datagram data plane is mandatory-framing by substrate
discrimination; "raw halves of a datagram session carry the
frames" is the same statement as "the codec lives at the substrate
boundary." `take_halves` yielding raw halves keeps ONE semantic
across substrates (the tunnel's wire bytes, uninterpreted); a
per-substrate `take_halves` (option 3) diverges semantics by
substrate — the worse confusion at the type level even as it
removes the byte-level footgun.
- **`pump_against` needs the framed adapter, not framed halves.** The
adjacent seam (N-11, resolved in-review with the doc note): a UDP
session pumped against an accepted local half requires the FRAMED
adapter on the accepted side — the `local` feature's `UdpHalf` (or
an assembly equivalent), not a raw socket. What composes there is
the adapter AT the boundary, not a session-side wrapper pair; a
`take_framed_halves` convenience would not feed this use.
- **The convenience is additive and can land later.** Option 2's
`take_framed_halves` breaks nothing when added after the first
consumer (unlike a `take_halves` reshape), so its timing is
decoupled from this window — deferring it costs nothing now, and
the first real consumer's reach defines its shape (a
`DatagramReader`-wrapped pair has an open design question: an
`AsyncRead` face re-collides with the F-2 empty-datagram/EOF
ambiguity the codec exists to prevent — the codec-side framing
wants accessor-shaped surface, which the session already has).
- **The docs carry the contract.** `take_halves`'s doc states the
framing (landed with the implementation); consumer.md's datagram
variant section now states the posture explicitly.
**Consequence:** datagram callers wanting codec-applied I/O after
taking ownership should keep the session (drive `send_datagram`/
`recv_datagram`; the session IS the owner) or compose `UdpHalf` at
their own boundary — `take_halves` is the raw-wire escape hatch and
stays documented as such. Revisit only on a demonstrated consumer
need (file an OQ at that point).
### Consequences (original)
- **Assembly layers cannot leak adopted channels** by forgetting to
reap — the type is the discipline (the W3 gap closes structurally).
- **The reverse-flow initiator gets the same session shape** as the
+11
View File
@@ -447,6 +447,17 @@ convenience `take_framed_halves` that keeps the codec (a
the review notes it for the API-stability window (ADR-005's surface
becomes ABI-stable at first external consumer — decide before then).
**RESOLUTION (2026-09-08, decided before the first external
consumer):** option 1 — keep as-is, docs carry it — recorded as
ADR-005's §Amendment (2026-09-08, N-3). No external consumer exists
(window open, so the breaking reshape was available but
undemonstrated); one `take_halves` semantic across substrates beats
per-substrate divergence; the `pump_against` seam composes the
framed ADAPTER (UdpHalf), not framed halves; and the convenience
(option 2) is additive — it can land after the first consumer
defines its shape without breaking anything. consumer.md's datagram
section states the posture.
## N-4 [minor] — `open_reverse_channel` builds the params JSON by hand instead of serializing `TunnelParams` — FIXED in-review
`src/consumer.rs:395-409`: the substrate is matched to its string and
+30 -3
View File
@@ -1,13 +1,14 @@
---
id: tunnels/decide-take-framed-halves
name: "N-3 decision — take_halves on datagram sessions hands RAW FRAMED bytes; decide the API posture before the first external consumer"
status: pending
status: completed
depends_on: [tunnels/review-impl]
scope: single
risk: low
impact: component
level: research
tags: [api, udp, codec, adr-005, api-stability, review-remediation]
completed: 2026-09-08
---
## Description
@@ -66,12 +67,38 @@ exposes both codec halves of the job).
## Acceptance Criteria
- [ ] A written decision (ADR amendment or task-note resolution) with
- [x] A written decision (ADR amendment or task-note resolution) with
the chosen option and rationale, before the first external
consumer
- [ ] If the decision adds surface: tests + battery green; review 001
- [x] If the decision adds surface: tests + battery green; review 001
§N-3 gets a resolution note
## Resolution (2026-09-08)
**Option 1 — keep as-is, docs carry it.** Recorded as ADR-005's
§Amendment (2026-09-08, N-3). Decision inputs:
- **No external consumer exists** (workspace grep verified 2026-09-08)
— the window is open, so the breaking reshape (option 3) was
available but nothing demonstrates the need.
- One `take_halves` semantic across substrates (the tunnel's wire
bytes, uninterpreted) beats per-substrate divergence; the framing
asymmetry is ADR-003's spec, not drift.
- The `pump_against` seam composes the framed ADAPTER (`UdpHalf`,
ADR-003's boundary codec — landed with the U-1 remediation the same
day), not framed halves; option 2 would not feed that use.
- Option 2 (`take_framed_halves`) is additive and can land after the
first consumer defines its shape without breaking anything —
deferring costs nothing. Design note recorded in the ADR: an
`AsyncRead`-faced wrapper re-collides with F-2's
empty-datagram/EOF ambiguity; the codec-side framing wants
accessor-shaped surface, which the session already has.
Docs sync: ADR-005 §Amendment; consumer.md's datagram section states
the posture; review 001 §N-3 resolution note landed. No code change
(the decision adds no surface), so the standing battery is the
verification: 63 default / 76 local green, doc build clean.
## References
- `docs/reviews/001-implementation-review.md` §N-3 (the finding) and