docs(architecture): scaffolding + ADR-001 (alk/ssh ALPN pin, Proposed)

- decisions/001: pins alk/ssh to one contract — raw vanilla-SSH byte
  stream, endpoint = SshAdapter::handle; payload-blind relay restated
  per alkcall ADR-042; phase 1 serves edge termination only; relay/
  browser legs stay compatible by construction (consumers deferred,
  OQ-01). Status Proposed — ledger-rule-1 acceptance pending review.
- open-questions.md: 13 OQs seeded from POC findings (3 resolved by
  evidence: username disposition, vault role split, reconnect policy;
  4 deferred with blockers: relay consumer, cert auth, env allowlist,
  sftp/alkfs); deferred/failed trackers filed under tasks/architecture/
  for each deferred(scope) OQ (taskgraph validate: 4/4 valid)
- README.md index: current state, doc table, planned docs, lifecycle
This commit is contained in:
glm-5.3-flash committed 2026-10-26 06:11:42 +00:00
1 parent 31207057ff
commit 644269b258
7 files changed
+458

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
---
status: draft
last_updated: 2026-10-26
---
# Architecture index
## Current State
Phase 1 (architecture) — in progress. Phase 0 is closed: all three
POCs passed (121 checks; `docs/research/poc-{1,2,3}-*.md`) and the
research docs record their corrections. This directory is being
populated: ADR-001 (the `alk/ssh` ALPN pin) is drafted for review, the
OQ tracker is seeded from the POC findings, and the spec documents
below are being written. No implementation until specs reach
`reviewed` (per `docs/sdd_process.md`).
## Architecture Documents
| Document | Status | Scope |
|---|---|---|
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker (seeded from phase 0 findings) |
| [decisions/001](decisions/001-alkssh-alpn-pin.md) | Proposed | The `alk/ssh` ALPN pin (ledger rule 1) |
### Planned documents (not yet written)
| Planned doc | Scope |
|---|---|
| `overview.md` | Crate purpose, exports, dependency posture, bounded-resources policy |
| `mapping.md` | The spine — server-direction channel-type/request→family-seam rows (absorbs the validated `mapping-table.md`) |
| `client.md` | Client-direction surface: dial rows, the forwarded-channel acceptor, reverse-runner shape |
| `auth.md` | Server auth bridge + client-side vault key custody |
## ADR Table
| ADR | Title | Status |
|---|---|---|
| [ADR-001](decisions/001-alkssh-alpn-pin.md) | The `alk/ssh` ALPN pin — edge-termination contract for `SshAdapter` | Proposed |
## Open Questions
Tracked in [open-questions.md](open-questions.md) — OQ-01 … OQ-13,
seeded from the phase 0 POC findings and research docs.
## Document Lifecycle
| Status | Meaning | Transitions |
|--------|---------|-------------|
| `draft` | Under active development; may change significantly | → `reviewed` when its open questions are resolved |
| `reviewed` | Final; implementation may begin; changes require review | → `stable` when implementation is complete and verified |
| `stable` | Locked; changes require review and may warrant an ADR | → `deprecated` when superseded |
| `deprecated` | Superseded; kept for reference | Removed when unreferenced |
## References
- `docs/sdd_process.md` — the SDD process
- `docs/research/` — phase 0 (vision, POC plans + findings, russh
inventory, auth bridge, mapping table, reverse-runner extraction)
- Family: `/workspace/@alkdev/alkfamily/docs/architecture/decisions/001..003`
(ADR-003 is the alkssh structural decision)
- Sibling: alkcall ADRs 002/004/009/034/042/051, alkhttp ADR-014/016/073-analogs
@@ -0,0 +1,109 @@
---
status: draft
last_updated: 2026-10-26
---
# ADR-001: The `alk/ssh` ALPN pin — edge-termination contract for `SshAdapter`
## Status
**Proposed** — 2026-10-26. Pending review sign-off; once Accepted this
is a one-way door (ledger rule 1: ALPN registry entries are stable —
"once published, does not change"). Acceptance marks the pin for the
family ledger; the first consumer is alkforge (family ADR-003
decision 4: the pin lands before alkforge becomes the consumer).
## Context
Family ADR-003 (decision 4) requires alkssh to pin exactly one
`alk/ssh` ALPN via its own ADR. Four alkcall ADRs constrain what that
pin means:
- **ADR-004** (ALPN convention + connection model) reserves
`alk/ssh` → `SshAdapter` as a custom protocol.
- **ADR-034** (channels wire format) grants `alk/ssh` stream_types
`[0, 1]` — "SSH multiplexes internally, including its own control".
The channels layer is byte-blind to SSH; it must never parse SSH
channel data.
- **ADR-042 / ADR-051** (hub relay): "The hub never runs a handler for
`alk/tty`, `alk/ssh`, or `alk/tunnel`" — SSH channels are relayed
payload-blind between spokes (translate + byte-forward with id
rewrite, ACL fired at `channel/open` by the hub's `CallAdapter`).
- **ADR-009** (BiStream handler leaf) sketches the browser route: a
browser opens a WebSocket/WebTransport session to `/alk/ssh`; the
h3/WS handler hands each bidi stream to `SshAdapter::handle` as a
`Connection`; a WASM SSH parser speaks vanilla SSH over the stream.
Phase 0 left the topology open (`mapping-table.md` §topology; vision
scope question 1): whether `alk/ssh` carries raw vanilla-SSH bytes
between alkssh instances (a relay leg), serves the browser route, or
both — and how that coexists with ADR-042's relay ruling. POC-3
settled what could be settled by evidence: the served topology is
**edge termination** (russh terminates vanilla SSH at the alkssh
endpoint; family seams are consumed in-process or via `alk/channels`
bridge legs), worked end to end with vanilla clients through published
sibling crates — and no leg of it requires the hub to parse SSH.
The decision that remains is the *pin*: what contract `alk/ssh`
commits to on the ledger, so the ALPN string cannot be reinterpreted
later.
## Decision
1. **The pin.** `alk/ssh` is pinned for exactly one contract: a
connection opened under `alk/ssh` carries a **raw vanilla-SSH byte
stream**, unchanged and uninterpreted, and the receiving endpoint
is `SshAdapter::handle(Connection)` — alkssh's russh server stack
mounted at an alkcall `channel/open` boundary. Each bidi stream is
one independent SSH transport with its own KEX, auth, and channel
multiplexing (hence ADR-034's `[0, 1]` — SSH owns its own
multiplexing, and the channels layer stays byte-blind, per
ADR-042's relay ruling).
2. **Payload-blind relay is part of the contract.** Any intermediate
(the hub in ADR-042's topology, or a spoke forwarding to a spoke)
relays `alk/ssh` streams as opaque bytes; it runs ACL at
`channel/open` and never an SSH handler. This is ADR-042's ruling,
restated as the ALPN's own contract so it cannot drift.
3. **Phase 1 serves edge termination only.** The first deployment
shape — russh terminating vanilla SSH at the alkssh edge, family
seams consumed in-process — does not speak `alk/ssh` itself: it is
a door, not a relay party. No pin behavior in this ADR requires the
relay leg to exist.
4. **The relay leg and the browser-WASM route remain compatible by
construction.** Because the contract is a transport-blind raw byte
stream, either future leg (hub-relayed SSH, browser WS →
`SshAdapter::handle`) satisfies it without a pin change. Their
*consumers* are deferred — see OQ-01 — on a concrete use case.
5. **What the pin is not.** It is not a carrier for alkcall's framed
channels traffic (that has its own protocol); it is not a second
SSH-flavored control channel; and honesty cuts both ways — an
endpoint that declines to serve `alk/ssh` must refuse the open,
never accept-and-stall (the same honest-advertisement rule that
runs through every alkssh decision).
## Consequences
- **Positive**: the ALPN decouples the *endpoint-adjacency contract*
(frozen here) from the *served edge topology* (which evolves with
the family seams); the browser and relay routes are pre-enabled at
zero pin cost; the ledger gets a single unambiguous entry.
- **Negative**: one-way door — the raw-vanilla-SSH-stream contract
binds every future consumer (alkforge first). If a consumer ever
needed framed or pre-parsed SSH over `alk/ssh`, that would be a new
ALPN + new ADR, not a reinterpretation.
- **Neutral**: `SshAdapter::handle` mounts the full russh server
stack per stream, so endpoints pay per-connection bounded-resource
limits (window, auth-attempt, inactivity ceilings) — the alkssh
limits contract applies per `alk/ssh` stream, not per transport.
## References
- Family ADR-003 (`003-alkssh-russh-adapter.md`) — decisions 1 and 4
- alkcall ADR-002 (ProtocolHandler ALPN claim), ADR-004
(the `alk/ssh` → `SshAdapter` reservation), ADR-009 (browser-WASM
sketch), ADR-034 (`alk/ssh` stream_types `[0, 1]`), ADR-042/051
(relay: payload-blind, hub handler-free)
- `docs/research/mapping-table.md` §topology,
`docs/research/vision.md` scope Q1,
`docs/research/poc-3-mapping-bakeoff.md` (edge termination, proven)
- OQ-01 (relay/browser-leg consumers, deferred)
+145
View File
@@ -0,0 +1,145 @@
---
status: draft
last_updated: 2026-10-26
---
# Open questions
Centralized tracker for alkssh. Seeded from the phase 0 POC findings
(`docs/research/poc-{1,2,3}-*.md`), the research docs' OQ seeds, and
the reverse-runner extraction. Numbers are repo-local (family and
alkcall trackers have their own OQ spaces).
## Theme A — wire & family topology
### OQ-01: Does an `alk/ssh` relay/browser-leg consumer emerge?
- **Origin**: [mapping-table.md](../research/mapping-table.md) §topology, `vision.md` scope Q1
- **Status**: deferred(scope)
- **Door type**: two-way (the pin is one-way; consumer *existence* is not)
- **Priority**: medium
- **Impacts**: none for the phase 1 scope — ADR-001 pins the contract so relay/browser legs don't foreclose it; the phase 1 docs serve edge termination only. A future relay leg would exercise `docs/architecture/client.md`'s accept surface in a new role.
- **Blocked on**: a concrete consumer — either the browser-WASM route (alkcall ADR-009 sketch) materializing in an assembly, or a hub deployment needing SSH-in-relay. No such use case exists yet.
- **Resolution**: Not yet decidable — no consumer use case exists. ADR-001 decides everything decidable now: the ALPN pin's raw-stream contract, and its transport-blindness.
- **Cross-references**: ADR-001, OQ-13
## Theme B — session-channel dispatch & mapping details
### OQ-02: What is the session-channel dispatch shape (SOCKS-detect vs parse, and who bounds the window)?
- **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §Stage B.3, `mapping-table.md` §notes
- **Status**: deferred(unclear)
- **Door type**: two-way
- **Priority**: high
- **Impacts**: blocks the exact dispatch contract detail in the `mapping.md` spec (ordering: exec-parse vs SOCKS-detect vs pipe; the detect window's bound; crate default vs assembly config).
- **Investigation**: the pieces exist — POC-3 proven both modes (stage A parse-then-route for git; stage B bounded first-byte sniff for SOCKS) and proven both serve "session" channels behind different features. Unclear is the composition: walk 2+ concrete session-channel flows (git exec, family-native SOCKS client, plain pipe exec) and pin dispatch precedence, the detect window's default bound, and the configuration seam. Do during `mapping.md` writing.
- **Resolution**: Not yet decidable by inspection — needs the composition worked through, not new facts.
- **Cross-references**: OQ-08
### OQ-03: ~~Username disposition~~ — resolved
- **Origin**: [poc-1-auth-bridge.md](../research/poc-1-auth-bridge.md) §2, `auth-bridge.md` §The mapping
- **Status**: resolved
- **Resolution**: usernames are never the identity model and carry no routing semantics — the fingerprint is the sole resolution path (POC-1: `ssh anything@host` works with the same identity). Whether a username string appears in logs is deployment policy, outside the crate. Rides as the recorded contract in the `auth.md` spec / ADR-004.
### OQ-04: OpenSSH certificate auth — is there ever a CA-trust use case?
- **Origin**: `mapping-table.md` (auth rows), [poc-1-auth-bridge.md](../research/poc-1-auth-bridge.md) OQ seed 4
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low
- **Impacts**: none — `auth_openssh_certificate` is declined (the ADR-004 disposition), and POC-1 established the decline exists. A revisit only matters if alkforge ever wants CA-issued certificates.
- **Blocked on**: a concrete OpenSSH-CA use case arriving in an embedding assembly.
- **Resolution**: Not yet decidable — decline initial; no CA-trust story in the family identity model today.
## Theme C — auth, identity & key custody
### OQ-05: What is the client-side host-key pin store?
- **Origin**: [auth-bridge.md](../research/auth-bridge.md) §Host keys, `reverse-runner.md` §5
- **Status**: open
- **Priority**: high
- **Impacts**: blocks the `client.md` dial surface (the `check_server_key` policy and its configuration seam) and any alkforge reverse-runner deployment (every dial needs a pinned host).
- **Resolution**: pending. The no-TOFU rule is fixed; candidates are the family identity registry vs an assembly-supplied pin set vs `check_known_hosts`. Decide when `client.md` is written — the reverse-runner evidence (chalk-full accept-all in old code) is the anti-pattern witness, not the precedent.
- **Cross-references**: ADR-004, OQ-07
### OQ-06: ~~One vault, many roles?~~ — resolved
- **Origin**: [poc-2-vault-keys.md](../research/poc-2-vault-keys.md) OQ seed 3
- **Status**: resolved
- **Resolution**: not an alkssh constraint. The crate takes vault handles the assembly injects; whether the assembly runs separate vault instances per role (client keys vs host keys) is assembly policy. ADR-005 records the boundary: vault handle in, key material out, version semantics owned by the vault.
### OQ-07: Is the alkvault `decrypt` plain-`String` zeroization gap closed at the vault or at the adapter?
- **Origin**: [poc-2-vault-keys.md](../research/poc-2-vault-keys.md) §2 + OQ seed 1
- **Status**: open
- **Priority**: medium
- **Impacts**: an in-family cross-crate ask (alkvault API change vs alkssh-side convention). Blocks nothing functionally — POC-2 proves the adapter-side `Zeroizing` wrap correct — but decides where the security convention lives for the whole family (alkhttp secrets ride the same `&str`-out surface).
- **Resolution**: pending. Candidates: alkvault returns `Zeroizing<String>` (family-wide fix, touches a published crate), or the adapter-side wrap is the recorded family convention (ADR-005). Bring to alkvault; if upstream accepts, ADR-005 cites the follow-up; if not, ADR-005 records the adapter obligation as the convention.
- **Cross-references**: OQ-05, ADR-005
## Theme D — session-request mapping details
### OQ-08: Shell command convention for `shell_request`
- **Origin**: `mapping-table.md` (shell row), [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) OQ seed 3
- **Status**: open
- **Priority**: medium
- **Impacts**: blocks the `mapping.md` session-row detail (what alktty `drives` when a vanilla `ssh host` lands). The POC's stand-in (`bash`/`sh -c`) was explicit assembly policy, not wire-derived.
- **Resolution**: pending. Direction known (assembly policy, nothing wire-supplied); the decision is the concrete convention surface: a config field on the assembly vs a crate-level default that the assembly may override. Decide when writing `mapping.md`.
### OQ-09: `env_request` policy — is an allowlist ever wanted?
- **Origin**: `mapping-table.md` (env_request row)
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low
- **Impacts**: none — decline-by-default is the fixed posture (wire env is not family config). An allowlist (`LANG`, …) only matters if a consuming assembly has a concrete need.
- **Blocked on**: a concrete consumer use case requiring wire-supplied environment variables.
- **Resolution**: Not yet decidable — no use case; decline stands.
### OQ-10: The alktty exit chunk carries no signal identity — widen the chunk or map by assembly policy?
- **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §D.2, `mapping-table.md` (exit_signal row)
- **Status**: open
- **Priority**: medium
- **Impacts**: blocks the `exit-signal` fidelity detail in `mapping.md`; an in-family upstream ask (alktty wire change, third-party-upstream posture applies family-internally too).
- **Resolution**: pending. Candidates (per POC-3): alktty widens the exit chunk with the signal name (filed as an ask — alkssh never silently works around), or alkssh maps negative exit codes via assembly policy (fidelity compromise: name inferred/coarse). Also in scope for the same OQ: pipe-mode signals are pid-only vs PTY process-group scope (backend-semantics fidelity note for the session rows). Decide the *fallback* during `mapping.md`; the upstream ask stays open until alktty answers.
- **Cross-references**: OQ-08
### OQ-11: `direct-streamlocal` platform surface — compile-time or runtime decline shape?
- **Origin**: `mapping-table.md` (streamlocal row)
- **Status**: open
- **Priority**: low
- **Impacts**: blocks the `tunnel` feature's platform matrix detail (unix-only compile-out vs universal-compile runtime decline), a small contract test question.
- **Resolution**: pending — decide during `mapping.md`; default direction is runtime decline (matches fail-closed and windows-on-ssh portability) unless the unix-only compile split proves simpler.
### OQ-12: ~~Client reconnect: does the crate own forward/session restoration?~~ — resolved
- **Origin**: `reverse-runner.md` §4
- **Status**: resolved
- **Resolution**: the crate owns lifecycle primitives only — bound-port handles (forward listeners return the actually-bound port, per russh's `tcpip_forward -> u32`), explicit cancellable forward/session handles, and clean disconnects. Reconnect-and-restore policy (rebinding persisted tunnel ports per alkminer's flow) is assembly-owned. Recorded as a constraint in `client.md`; no reconnect policy is baked into the crate.
## Theme E — deferred features
### OQ-13: The `sftp` client+server halves when alkfs lands
- **Origin**: `mapping-table.md` (sftp rows), `reverse-runner.md`, POC-2 scope notes
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low (until alkforge phase 0 resumes)
- **Impacts**: the `sftp` feature ships declining from the first build (honest advertisement, decline contract tested on both sides); the alkforge CI deploy path (binary/artifact push, alkminer-precedent evidence) and the alkfs server seam are the eventual consumers.
- **Blocked on**: alkfs — family ADR-002, paused so alkforge can land; alkssh's sftp implementation follows alkfs' maturity.
- **Resolution**: Not yet decidable — the target protocol is pending. The placeholder contract (decline, tested) is in scope for the first architecture pass.
- **Cross-references**: OQ-01
## Deferred / Blocked (at a glance)
| OQ | Blocked on | Priority |
|---|---|---|
| OQ-01 | concrete relay/browser-leg consumer | medium |
| OQ-02 | (deferred(unclear)) investigation during `mapping.md` | high |
| OQ-04 | concrete OpenSSH-CA use case | low |
| OQ-09 | concrete wire-env use case | low |
| OQ-13 | alkfs landing (family ADR-002) | low (for now) |
@@ -0,0 +1,36 @@
---
id: architecture/oq-01-relay-leg-consumer
name: "OQ-01 tracker — alk/ssh relay/browser-leg consumer"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
tags: [deferred-oq, external-trigger]
---
## Description
External-trigger tracker for [OQ-01](docs/architecture/open-questions.md) — "does an `alk/ssh` relay/browser-leg consumer emerge?" (deferred(scope)). Not actionable work; tracks whether the unblocking condition has arrived.
## Work
Transition check only: when a concrete consumer use case exists — a browser-WASM route landing in an assembly (alkcall ADR-009 sketch) or a hub deployment needing SSH-in-relay — mark this task `completed` and move OQ-01 from `deferred(scope)` to `open` in `open-questions.md` (directly to `resolved` if no decision remains, which would be an ADR-001 amendment since the pin is one-way).
## Verification
- [ ] OQ-01 status in `open-questions.md` matches this task's state.
## References
- `docs/architecture/decisions/001-alkssh-alpn-pin.md`
- `docs/architecture/open-questions.md` (Theme A)
## Out of scope
Any actual relay/browser-leg design — that work would be new ADR/spec territory once the use case exists.
## Summary
> Filled on completion.
@@ -0,0 +1,35 @@
---
id: architecture/oq-04-openssh-cert-auth
name: "OQ-04 tracker — OpenSSH certificate auth use case"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
tags: [deferred-oq, external-trigger]
---
## Description
External-trigger tracker for [OQ-04](docs/architecture/open-questions.md) — whether an OpenSSH-CA-trust use case ever arrives for `auth_openssh_certificate` (deferred(scope)). Not actionable work; decline is the standing disposition (ADR-004 territory).
## Work
Transition check only: when a consuming assembly presents a concrete CA-issued-certificate requirement, mark this task `completed` and move OQ-04 to `open` for a design pass (the decline disposition itself would be revisited by ADR amendment).
## Verification
- [ ] OQ-04 status in `open-questions.md` matches this task's state.
## References
- `docs/architecture/open-questions.md` (Theme B)
## Out of scope
Any certificate-auth design.
## Summary
> Filled on completion.
@@ -0,0 +1,35 @@
---
id: architecture/oq-09-env-request-allowlist
name: "OQ-09 tracker — env_request allowlist use case"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
tags: [deferred-oq, external-trigger]
---
## Description
External-trigger tracker for [OQ-09](docs/architecture/open-questions.md) — whether a consuming assembly ever wants a wire-supplied env allowlist on session channels (deferred(scope)). Decline-by-default is standing posture.
## Work
Transition check only: when a concrete use case requiring wire-supplied environment variables arrives, mark this task `completed` and move OQ-09 to `open` in `open-questions.md` for an allowlist-shape design pass.
## Verification
- [ ] OQ-09 status in `open-questions.md` matches this task's state.
## References
- `docs/architecture/open-questions.md` (Theme D)
## Out of scope
Any allowlist design.
## Summary
> Filled on completion.
+37
View File
@@ -0,0 +1,37 @@
---
id: architecture/oq-13-alkfs-sftp
name: "OQ-13 tracker — alkfs landed (sftp implementation unblocks)"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
tags: [deferred-oq, external-trigger]
---
## Description
External-trigger tracker for [OQ-13](docs/architecture/open-questions.md) — the `sftp` feature's implementation waits for alkfs (family ADR-002, paused for alkforge). The `sftp` feature ships declining (tested decline contract on both directions) until this unblocks.
## Work
Transition check only: when alkfs reaches an implementation-ready state (its spec/implementation exists), mark this task `completed` and move OQ-13 to `open` for the sftp mapping design (client+server halves; alkforge CI artifact deploy is the consumer).
## Verification
- [ ] OQ-13 status in `open-questions.md` matches this task's state.
- [ ] The `sftp` decline contract remains tested until that point.
## References
- `docs/architecture/open-questions.md` (Theme E)
- `docs/research/reverse-runner.md` (the deploy use case evidence)
## Out of scope
Any sftp mapping design.
## Summary
> Filled on completion.