- Rewrote the remaining em dashes outside docs/reviews/ (preserved as historical record, matching the alktty precedent): tests/ (313), fuzz/ (34 plus one shell comment), tasks/ (671 plus 12 kept in fences), docs/research + sdd_process + plans (525 plus 25 in fences), docs/architecture/overview.md fence labels (13), README, CHANGELOG, AGENTS.md, fuzz/README.md (176), and the .opencode/agents prompts (110) - Rewrote fenced content too: ASCII tree and module-map label separators, Rust snippet comments, and template placeholders; wire data, JSON/BAST examples, and transcripts survive verbatim - Rewrote 6 assert/expect message literals in tests/ and 2 format strings in fuzz/shared/src; no test data changed - Added Project Convention 18: no em dashes in prose (doc comments, markdown including docs/ and tasks/, agent prompts, commit messages); fence content rule, en-dash clause rule, and the no-spaced-hyphen prohibition follow alktty's convention 15 Follows6b20924(src doc comments) andcd0afe0(docs/architecture). Verification: cargo test (141 pass); cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/
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 |
|
single | low | component | research |
|
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:
- Keep as-is + docs (today's posture): raw framed halves are power-user territory; the doc note carries it.
- Add a convenience
take_framed_halves(the review's sketch): aDatagramReader-wrapped pair that keeps the codec so the halves API matches the datagram methods' framing posture. Additive to the rawtake_halves, which stays. - Re-shape
take_halvesfor datagram sessions (breaking, before the window closes): e.g. a datagram session'stake_halvesreturns framed-preserving halves by default, raw access via a distinct explicit method, which removes the footgun entirely at the cost of divergingtake_halvessemantics 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_halvesimport 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
taskgraphgraph stays acyclic; if an ADR lands,docs/ architecture/and the crate docs stay in sync (cargo doc --no-depsclean); 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) so the window is open and the breaking reshape (option 3) was available but nothing demonstrates the need.
- One
take_halvessemantic across substrates (the tunnel's wire bytes, uninterpreted) beats per-substrate divergence; the framing asymmetry is ADR-003's spec, not drift. - The
pump_againstseam composes the framed ADAPTER (UdpHalf, ADR-003's boundary codec, which 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, so deferring costs nothing. Design note recorded in the ADR: anAsyncRead-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)