Files
alknet/docs/architecture/decisions/008-secret-service-integration.md
T
glm-5.2 2aa6363e57 docs(arch): sync specs to transport generalization sweep (ADR-064, ADR-065)
Three code commits landed a clean sweep discovered when building an
external app against the crates. This syncs the architecture specs to
match the codebase and amends the affected decisions.

ADR-064 (supersedes ADR-005): irpc was never integrated — no .rs file in
the workspace ever imported it. The wire protocol (wire.rs) is hand-rolled
length-prefixed JSON; the EventEnvelope shape was derived from the
@alkdev/pubsub TypeScript prior art (ADR-013), not from irpc. ADR-005's
premise ('irpc as the call protocol foundation') was never implemented as
stated. Superseded with a clear header; the body is kept as historical
record.

ADR-065: Connection::from_stream / from_bidi — Connection now accepts any
AsyncRead + AsyncWrite pair via ConnectionKind::Stream (yield-once
accept_bi contract: QUIC yields many streams, everything else yields one
then ConnectionClosed). Unblocks TCP+TLS, SSH channel dispatch,
WebTransport streams, and wasm streams through the same HandlerRegistry
as QUIC connections, with zero handler code changes. MockConnection /
ConnectionKind::Mock removed (tests use from_stream with sink/empty).
Stream-level Mock variants renamed to Stream (they were already generic).

Amended ADRs: ADR-003 (irpc removed from dep table), ADR-007 (from_stream
opens the server-side door; MockConnection removed), ADR-010 (TCP+TLS can
now dispatch through the registry via from_bidi; iroh 1.0 migration noted).

Updated specs: core-types.md (Connection/SendStream/RecvStream sections),
endpoint.md (TCP section, iroh note), call-protocol.md, operation-registry
(irpc Integration section replaced), overview.md, http-server.md,
webtransport.md, call README. OQ-09 (WASM) resolution amended: the
Connection door is now open via from_stream; the accept-loop runtime door
remains closed (tokio doesn't run on WASM).

See docs/research/transport-generalization/findings.md for the full trace.
2026-07-09 07:40:51 +00:00

5.7 KiB

ADR-008: Vault Integration Point

Status

Accepted

Context

alknet-vault (formerly alknet-secret) is a standalone crate with zero alknet crate dependencies and zero RPC framework dependencies (ADR-025). It provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and a direct-method-call API (VaultServiceHandle). It is already implemented and stable, pending the ADR-025 refactor to drop irpc.

The question (OQ-08) was: how does the rest of the alknet system access alknet-vault's capabilities? The options were:

  1. Call protocol exposure: Other services call vault operations through the call protocol.
  2. ALPN handler on alknet/secret: alknet-vault implements ProtocolHandler and gets its own ALPN.
  3. Direct library dependency: alknet-core or handler crates depend on alknet-vault directly, breaking its independence.
  4. CLI-embedded with call protocol exposure: The CLI binary instantiates VaultServiceHandle locally and registers vault operations in the call protocol's registry.

This is a one-way door because if alknet-vault gets pulled into alknet-core as a dependency, its independence is permanently lost. The standalone property is valuable — alknet-vault has no QUIC, no tokio runtime requirement (the handle works without it), and no alknet crate dependencies. It can be used in contexts where QUIC networking doesn't exist (CLI tools, test harnesses, WASM key derivation).

Beyond the integration point, there's a question of access patterns. The vault holds the master seed and can derive keys and encrypt/decrypt arbitrary data. This is used for:

  • Identity key derivation (SSH host keys, node identity)
  • Provider API key storage (Vast.ai, LLM providers) — encrypted at rest, decrypted on demand
  • Credential encryption for storage
  • Multi-tenant key derivation (different derivation paths per tenant)

The vault is a capability source, not a service endpoint. Operations that need provider keys don't hold a reference to the vault — they receive the derived/decrypted material through their operation context. The vault is unlocked at startup by the CLI, and the CLI injects material into operation contexts as needed.

Decision

Option 4: CLI-embedded, assembly-layer only.

The CLI binary (the alknet crate) is the integration point. It:

  1. Instantiates VaultServiceHandle locally at startup (or on-demand with Unlock/Lock lifecycle).
  2. Derives and decrypts the credentials each handler needs.
  3. Injects those credentials into handler capabilities at construction time.
  4. Other handlers access vault-derived material through their OperationContext.capabilities — they don't import alknet-vault directly and don't call vault operations over the wire.

alknet-vault does NOT get its own ALPN. Key derivation is a local operation — the master seed never crosses the network. If a remote node needs derived public keys (e.g., for identity verification), they're shared through the call protocol, not through direct vault access.

The vault is accessed at the assembly layer, not by individual handlers. The CLI (or a configuration middleware it sets up) is the only component that talks to the vault directly. Derived keys and decrypted credentials are injected into operation contexts — handlers receive the material they need, not a vault reference.

No vault operations are registered in the call protocol. The vault is not exposed over the wire. This is the mechanism this ADR described in prose ("derived keys and decrypted credentials are injected into operation contexts at the assembly layer"); ADR-014 specifies it as a one-way door with explicit constraints. See ADR-014.

This is analogous to the reverse-proxy admin key pattern (ADR-028 in the reverse-proxy project): the proxy reads the key file once at startup, hashes it, and individual handlers never see the file. Here, the CLI unlocks the vault once at startup, and individual handlers receive the results of vault operations through their contexts.

Consequences

Positive:

  • alknet-vault remains fully standalone — no QUIC dependency, no tokio runtime requirement for the handle
  • Key derivation and encryption are local-only by default — the master seed never leaves the node
  • Remote access to public key material (not secrets) flows through the existing call protocol — no separate ALPN needed
  • The CLI binary is the single integration point — clean dependency graph, no circular dependencies
  • The VaultServiceHandle is used in-process with zero serialization overhead — direct method calls, not irpc messages
  • Test harnesses can use VaultServiceHandle directly without any QUIC infrastructure
  • Access pattern is clear: vault → CLI → operation contexts, not vault ← handlers

Negative:

  • Handlers that need keys must receive them through their operation context — this requires the CLI or call protocol to mediate
  • The CLI binary has a larger dependency tree since it imports both alknet-call and alknet-vault (expected: the CLI assembles everything)
  • If the call protocol is not yet running when a handler needs a key, the handler must wait for initialization (mitigated: the CLI starts VaultServiceHandle before accepting connections)

References

  • ADR-003: Crate decomposition (alknet-vault is standalone)
  • ADR-005: irpc as call protocol foundation (superseded by ADR-064 — irpc was never integrated; the vault no longer uses irpc — see ADR-025)
  • ADR-009: One-way door decision framework
  • ADR-014: Secret material flow and capability injection (specifies the mechanism this ADR described in prose)
  • ADR-025: Vault local-only dispatch (dropped irpc from the vault; direct method calls only)
  • OQ-08: Secret service integration point (resolved by this ADR, refined by ADR-014)
  • alknet-vault implementation: crates/alknet-vault/