feat(review 007 Unit 1): Establishment carries the channel plan (R-01) + lifetime doc (R-02)

Implements ADR-049 amendment 2 — the reserved Establishment payload is
filled, and the OpenHandler lifetime contract is documented.

- Establishment { plan: Option<ChannelPlan> } with ChannelPlan =
  Arc<dyn Any + Send + Sync>: typed-opaque, because the payload an
  establisher hands the pump handler is a live handle (dialed socket,
  TTY handle), not JSON — the review's Option<Value> sketch could not
  satisfy its own verification gate. #[non_exhaustive] keeps a future
  carrier change from being another break. Construction:
  Establishment::new(plan) / Establishment::default().
- OpenHandler gains the plan parameter:
  Fn(Value, Option<ChannelPlan>, Connection, AuthContext) ->
  JoinHandle<()>. Separate parameter (not merged into input) — a
  typed payload cannot ride the JSON input; no schema collision.
  Wire surface unchanged: the plan is process-local (establisher ->
  wrapper -> handler).
- run_open_wrapper threads establishment.plan to the handler; None
  when no establisher is registered. Kills the alktunnels-POC
  side-channel handoff (resource-keyed slot + poll loop) whose
  concurrent same-resource race is now unreachable — each open's
  establisher result flows to its own handler.
- Lifetime contract documented (R-02, doc-only half): the returned
  JoinHandle must track the data-plane lifetime — the wrapper awaits
  it and its completion triggers teardown; early return = teardown
  at birth. Noted on the OpenHandler type docs and both registration
  entry points.
- Breaking at 0.6.0 (the point of landing it before alktunnels
  Phase 1): Ok(Establishment {}) sites become
  Ok(Establishment::default()) mechanically.

Verification: cargo test (621 passed, +4: plan-flows-to-handler,
concurrent same-resource opens get distinct plans, no-establisher
None plan, Establishment construction), clippy -D warnings, fmt
--check, doc clean, test --all-features clean.
This commit is contained in:
glm-5.3-flash committed 2026-09-07 08:43:17 +00:00
1 parent 6590ab005f
commit dc4ad2bc6d
6 files changed
+385 -63

No files matched your search

@@ -353,4 +353,56 @@ CallError }` / `MissingChannelId` / `AdoptFailed`, with
verification gates from the review landed as tests (establisher
failure e2e through a real channels connection with ledger
un-increment + no-channel assertions, bounded timeout, no-establisher
compat, establisher-success pump round-trip).
compat, establisher-success pump round-trip).
## Amendment 2 (plan payload, 2026-09-07 — review 007 R-01/R-02)
Review 007 (from the alktunnels UDP POC) filed two follow-ups on the
establishment surface; both landed in alkcall 0.6.0.
**1. `Establishment` carries the channel plan (R-01).** §1 reserved
the payload ("today: nothing") and the wrapper consulted only
success/failure — so an establisher whose backend produces a handle
(a dialed socket, a TTY allocation) had to cross it to the pump
handler through a per-crate side channel. The alktunnels POC shipped
a resource-keyed slot + poll loop whose concurrent same-resource race
is unfixable within that shape; alktty documented the same wall
(backend `allocate` cannot cross, so failure classes stayed in-band —
the phantom-channel shape ADR-049 removed, alive one layer down).
The plan is now real: `Establishment { plan: Option<ChannelPlan> }`
with `ChannelPlan = Arc<dyn Any + Send + Sync>` — **typed-opaque, not
`serde_json::Value`**. The review's `Option<Value>` sketch could not
satisfy its own verification gate ("establisher dials, `plan` carries
the handle"): the payloads establishers actually hand off are live
handles with no JSON representation. The establisher and the
`OpenHandler` agree on the concrete type; alkcall never inspects it.
The wrapper threads `establishment.plan` to the handler's new second
parameter (`OpenHandler = Fn(Value, Option<ChannelPlan>, Connection,
AuthContext) -> JoinHandle<()>`); the separate-parameter shape wins
over merging into `input` because a typed payload cannot ride the
JSON input without a downcast-side registry and the reserved-key
collision the review already anticipated. The plan is process-local
(establisher → wrapper → handler on the producing side); the wire
surface is unchanged — nothing crosses the transport that isn't
already the open op's input. `#[non_exhaustive]` on `Establishment`
keeps a future carrier change from being another breaking release.
Construction is `Establishment::new(plan)` /
`Establishment::default()`; the 0.5.0 `Ok(Establishment {})` sites
break mechanically at 0.6.0, which is the point of landing this now
(before alktunnels Phase 1 ships the side-channel shape into a real
crate and the payload lands later anyway as a second break).
**2. The `OpenHandler` lifetime contract is documented (R-02).** The
wrapper awaits the returned `JoinHandle` and its completion triggers
teardown — so the handle must track the data-plane lifetime: a
handler that returns before its pumps finish tears the channel down
at birth (the POC's first pump implementation hit exactly this: every
tunnel connected then instantly EOF'd). The contract was implemented
but never documented; the type docs now state it ("await the pumps
inline, never spawn-and-forget and return early") on `OpenHandler`
and the registration entry points, plus a `debug!` telemetry line in
`run_open_wrapper` when a handler exits without having accepted the
channel's `BiStream` (the birth-teardown hint; the accept is
observable in-process via the yield-once source). §6's pinned
EOF-shaped panic semantics are unchanged.