Renames the written-usage rule into practice across the contract surface: docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files, taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the .opencode/agents/ prompts. Every replacement is a sense-preserving rewrite (colons, semicolons, commas, parentheses, sentence splits, varied), never a mechanical swap; no spaced hyphens were substituted. Enum fields, frontmatter, tables, wire data, and cited identifiers survive verbatim; en dashes inside numeric ranges keep their meaning. Verification: taskgraph validate (43 tasks), repo grep clean (single exempt hit: the verification command's own search pattern in AGENTS.md), cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
17 KiB
status, last_updated
| status | last_updated |
|---|---|
| reviewed | 2026-10-28 |
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).
All thirteen questions are resolved. The tracker holds resolutions only; the genuinely-upstream items it surfaced (the PTY exit limit, the alkvault zeroization gap, the alkfs dependency) are noted at the end and carry their work where their owners are.
Theme A: wire & family topology
OQ-01: Does an alk/ssh relay/browser-leg consumer emerge? (resolved)
alk/ssh relay/browser-leg consumer emerge?- Origin: mapping-table.md §topology,
vision.mdscope Q1 - Status: resolved; ADR-001 is the entire decision surface; what remained was a forecast about the world, not an alkssh decision.
- Resolution: no relay/browser-leg consumer is in scope and no crate behavior is gated on one. ADR-001 decided everything decidable now: the
alk/sshpin's raw-stream contract (§1), payload-blind relay as part of the contract (§2), the phase-1 edge-termination-only scope (§3), and future-leg compatibility by construction (§4). Whether a consumer ever materializes is an assembly/family use-case question: a future leg arrives via its own ADR referencing ADR-001's already-fixed contract and would exerciseclient.md's accept surface in a new role. Nothing in the crate waits on this. (Door-type note retained for the record: the pin is one-way; consumer existence was never an architecture decision.) - 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)? (resolved)
- Origin: poc-3-mapping-bakeoff.md §Stage B.3,
mapping-table.md§notes - Status: resolved. Decided by ADR-006 point 3, then simplified by the ADR-003 §1a amendment: the
dynamic-forwardfeature retired (no consumer is ever the family-native SOCKS-in-channel client; vanilla-Dis client-side SOCKS + plaindirect-tcpip; the family-Ddoor is alksocks composed locally at the assembly), so the detect lost its owner. Session dispatch is parse-only; request-less session channels decline. POC-3 stage B survives as the feasibility record for re-adding a remote SOCKS door via explicit ADR. - Cross-references: ADR-006, ADR-003 §1a
OQ-03: Username disposition (resolved)
- Origin: 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@hostworks with the same identity). Whether a username string appears in logs is deployment policy, outside the crate. Rides as the recorded contract in theauth.mdspec / ADR-004.
OQ-04: OpenSSH certificate auth, is there ever a CA-trust use case? (resolved)
- Origin:
mapping-table.md(auth rows), poc-1-auth-bridge.md OQ seed 4 - Status: resolved. The decline is the settled disposition; the "is there ever" remainder was a forecast.
- Resolution:
auth_openssh_certificateis declined unconditionally (ADR-004 decision 3: explicit reject implementation, contract-tested, POC-1-proven). There is no CA-trust story in the family identity model: exactly one SSH-shaped resolution path exists (verified key fingerprint →IdentityProvider::resolve_from_fingerprint→Identity). A certificate trust model would first require the family identity model to gain one upstream (alkcall identity territory) and would then enter alkssh as a new ADR, since an auth-posture change is wire-visible. Nothing in the crate waits on this; no CA use case exists. - Cross-references: ADR-004, auth.md §out-of-scope
Theme C: auth, identity & key custody
OQ-05: What is the client-side host-key pin store? (resolved, policy-seam shape)
-
Origin: auth-bridge.md §Host keys,
reverse-runner.md§5 -
Status: resolved. The seam is decided; where learned pins persist is assembly detail (the crate takes a policy, not a pin store).
-
Resolution: two basic client cases, one seam:
- Known beforehand: trust anchors exist. Family-built runners mint their host key at assembly time, so the expected fingerprint is known before first contact: assembly-supplied pins, hard decline on mismatch (no soft failure).
- Unknown beforehand: vanilla
sshdon container fleets the assembly didn't provision; no anchor exists until first contact. The crate surfaces the presented host key (host, port, fingerprint) to an assembly-providedHostKeyPolicythat decides: prompt-and-record, provider-metadata match, learn-and-record, or accept-and-continue (the ephemeral-fleet shape, see below). The OpenSSH "add to known_hosts?" moment becomes an assembly policy rather than crate behavior.
Crate rules that make this family-shaped: the crate's default policy is fail-closed (reject unknown, hard-decline mismatch; russh's own reject-all default, kept); the crate never decides trust itself and never hard-codes a trust model, so accept-all is not a built-in crate option, but a permissive case-2 policy is the assembly's to choose where its fleet model calls for it (alkminer's accept-all for rental fleets is that shape, made deliberately: fresh container per rent → fresh host key, so no pin can exist beforehand and the security anchor is hub-held per-instance dial keys plus what rides the tunnels, not host pins). The contract lands in the
auth.mdspec (host-key policy section) andclient.md's dial surface. -
Cross-references: OQ-07,
reverse-runner.md, poc-2-vault-keys.md §5 (host keys ride the vault model)
OQ-06: One vault, many roles? (resolved)
- Origin: 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? (resolved, at this level)
decrypt plain-String zeroization gap closed at the vault or at the adapter?- Origin: poc-2-vault-keys.md §2 + OQ seed 1
- Status: resolved. The alkssh-level answer was the adapter-side wrap; the vault-side improvement belongs upstream, not here. Superseded upstream 2026-11-04 (alkvault 0.2.0): the ask shipped,
decryptreturnsZeroizing<String>, zeroizing the buffer on every path including the error one, and therotatepath never holds a bare plaintext either. - Resolution: ADR-005 decision 3 was the contract on this path: the adapter owned a
Zeroizing<String>wrap at the seam (POC-2-proven; bounds the plaintext from adapter-takeover onward; ssh-key zeroizes the decoded key bytes on drop). The remaining unfenced pre-wrap window was alkvault's API shape to close; the ask was filed in alkvault's own tracker (downstream/family-space concern; alkforge and every secrets-bearing crate benefits, not just alkssh). alkssh did not carry it, did not gate on it, and ADR-005 recorded only the adapter obligation. With alkvault 0.2.0 the window is closed upstream: the adapter-side wrap is dropped (a double wrap would be dead posture),decrypt_and_decodeconsumes the vault's zeroized return directly, and ADR-005's decision-3 pointer is amended (the predicted one-line doc touch, exactly). - Cross-references: ADR-005 §3, OQ-05
Theme D: session-request mapping details
OQ-08: Shell command convention for shell_request (resolved)
shell_request-
Origin:
mapping-table.md(shell row), poc-3-mapping-bakeoff.md OQ seed 3 -
Status: resolved; assembly-supplied serve-config field, no crate default.
-
Resolution: the shell convention is assembly-supplied serve configuration: the crate bakes no default, and each unset field means its request declines with a clean error (never a silent fallback). Grounding for the shape: alktty's contract requires a non-empty
cmdvector (there is no pass-through-absence seam atdrive_session), the wire supplies nothing (shell_requestcarries no command), and the crate is not an sshd and has no account/passwd model from which "the user's shell" could be derived, so a crate-level default (bash,sh) would be deployment policy smuggled into the crate (the composability boundary,overview.md). Two fields, each governing its own row:- shell argv: what
shell_requestruns verbatim asNegotiateRequest.cmd. - exec wrapper argv (e.g.
["/bin/sh", "-c"]): what a non-gitexec_requesttranslates to. The wire's single command string is appended as the wrapper argv's final element (wrapper ++ [cmd]), preserving vanillassh host "cmd"quoting/expansion semantics; command strings are never naively split (that would break quoting). The wrapper form is platform-neutral: the assembly configures whatever its fleet's shell is.
Unset field → its row declines; a
sessionbuild with no configured shell serves neither shell nor exec (fail-closed, no half-capability where the feature is on). The git row is unaffected (parse toalkgitfirst, since it never needs a shell).cwd/envinNegotiateRequestare assembly-supplied serve-config values, never wire-derived (env_requeststays declined, OQ-09). Recorded onmapping.md'sshell_request/exec_requestrows. - shell argv: what
-
Cross-references: OQ-09, mapping.md
OQ-09: env_request policy, is an allowlist ever wanted? (resolved)
env_request policy, is an allowlist ever wanted?- Origin:
mapping-table.md(env_request row) - Status: resolved. Decline-by-default is the fixed contract; no allowlist ships.
- Resolution:
env_requestis declined unconditionally. Wire-supplied environment is not family config: session env comes from assembly serve configuration (NegotiateRequest.env), never from the wire. The only open shape was whether an allowlist ships; it does not, and a future allowlist would be a new ADR (a serving capability is wire-visible) on a concrete consumer need, of which none exists. - Cross-references: OQ-08, mapping.md (
env_requestrow)
OQ-10: SSH exit-signal fidelity over the alktty exit-code convention (resolved: closed as contract + upstream constraint)
exit-signal fidelity over the alktty exit-code convention- Origin:
mapping-table.md(exit_signal row), poc-3-mapping-bakeoff.md §D.2. Premise corrected 2026-10-27 (with the alktty side): the POC seed's "the exit chunk carries no signal identity (alktty might widen the chunk)" framing was wrong; the negative code is the signal encoding. Integrated into the seed rather than argued after the fact. - Status: resolved. As asked, this was never a decision to make: the convention alkssh consumes does not change (alktty's exit-chunk convention, alktty ADR-004 / alknet ADR-055 lineage; no widening proposed or justified). "Easier for alkssh" is not a tty/pty-grounded justification, and any widening ripples
ControlMessage::Exit's parsed shape, the backend API, and that ADR lineage, so it needs its own alktty ADR. What the entry surfaced was (a) a derivation contract on alkssh's side and (b) an upstream fidelity limitation, both now recorded where they constrain. - Resolution:
- The derivation contract is formalized as
mapping.md§"Exit chunk → SSH exit reporting" (the contract behind theexit_status/exit_signalrows):code ≥ 0→exit-status(N); negative−Non the Unix pipe path →exit-signal(name)via a server-side Unix signal-number→name table;−1→exit-status(255)and never a signal claim (the sentinel collides with SIGHUP; fabricatingexit-signal("HUP")for a wait failure invents a signal that never happened); non-Unix negatives → the−1rule;core_dumpednever asserted. POC-3 §D.2 observed the pipe-path derivation live (−15→exit-signal("TERM")). - The upstream constraint (superseded 2026-11-04, alktty 0.8.2): the PTY path used to lose the signal number (portable-pty 0.9 reaps a signal-terminated Unix child as a plain code 1 through its trait surface; verified against its source). alktty 0.8.2 retired that constraint consumer-input-driven (alktty review 004, this entry's bookkeeping): its Unix PTY waiter now reaps the direct child via
waitpidand decodes signal deaths as−N, so the convention holds on both paths and the PTY-row signal-terminated session reportsexit-signal(name)with no alkssh contract change (the negative code is consumed verbatim;mapping.mdrule 5 amended). The-1sentinel collision (SIGHUP vs wait-failure) remains on the pipe path and gains the cancel path; the rule-3 reading stands unchanged.
- The derivation contract is formalized as
- Cross-references: OQ-08, mapping.md (derivation contract +
exit_signal/exit_status/signalrows), ADR-006, alktty ADR-004 / alknet ADR-055
OQ-11: direct-streamlocal platform surface, compile-time or runtime decline shape? (resolved)
direct-streamlocal platform surface, compile-time or runtime decline shape?- Origin:
mapping-table.md(streamlocal row) - Status: resolved: universal compile, runtime decline.
- Resolution:
direct-streamlocalcompiles wherevertunnelcompiles; the serve path is runtime-Unix-gated (on Unix it serves with theSubstrateAddr::Pathtreatment, on every other platform it declines with a clean SSH open-failure, never a half-open). This wins over a unix-only compile split: no per-platform feature fork (the feature graph stays flat and platform-independent, ADR-003), the decline matrix stays uniform across builds, and the decline path is contract-tested in every build like every other decline (fail-closed posture). The research row's "compile-time feature + runtime decline" hybrid collapses to the runtime-decline half; no platform-conditional feature is needed. - Cross-references: ADR-003, mapping.md (
direct-streamlocalrow + the-Rlisten flow)
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 inclient.md§Lifecycle primitives; no reconnect policy is baked into the crate. - Cross-references: client.md,
reverse-runner.md
Theme E: future features
OQ-13: The sftp client+server halves when alkfs lands (resolved)
sftp client+server halves when alkfs lands- Origin:
mapping-table.md(sftp rows),reverse-runner.md, POC-2 scope notes - Status: resolved. The alkssh-side answer is fully decided; the remainder belongs to alkfs, not to this tracker.
- Resolution: the
sftpfeature ships as the tested decline placeholder in both directions from the first build: the feature exists in the graph (ADR-003), advertisement is honest, and the decline contract is tested on both sides (mapping.md/client.md). That is the shipped behavior for as long as alkfs is pending, and it needs no further decision. What remained "pending" was the eventual alkfs-protocol mapping, a decision downstream of alkfs' design (family ADR-002; alkssh follows alkfs' maturity): alkssh writes that mapping and its ADR when the target protocol exists to map, the same way every other row's mapping was decided before implementation. No alkssh decision waits on this; there is no alkssh-side sftp work. - Cross-references: OQ-01, ADR-003, client.md §op rows, family ADR-002
Upstream watch items (not OQs)
Recorded so the dependency is visible, with the work carried where their owners are:
- alkfs landing: the
sftpplaceholder's eventual replacement (family ADR-002); alkssh writes the sftp mapping + ADR when the protocol exists (OQ-13). PTY-path exit fidelity: closed upstream (alktty 0.8.2, review 004's resolution): the PTY waiter now reports signal deaths as−N; the derivation contract needed no alkssh change (mapping.mdrule 5 amended; OQ-10).alkvault: closed upstream (alkvault 0.2.0, review 001's resolution; also closes theZeroizing-outdecryptrotatewindow and zeroizes the error path): the adapter seam wrap is dropped, ADR-005 decision 3 + OQ-07 amended as the predicted one-line doc touch.