adr-014: the idle-timeout posture — teardown-governed phases, no protocol-layer idle bound

- ADR-014 accepted (review 002 C-2): the pre-command handshake, the
  BIND quiet wait, and the association lifetime are all
  teardown-governed; the protocol layer never times out (option 1)
- the BIND relationship recorded as same regime (ADR-007 decided one
  phase's ends by designing BIND; ADR-014 records the inherited
  resolution — one principle, not two)
- the doors' accept-path obligations recorded pre-batch-2 (accept-stage
  idle bound, per-credential/per-peer caps, accept-queue bounds);
  revisitable additively (an optional producer-side idle seam would
  be config, not wire)
- spec lines carry the decided posture: wire.md (association-lifetime
  bullet, no-in-band-control bullet, preamble, frontmatter);
  producer.md (the open handler, the local-backend section, decision
  table, frontmatter); overview.md ADR index
- one upstream-fidelity correction: upstream's dial timeout maps to
  0x06 (TTL expired), not 0x01 as the review sketch suggested
- open-questions.md unchanged (C-2 was never tracked there)

Verification: cargo test green (357 passed), clippy -D warnings clean,
fmt clean, cargo doc clean, wasm check + clippy (wasm32) clean
This commit is contained in:
glm-5.3-flash committed 2026-09-24 19:16:30 +00:00
1 parent ff3295422c
commit 986bd13ce7
5 files changed
+309 -28

No files matched your search

@@ -1,7 +1,7 @@
---
id: architecture/adr-014-idle-timeout-posture
name: "ADR-014 — the idle-timeout posture for the pre-command handshake and the association (review-002 C-2)"
status: pending
status: completed
depends_on: []
scope: narrow
risk: medium
@@ -68,19 +68,19 @@ one.
## Acceptance Criteria
- [ ] ADR-014 exists at
- [x] ADR-014 exists at
`docs/architecture/decisions/014-<slug>.md`, status accepted,
deciding explicitly between the two options (or a reasoned
third)
- [ ] The BIND quiet wait's ADR-007 relationship is stated (same
- [x] The BIND quiet wait's ADR-007 relationship is stated (same
regime or a principled difference)
- [ ] The doors' DoS posture is recorded (what the assembly layer
- [x] The doors' DoS posture is recorded (what the assembly layer
must do if the protocol layer does not)
- [ ] The affected spec lines (wire.md, producer.md) carry the
- [x] The affected spec lines (wire.md, producer.md) carry the
decided posture — no implicit residue
- [ ] The decision lands before any `local`/`composed` door work
- [x] The decision lands before any `local`/`composed` door work
(the batch-2 tasks may depend on this one)
- [ ] `cargo doc --no-deps` clean if any doc-comment carries the
- [x] `cargo doc --no-deps` clean if any doc-comment carries the
posture; the full verification sweep green
## References
@@ -99,8 +99,68 @@ one.
## Notes
> Agent fills this during implementation.
> Filled by the implementation agent (2026-09-24):
- **Option 1 won** — the protocol layer never times out; every phase
(pre-command handshake, BIND quiet wait, association lifetime) is
teardown-governed (EOF / garbage / error sentinels), never
idle-expired. No code changed; the posture is recorded.
- **The sharpening argument** (why a handshake-only bound is
incoherent): every post-reply phase *legally* parks indefinitely on
a silent peer — an idle CONNECT tunnel, a quiet association,
BIND's quiet wait (ADR-007). The pre-command hold costs exactly
what those holds cost (one parked task + one channel entry); a
handshake-only bound protects a sliver while the dominant hold
stays contractually open, and a bound on both is a full inactivity
timeout, which contradicts the landed contracts (association
lifetime = channel lifetime; "FTP-length waits stay legal,"
ADR-007). The unbounded-time aggregate is a property of every idle
session — bounded by the cap regime (ADR-040/041), not by a timer.
- **One upstream-fidelity correction to the review's sketch**:
upstream's connect-timeout path maps its *dial* timeout to
`ReplyError::ConnectionTimeout` → 0x06 (TTL expired), not 0x01 —
verified in the reference checkout (`util/stream.rs`) and the
forked table (`src/rfc1928/mod.rs`). Recorded in the ADR; relevant
only if option 2 is ever built.
- **open-questions.md needed no update** — C-2 was tracked as a
review finding (tasks/), not as an OQ; no OQ entry exists for it.
- **The doors' obligations are recorded pre-batch-2** (the
"inherits this bound knowingly" pattern, wire.md §UDP
ASSOCIATE's): an accept-stage idle bound (the slowloris sliver —
an accepted socket is not a channel, the cap regime does not bound
it), per-credential/per-peer session caps (the cred-bearing
door's config table, ADR-005, is the knob), and accept-queue/
relay-task bounds. Same obligations for the producer-side local
backend's accept loop and a composed bind-first listener's front.
- **Revisitability**: not a one-way door — a producer-side idle seam
is additive (builder config defaulting to `None` = this posture;
`tokio::time` already in the wasm-clean subset; timeout = EOF-
shaped teardown). Revisit trigger recorded: a deployment with a
peer class outside both the cap regime and any door policy, with
measured evidence.
## Summary
> Agent fills this on completion.
ADR-014 landed
(`docs/architecture/decisions/014-idle-timeout-posture.md`, status
accepted, 2026-09-24), deciding review 002 C-2: **option 1 — the
protocol layer never times out**. The pre-command handshake, the
BIND quiet wait, and the association lifetime are all
teardown-governed; the BIND relationship is recorded as *same
regime* (ADR-007 decided one phase's ends by designing BIND; ADR-014
decides the rest by recording the inherited resolution — one
principle: the protocol layer cannot distinguish malicious silence
from a slow legitimate peer, so timeout policy belongs to the
deployment-facing layer). The DoS posture is explicit and layered
(ACL → per-connection cap → per-identity cap → door/dial policy —
the ADR-010 pattern), with the doors' accept-path obligations
recorded before any batch-2 door exists. The decision is revisitable
additively (not a one-way door). Spec lines updated — wire.md §UDP
ASSOCIATE's association-lifetime bullet + the phase-model
"no in-band control vocabulary" bullet + the preamble's ADR list +
frontmatter; producer.md §The Open Handler's pump description + the
local-backend section + the decision table + frontmatter;
overview.md's ADR index. No code changes; no OQ update needed
(C-2 was never tracked there). `cargo doc --no-deps` clean; full
verification sweep green (`cargo test` 348+ green, clippy, fmt,
wasm check).