Files
alktunnels/tasks/tunnels/decide-take-framed-halves.md
T
glm-5.3-flash 0bf4da3bb6 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.
2026-09-08 21:24:28 +00:00

4.7 KiB

id, name, status, depends_on, scope, risk, impact, level, tags, completed
id name status depends_on scope risk impact level tags completed
tunnels/decide-take-framed-halves N-3 decision — take_halves on datagram sessions hands RAW FRAMED bytes; decide the API posture before the first external consumer completed
tunnels/review-impl
single low component research
api
udp
codec
adr-005
api-stability
review-remediation
2026-09-08

Description

Review 001 finding N-3 (docs/reviews/001-implementation-review.md): a datagram session exposes send_datagram/recv_datagram (codec applied) until take_halves — then the caller owns RAW halves whose wire bytes carry the codec framing ([len: u16 BE][payload], documented on take_halves). The doc covers it, but it is a footgun asymmetry: the same session flips from "codec applied" to "codec is yours" depending on which accessor was called, and nothing in the type system warns about it.

This is an API-posture question, not a bug: ADR-005's surface becomes ABI-stable at the first external consumer, so the decision must land before then. The options on the table:

  1. Keep as-is + docs (today's posture): raw framed halves are power-user territory; the doc note carries it.
  2. Add a convenience take_framed_halves (the review's sketch): a DatagramReader-wrapped pair that keeps the codec so the halves API matches the datagram methods' framing posture. Additive to the raw take_halves, which stays.
  3. Re-shape take_halves for datagram sessions (breaking, before the window closes): e.g. a datagram session's take_halves returns framed-preserving halves by default, raw access via a distinct explicit method — removes the footgun entirely, at the cost of diverging take_halves semantics per substrate.

The decision should weigh: the pump_against shape (a UDP session pumped against an accepted local half needs the FRAMED adapter on the accepted side — the same framing-responsibility seam N-11's doc note covers), what an actual consumer (alkcall's assembly layer, a DNS-proxy shape) reaches for first, and whether a DatagramReader + write-frame wrapper pair is small enough to just ship (option 2 is a thin compose of existing public pieces — src/wire.rs already exposes both codec halves of the job).

Work

  • Re-read review 001 §N-3 + ADR-005 (the API-stability window); confirm no external consumer has pinned the take_halves import style yet (if one has, the window is closed — record that and choose the additive path (2) or docs-only (1)).
  • Write the decision as an ADR amendment (ADR-005) or a new ADR if the posture adds a public method; file the OQ only if the decision defers again.

Verification

  • taskgraph graph stays acyclic; if an ADR lands, docs/ architecture/ and the crate docs stay in sync (cargo doc --no-deps clean); if a method lands, the full battery + wasm checks pass.

Acceptance Criteria

  • 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 §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 §N-11 (the adjacent pump_against framing seam)
  • docs/architecture/decisions/005-consumer-session-owns-teardown.md (the API-stability window), src/consumer.rs (take_halves, TakenHalves), src/wire.rs (frame_datagram, DatagramReader)