docs(review): alkssh consumer input — the post-construction identity seam (one dialed connection, many per-session identities)

This commit is contained in:
glm-5.3-flash committed 2026-10-31 12:56:01 +00:00
1 parent a99cc67e76
commit 8e6aa79736
1 file changed
+137
@@ -0,0 +1,137 @@
# Review 014 — The post-construction identity seam: consumer input from alkssh's SSH server direction
## Status
Open — filed 2026-10-31. Consumer input from alkssh 0.1.0 (published
2026-10-31), under the family's upstream posture (file asks, never
absorb silently). Booked alkssh-side at its wave-3 review gate (item 5)
as the "non-detaching post-construction identity seam" note and carried
unabsorbed through the alkssh 0.1.0 release; this is that filing.
This is the server-direction sibling of the identity-capture class the
alkhttp ledger recorded as CF-005 ("no caller-identity capture on the
connect-side serving path"): there, the transport-authenticated peer
could not be captured *at acceptance*; here, a correctly resolved
application-identity cannot be attached *after dialing*. Same boundary
(transport connection identity vs. per-session/per-op truth), opposite
end of the connection's lifecycle.
## The finding
A consumer that authenticates **after** the fabric connection exists —
an SSH front door, where the russh auth hooks fire per-session while
the substrate connection is dialed once by the assembly — has no
correct surface to make its resolved identity the one the fabric's ACL
sees.
The pieces, verified against 0.10.1 source:
- `Connection::set_identity` is once-only (`src/core/types.rs:541` —
a second call errors `IdentityAlreadySet`). The identity slot is
consumed at construction/establishment time; the hub template
demonstrates the intended composition point: the establisher
closure receives `channel0_conn` and may set identity at the
transport-establish event (`src/channels/hub_leg.rs:206-221` —
"transport connection's identity propagates to channel 0 …
dispatch's fallback resolves through it").
- The only post-construction way to reach an already-held
`ChannelClient`'s connection is `ChannelClient::take_call_connection`
(`src/channels/client.rs:521`), which **detaches the client's own
calling surface** ("after this, `call_open_op` returns an error").
For a hub that is at least arguable (the relay note flags the detach
as wrong there too). For a long-lived door it is disqualifying:
alkssh rejected this surface on exactly that ground — "the detach
would strand every later session."
## The consumer scenario, concretely (alkssh server direction)
1. The assembly dials the substrate and configures the door with an
`Arc<ChannelClient>` (alkssh `ServeConfig::channel_client`). The
fabric connection — and its identity slot — exist and are composed
**before any SSH client has connected**. The assembly's dial-time
identity ("once at dial, propagated to channel 0 at
`from_connection`") is what `AccessControl::check(peer_identity)`
on the substrate sees from then on.
2. Each SSH session authenticates later, via `auth_publickey`
(post-verification; fingerprint → `Identity` resolution,
fail-closed). alkssh ties into these hooks correctly — the
resolution, the door-side `no-identity` guard, and the gate at the
door all work and need no alkcall change.
3. The gap is only the second gate: the admitted mapped open rides
the dialed fabric connection, and the substrate's ACL re-checks
`peer_identity` (CF-005/CF-006). There is no non-detaching,
post-construction way to attach the per-auth resolved identity —
and structurally there cannot be a single one for a session with
a **shared** handle: `set_identity` is once-only, so even a
hypothetical setter cannot represent N sessions' identities on one
connection.
So this review documents two coupled facts, not one bug: **(a)** no
seam exists to attach identity post-dialing without detaching the
calling surface; **(b)** even with one, the once-per-connection
identity model does not fit "one fabric connection, many
per-session authentications."
## What the consumer shipped instead (the current honest posture)
- The no-detach posture preserved (`set_identity`/`take_call_connection`
appear nowhere in alkssh `src/`; recorded in alkssh
`review-wave-3.md`, item 5's disposition and `w3-tunnel-direct`'s
mechanics record).
- Identity gated at the door (fail-closed `no-identity` guard,
unreachable on the wire) with the substrate gate seeing dial-time
composition — the composed-gate arrangement is documented in
alkssh `config.rs`'s module docs (§the identity ride) as a decided
posture, not a drift risk. Safety degrades to the door-side gate
plus the assembly's dial-time identity semantics; nothing
anonymous passes at either gate unless the assembly composes it.
- One incidental hardening alkssh took that touches this crate's
surface in spirit: the door's listen router refuses a colliding
token mint fail-closed (never replaces; alkssh `w4-router-collision`
— in-depth defense, safety otherwise resting on this crate's
ADR-008 mint discipline). No alkcall change asked; recorded for
the trail.
## What a resolution decides (framing, not prescription)
Any resolution is an API/semantics decision in a published crate —
ADR-class in this repo, one-way from alkssh's consumer:
1. **A seam**: a non-detaching post-construction identity mechanism on
a held `ChannelClient`/connection (the hub's establisher hook has
no consumer-side equivalent today). Whether that is connection- or
op-scoped decides nothing else below by itself.
2. **The identity model behind it**: what `peer_identity` *means* when
one dialed connection serves many per-session identities —
per-open identity carriage (op-scoped, the CF-005 payload-token
path's shape), connection-per-auth, or an explicit ruling that
dial-time identity is always the substrate-truth and per-session
gates are the door's job. A ruling alone (option 3) is a valid
resolution: it converts the current arrangement from "happened to
work" to contract.
3. **The shared-handle corollary**: N-authentications-per-connection
is what doors built like alkssh will always present. Whatever
shape lands should state this case explicitly rather than assume
one identity per connection is the norm.
## References
- alkssh `docs/architecture/auth.md` §Server direction step 4 (the
auth-hook resolution; the visible-surface invariant)
- alkssh `src/config.rs` module docs §the identity ride (the shipped
posture; why `take_call_connection` was rejected) and
`src/tunnel_map.rs` (the no-detach posture in code: no
`set_identity` calls)
- alkssh `tasks/implementation/review-wave-3.md` item 5 (the original
booking) and `tasks/implementation/w3-tunnel-direct.md` (the
mechanics record); `tasks/implementation/review-wave-5.md` /
`docs/plans/implementation.md` (carried unabsorbed through release)
- `src/core/types.rs:541-549` (`set_identity` once-only),
`src/channels/client.rs:514-523` (`take_call_connection` detach),
`src/channels/hub_leg.rs:213-221` (the establisher's identity
composition point)
- `docs/reviews/consumer-findings-ledger.md` CF-005 (the
connect-side sibling of this class; its resolution is the
payload-token/CF-005 precedence machinery alkssh's scenario rides)
- ADR-047/ADR-048 class precedents for how a published-crate
semantics ruling ships (loud default precedent, ADR-048)