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:
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.
|
||||
Reference in new issue
Block a user