Commit Graph
61 Commits
Author SHA1 Message Date
glm-5.2 44f221e8cc feat(typedef/crate-init): initialize alknet-typedef crate skeleton
Created crates/alknet-typedef/ with Cargo.toml depending on jsonschema 0.46
(default-features = false for WASM-cleanliness) and serde_json with
preserve_order. Added src/lib.rs with module declarations for all 9 modules
and skeleton source files for each. Added crate to workspace members list.

Verified: cargo check, clippy -D warnings, and build --workspace all succeed.
Dependency tree confirmed free of tokio/reqwest/rustls (WASM-clean).
2026-07-21 09:54:05 +00:00
deepseek-v4-pro 4fc6854846 chore(call): prune connect, TLS helpers, CallCredentials, and dead paths (Phase 5)
- Remove RemoteIdentity, CallCredentials, ClientError, connect() from call_client.rs
- Remove all TLS helpers (build_quinn_client_config, build_client_auth,
  select_server_verifier, FingerprintPinVerifier, RawKeyClientCertResolver,
  NoClientCertResolver, Ed25519SigningKey, cert/key loaders)
- Remove credentials_auth_token dead path from from_call.rs
  (OpSummary field, build_bundles, make_forwarding_handler,
  make_streaming_forwarding_handler, build_forwarded_payload)
- Remove 2 dead-path tests (build_forwarded_payload_sets_auth_token,
  streaming_forwarding_handler_sets_auth_token)
- Update mod.rs re-exports to only CallClient
- Remove quinn feature and TLS deps from Cargo.toml
- Delete tests/two_node_call.rs (used connect() + CallCredentials)
- alknet-call is now a pure protocol crate
2026-07-17 14:01:51 +00:00
deepseek-v4-pro a0dbe4fd3c chore(core): prune endpoint.rs and heavy deps (Phase 4)
- Delete crates/alknet-core/src/endpoint.rs (1606 lines)
- Remove pub mod endpoint from lib.rs, update doc comment
- Remove rcgen, rustls-pemfile, rustls-acme deps from Cargo.toml
- Remove acme feature
- Update fingerprint.rs doc comment references to alknet-endpoint/alknet-tls
2026-07-17 13:21:54 +00:00
deepseek-v4-pro 82ee37e206 feat(client): implement alknet-client crate — AlknetClient, dial methods, error type, SOCKS5 proxy
- Initialize alknet-client crate with Cargo.toml, deps, feature flags
- Implement ClientDialError enum with 5 variants (TlsConfig, Connect, Handshake, NoTransport, Proxy)
- Implement AlknetClient struct with builder methods (with_quinn, with_tcp_tls, with_iroh, with_socks5_proxy)
- Implement dial_quic — QUIC dial via quinn, producing a Connection
- Implement dial_tcp_tls — TCP+TLS dial via tokio-rustls, producing a Connection
- Implement dial_iroh — iroh dial, producing a Connection
- Implement SOCKS5 proxy support — Socks5ProxyConfig, Socks5Credentials, Socks5UdpSocket, proxy integration
- Add into_rustls_config() to TlsClientConfig in alknet-tls
- Add alknet-client to workspace members

Tasks: client/crate-init, client/error-type, client/client-core,
client/dial-quic, client/dial-tcp-tls, client/dial-iroh, client/socks5-proxy
2026-07-17 12:42:47 +00:00
deepseek-v4-pro 5467c30892 feat(endpoint): add tests and mark tasks 1-7 as completed
- Add 7 registry tests (handler_registry_*) to registry.rs
- Add 4 dispatch tests (build_auth_context_*, dispatch_decision_logic_*) to dispatch.rs
- Add 6 endpoint tests to endpoint.rs:
  - debug_for_alknet_endpoint_is_implemented_without_panicking
  - endpoint_constructs_with_iroh_raw_key_identity (adapted for new API)
  - iroh_endpoint_runs_accept_loop_and_shutdown (adapted for new API)
  - with_iroh_sets_field (replaces has_iroh_identity_true_for_raw_key)
  - without_iroh_field_is_none (replaces has_iroh_identity_false_for_x509)
  - endpoint_works_without_iroh (replaces has_iroh_identity_false_when_no_identity)
- Add async-trait as dev-dependency
- All 17 tests pass with iroh feature
- All 8 tests pass without features
- Workspace tests all pass (no breakage)
- Mark tasks 1-7 as completed
2026-07-17 10:53:25 +00:00
deepseek-v4-pro 2755b995c6 feat(endpoint): initialize alknet-endpoint crate with all modules
- Create crates/alknet-endpoint/ with Cargo.toml, feature flags (quinn, iroh, tcp, acme)
- Implement HandlerRegistry (registry.rs) — extracted from core/endpoint.rs
- Implement AlknetEndpoint (endpoint.rs) — fresh build against ADR-083 shape
  - new() takes no StaticConfig, no TLS config
  - with_quinn/with_iroh/with_tcp_tls builder methods
  - run() spawns accept loops for each active transport
  - shutdown() is infallible (no Result)
  - No EndpointError type
- Implement dispatch (dispatch.rs) — shared dispatch_connection free function
  - Transport-agnostic: takes pre-extracted alpn, fingerprint, remote_addr
  - ACME guard (acme-tls/1) behind acme feature
  - build_auth_context resolves identity from fingerprint
- Implement accept loops:
  - accept/quinn.rs — quinn accept loop with ALPN + fingerprint extraction
  - accept/iroh.rs — iroh accept loop with ALPN negotiation + fingerprint
  - accept/tcp_tls.rs — TCP+TLS accept loop (new code, not in old endpoint.rs)
- Add alknet-endpoint to workspace members
- All feature combos compile cleanly (no features, quinn, iroh, tcp, all three)
- Workspace cargo check passes
2026-07-17 10:48:39 +00:00
deepseek-v4-pro ad4d9446d0 chore: update Cargo.lock for alknet-tls crate dependencies 2026-07-17 10:10:03 +00:00
glm-5.2 acd049e8b2 feat(core): migrate iroh 0.35 -> 1.0.2
Cargo.toml: iroh = { version = "1.0", optional = true, default-features = false, features = ["tls-aws-lc-rs"] }

6 API changes in endpoint.rs + types.rs:
- Endpoint::builder() -> builder(presets::Minimal) (mandatory crypto_provider)
- SecretKey::from_bytes() now returns SecretKey directly, not Result
- SecretKey::generate() takes no rng arg (OsRng internal)
- Connection::remote_node_id() -> remote_id() (returns PublicKey, not Result)
- Connection::alpn() returns &[u8] directly, not Option<&[u8]>
- tls-aws-lc-rs feature matches quinn path's existing crypto provider

Unblocks alknet-blobs (depends on iroh-blobs 0.103 which pulls iroh 1.0
transitively). See docs/research/iroh-update/findings.md for the full trace.
2026-07-09 06:58:07 +00:00
glm-5.2 45506ed77b test(tty): end-to-end integration tests for LocalTtyBackend + drive_session
Add 18 integration tests in crates/alknet-tty-local/tests/ exercising
the full stack (LocalTtyBackend + TtyAdapter::drive_session over
tokio::io::duplex, real commands), validating the two crates work
together through the TtyBackend trait seam.

Tests live in alknet-tty-local (not alknet-tty) per the feature-gate
deviation: the cyclic dependency alknet-tty → alknet-tty-local →
alknet-tty prevents alknet-tty from depending on alknet-tty-local, so
the integration tests live in the crate that depends on both. Unix-only
tests (signal, process-group) use #[cfg(unix)].

PTY mode (8): happy path echo, interactive cat round-trip, resize,
SIGINT signal, process-group signal (REQ-TTY-02), stdin EOF sentinel,
cancel cleanup (ADR-056), exit-chunk-is-last (ADR-055).

Pipe mode (6): happy path echo, separate stderr, SIGTERM signal,
cancel cleanup, resize no-op, stdout chunk + sentinel.

Negotiation errors (5): unknown_backend, malformed_negotiation (bad
JSON, carriage != raw, empty cmd), allocate_failed (nonexistent
binary).

A shared test harness (tests/common/mod.rs) provides the client-side
wire protocol helpers: write_negotiation, write_chunk, write_control,
try_read_chunk, read_chunk_timeout, read_error_frame (asserts the
0x00 first-byte framing-disambiguation invariant), read_until_exit,
assert_no_more_chunks, close_write_half, plus spawn_session which
wires drive_session over a duplex pair with a test identity carrying
tty:open.

Verification: cargo test -p alknet-tty-local (42 tests pass), cargo
clippy -p alknet-tty-local -- -D warnings (clean), cargo fmt --check
-p alknet-tty-local (clean).
2026-07-07 23:48:12 +00:00
glm-5.2 0ff0ceeac3 feat(tty-local): implement LocalTtyBackend (TtyBackend) branching on terminal Some/None
Implements the wiring task: LocalTtyBackend::allocate() dispatches to
pty::allocate_pty when TtyParams.terminal is Some (PTY mode, stderr None)
and pipe::allocate_pipe when None (runner mode, stderr Some). Validates
non-empty cmd, ignores backend_params (local backend has no backend-
specific selector fields), and resource_id() returns None (local backend
creates its own resource — process). Adds async-trait dependency.
2026-07-07 23:03:05 +00:00
glm-5.2 6dce987ed4 Merge feat/tty-local/pipe-mode: resolve Cargo.toml/Cargo.lock conflicts with pty-mode (both tokio-stream and tokio-util needed) 2026-07-07 22:58:40 +00:00
glm-5.2 c76b6aab7a feat(tty-local): implement pipe mode — tokio::process spawn, PipeControl, ADR-056 kill guard
Implements the runner case (terminal: None, no PTY) in pipe.rs:
- allocate_pipe spawns via tokio::process::Command with Stdio::piped(),
  returning a TtyHandle with separate stdout/stderr (stderr=Some).
- stdin: ChildStdin boxed as Box<dyn AsyncWrite + Send + Unpin>.
- stdout/stderr: ChildStdout/ChildStderr wrapped via tokio_util::io::ReaderStream
  into Pin<Box<dyn Stream<Item = Bytes> + Send>> (errors map to stream EOF).
- exit_code: PipeExitFuture holding the Child; poll drives Child::wait() and
  disarms the guard on resolve; Drop calls Child::start_kill() on cancel
  (ADR-056 spec-compliant kill-on-Drop, not kill_on_drop(true) alone).
- control: PipeControl (no-op resize, libc::kill(pid, sig) signal on Unix,
  SIGKILL fallback for unknown names; non-Unix logs and relies on Drop guard).
  Doc comment documents the no-process-group limitation of pipe mode.

Adds tokio-util (io) and tokio-stream (dev) deps. 9 unit tests cover the
happy path, stdin round-trip, separate stderr, SIGTERM signal, cancel
cleanup (asserts child killed via kill(pid,0)), resize no-op, unknown
signal SIGKILL fallback, pid recording, and empty-command error.
2026-07-07 15:04:30 +00:00
glm-5.2 9f8d07fc25 feat(tty-local): implement PTY mode — three-thread bridge, PtyControl, REQ-TTY-02, ADR-056 kill guard
Implement allocate_pty in src/pty.rs: portable_pty openpty + spawn as
session leader (set_controlling_tty(true)), with the three-thread
blocking→async bridge (REQ-TTY-01): reader → mpsc<Bytes> with
zero-length EOF sentinel, writer draining mpsc<StdinCmd> (Bytes/Eof),
waiter → oneshot<i32>. TtyHandle.stdout is ReceiverStream<Bytes>,
stdin is a StdinSink AsyncWrite wrapper over mpsc::Sender<StdinCmd>
(parks a reserve+send future when the channel is full), stderr is None
(PTY merges), exit_code is LocalExitFuture.

PtyControl implements TtyControl: resize via MasterPty::resize; signal
via libc::kill(-pgid, sig) with kill(pid, sig) fallback (Unix, REQ-TTY-02)
using alknet_tty::signal_from_name; unknown names and non-Unix fall back
to ChildKiller::kill (SIGHUP).

LocalExitFuture wraps the oneshot::Receiver<i32> + a ChildKiller kill
guard (ADR-056). poll delegates to the receiver and disarms the guard
on Ready (no-op Drop on happy path); Drop on cancel calls
ChildKiller::kill (SIGHUP) — best-effort; the waiter thread reaps.

Bump portable-pty 0.8 → 0.9 to match the POC API (MasterPty: Send).
Add tokio-stream, serde_json (dev), tempfile (dev) deps.

Tests: happy path (echo), stdin round-trip (cat), resize, signal INT
kills child, signal reaches process group (bash -c "sleep 60"),
cancel cleanup on drop, unknown signal falls back to ChildKiller.
2026-07-07 14:56:35 +00:00
glm-5.2 2022a5496a feat(tty-local): initialize alknet-tty-local sibling crate (ADR-054)
Create crates/alknet-tty-local with Cargo.toml (alknet-tty workspace dep,
portable-pty, libc unix-only, tokio, bytes, futures-core, tracing,
thiserror), src/lib.rs with module declarations and LocalTtyBackend
re-export, and skeleton module files (pty, pipe, backend) with doc
comments and TODO markers. Add the crate to the workspace members list.
2026-07-07 14:16:10 +00:00
glm-5.2 8b8de61186 feat(tty): implement ControlMessage enum and signal_from_name
Port the POC's control channel schema (alknet-tty-poc/src/control.rs)
into crates/alknet-tty/src/control.rs: the ControlMessage tagged enum
(Resize/Signal/Eof/Exit) with snake_case type tag, to_json/from_slice
helpers, and the Unix-only signal_from_name mapping the 9 supported
signal names to libc numbers. Unknown type tags return a serde_json
error; the adapter (later task) ignores that error per the wire spec's
extensibility policy. Adds libc as a target.'cfg(unix)' dependency.

Refs: docs/architecture/crates/tty/tty-wire.md §"Control Channel"
Refs: docs/architecture/decisions/055-exit-code-on-control-chunk.md
2026-07-07 13:51:32 +00:00
glm-5.2 da6333c206 feat(tty): initialize alknet-tty crate skeleton
Create crates/alknet-tty with Cargo.toml (alknet-core path dep, tokio,
bytes, futures-core, tokio-stream, serde, serde_json, async-trait,
tracing, thiserror — no portable_pty/bollard/russh/alknet-call per
ADR-057), the `local` feature gate (dep wiring deferred to
tty/local-feature-reexport), src/lib.rs with the crate doc comment and
module declarations, and skeleton modules (wire, control, negotiation,
backend, adapter) with doc comments and TODO markers. Add the crate to
the workspace members list.

cargo check, clippy -D warnings, and fmt --check all pass.
2026-07-07 13:46:11 +00:00
glm-5.2 21c4f7d252 feat(http): implement ADR-051 YAML input for from_openapi — from_yaml + JSON-first from_str
Adds OpenAPISpec::from_yaml and OpenAPISpec::from_str alongside the
existing from_json. from_str is JSON-first, YAML-fallback (ADR-051 §2):
a valid JSON doc parses through serde_json (stricter grammar, immune to
YAML-specific coercion); only on JSON failure does it fall back to the
YAML parser. yaml_serde 0.10 (resolves to 0.10.4, latest stable) is the
maintained official-YAML-org fork of the deprecated serde_yaml; not
feature-gated per ADR-051 §3 (YAML OpenAPI is first-class).

Note: ADR-051 §2's stated YAML 1.1 boolean-coercion rationale does not
hold for yaml_serde 0.10 (YAML 1.2 core schema — yes/no/on/off are
strings). The JSON-first rule is retained per the Accepted ADR as a
defensive default; tests document the actual 0.10.x behavior with
explicit comments. The ADR rationale should be amended separately.

256 tests pass; clippy + fmt clean.
2026-07-06 13:15:53 +00:00
glm-5.2 3702da1aee feat(http): implement /healthz raw route and stealth decoy fallback
GET /healthz: raw route, no auth, no OperationContext, returns 200 OK
with plain-text 'ok' (ADR-036). Decoy fallback for unknown paths via
DecoyConfig: fake nginx 404 (default), static site serving, or redirect.
Decoy does not leak alknet presence (no alknet headers/format). Custom
routes take precedence over decoy (decoy is fallback only). Wire real
handlers into HttpAdapter router replacing placeholder 501s.
2026-07-01 18:40:01 +00:00
glm-5.2 b313dcbf20 feat(http): implement HttpAdapter (ProtocolHandler for h2/http1.1, axum over QUIC)
Wires the axum Router (gateway endpoints + /healthz + /openapi.json + MCP +
custom routes via extra_routes merge ADR-046) and drives hyper's HTTP/1.1 or
HTTP/2 connection driver over a single QUIC bidirectional stream. The
QUIC-to-hyper bridge wraps the (SendStream, RecvStream) pair as a
TokioIo-compatible duplex and feeds it to hyper-util's auto Builder (which
auto-detects HTTP/1.1 vs HTTP/2). h3 ALPN is not registered (ADR-044).

Route handlers, healthz/decoy logic, openapi.json, the MCP route, and the WS
upgrade handler are wired as 501 Not Implemented placeholders for their
respective tasks. The router state holds Arc<OperationRegistry> +
Arc<dyn IdentityProvider>; the router is built once at construction and
cloned per connection (cheap Arc clone). DecoyConfig defaults to NotFound.

Adds hyper-util dependency (server, service, tokio features).
2026-07-01 18:07:56 +00:00
glm-5.2 081fc911ef feat(http): implement shared HTTP client (ClientWithMiddleware + retry + Retry-After, OQ-40)
Adds SharedHttpClient wrapping ArcSwap<ClientWithMiddleware> with a
RetryTransientMiddleware + inlined RetryAfterMiddleware stack.
HttpClientConfig covers pool, timeout, retry policy, and optional CA
bundle/client cert. reload() rebuilds and swaps via ArcSwap. No env-var
reads; credential injection is per-request, not at construction.
2026-07-01 17:20:49 +00:00
glm-5.2 c2e6ba5b96 feat(http): initialize alknet-http crate with module skeleton
Add crates/alknet-http with Cargo.toml, src/lib.rs, and the five
subsystem modules (server, gateway, client, adapters, websocket) per
ADR-039 (server + client host colocated). The mcp feature gate pulls in
rmcp with streamable HTTP transport features only (ADR-037 — no stdio);
h3/WebTransport is absent (deferred per ADR-044). alknet-core and
alknet-call use workspace path deps. The crate is added to the workspace
members list.
2026-07-01 16:41:14 +00:00
glm-5.2 c106f4a37b feat(call): wire CallClient TLS client-auth and server cert verifier selection (call/call-client-verifier-selection)
Replace AcceptAnyServerCertVerifier (a security hole for X.509) with
verifier selection by PeerEntry presence (ADR-034 §3, OQ-29):

- build_client_auth presents the Ed25519 key as an RFC 7250 raw public
  key client cert (replaces with_no_client_auth), activating the
  PeerEntry fingerprint -> peer_id resolution path on quinn.
- select_server_verifier: Some(fingerprint) -> FingerprintPinVerifier
  (fingerprint match for known peers); None -> WebPkiServerVerifier
  (CA verification for public X.509 endpoints). None + Ed25519 raw key
  fails closed at handshake (no CA to fall back to).
- FingerprintPinVerifier matches ed25519:<hex> (raw key extraction) and
  SHA256:<hex> (DER hash); verifies handshake signatures via
  verify_tls13_signature_with_raw_key / verify_tls12/13_signature.
- Extract shared fingerprint logic into alknet_core::fingerprint (pub
  module) reused by endpoint (server-side) and call_client (client-side).
- remote_identity: None is load-bearing (not defaulted to placeholder).
- Integration tests updated to pin the self-signed server cert
  fingerprint (the known-peer path).
2026-06-28 22:24:09 +00:00
glm-5.2 4bf897f5ab feat(call): CallClient + shared dispatch loop + peer-scoped default-deny (ADR-017, ADR-028)
The #1 gap in alknet-call: the outbound connection opener. Every downstream
consumer (runner, container service, bilateral exchange, NAPI, agent
cross-node dispatch) is blocked on it.

Shared dispatch loop (ADR-017 §1 — the architectural commitment that keeps
CallClient from becoming a parallel protocol implementation):
- Extracts the accept-path dispatch (sweeper, accept_bi loop, handle_stream,
  dispatch_requested, build_root_context, compose_root_env, fail_all on
  close) out of CallAdapter into a new protocol/dispatch.rs Dispatcher struct.
  Both CallAdapter::handle and CallClient::connect produce a CallConnection
  and hand it to Dispatcher::run_loop — the loop is genuinely shared
  (refactored, not duplicated).
- CallAdapter keeps its public API and test-facing wrappers (pub(crate),
  #[cfg(test)]-gated) that delegate to the Dispatcher.

Peer-scoped default-deny (ADR-028 — the one-way-door security dimension):
- RemoteFilter { trusted_peer: bool } on the Dispatcher. In default-deny
  mode (CallClient::new), an incoming call to an op with remote_safe: false
  returns NOT_FOUND *before* any capability material reaches the handler —
  a remote peer's call must not populate OperationContext.capabilities from
  the local registration bundle unless the op is explicitly remote-safe
  (ADR-028 Context). Trusted-peer mode (CallClient::trusted_peer, explicit
  opt-in) bypasses the filter.
- The accept path (CallAdapter) uses RemoteFilter::trusted() by convention: a
  direct QUIC client is not a filtered CallClient peer in the ADR-028 sense.
- OperationRegistry::list_operations_peer_scoped(trusted_peer) +
  services_list_handler_peer_scoped for the CallClient's services/list
  serving path (ADR-028 Assumption 2: a peer should not see ops it cannot
  call, so discovery and dispatch filters agree).

CallClient (src/client/call_client.rs):
- CallClient { registry, identity_provider, trusted_peer: bool }.
- new() default-deny; trusted_peer() explicit opt-in (ADR-028 §3).
- connect(addr, CallCredentials) dials QUIC on ALPN alknet/call (quinn
  feature), spawns Dispatcher::run_loop, returns a live CallConnection.
- spawn_dispatch(connection) shared path for connect + tests.
- CallCredentials { tls_identity, auth_token, remote_identity } — all from
  Capabilities (ADR-014), never env vars (no-env-vars invariant). v1
  connects without client-auth TLS identity (server uses
  AcceptAnyCertVerifier); RawKey client-auth is a two-way-door remainder.
- RemoteIdentity { fingerprint } — concrete shape is a two-way door (OQ-25
  remainder); the one-way constraint is it comes from Capabilities.
- ClientError { Transport, TlsSetup, ConnectionClosed }.
- CallConnection is now Clone (shares the inner Arcs) so connect can hand
  the caller a live clone while the dispatcher task keeps its clone.

Tests (199 lib + 1 integration):
- Unit: default-deny NOT_FOUND for non-remote-safe; remote_safe dispatches;
  trusted-peer dispatches all External; default-deny does NOT populate
  capabilities (the load-bearing security assertion — verified by a handler
  that inspects context.capabilities and the fact that the handler is never
  reached for non-remote-safe ops); remote_safe op populates capabilities;
  services/list peer-scoped hide/trusted variants; CallClient constructors;
  CallCredentials builder; Send+Sync.
- Integration (tests/two_node_call.rs): real QUIC loopback — CallAdapter
  server (self-signed cert via rcgen) accepts, CallClient connects,
  client.call() round-trips to server/echo. Proves the connect path +
  shared dispatch loop work end-to-end.

clippy + fmt + test all green.

Refs: tasks/call/client/call-client.md
Refs: docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md §1, §2, §7
Refs: docs/architecture/decisions/028-callclient-peer-scoped-registry-filtering.md
Refs: docs/architecture/crates/call/client-and-adapters.md
2026-06-26 13:19:15 +00:00
glm-5.2 00edfc0889 feat(core): ADR-027 — RawKey decoupling, client cert request, ACME integration
Three tasks implementing ADR-027:

1. core/rawkey-decouple-from-iroh: TlsIdentity::RawKey now uses
   Ed25519SecretKey (alknet-core-owned wrapper over ed25519_dalek)
   instead of iroh::SecretKey. RawKeyCertResolver and Ed25519SigningKey
   un-gated from #[cfg(all(quinn, iroh))] to #[cfg(quinn)] only.
   Quinn-only builds (default) now support RFC 7250 raw-key identity.
   iroh transport converts via iroh::SecretKey::from_bytes.

2. core/endpoint-request-client-cert: replaced with_no_client_auth()
   with AcceptAnyCertVerifier — a custom ClientCertVerifier that
   requests client certs but doesn't require them or verify against
   a CA. alknet's identity model is fingerprint-based (the
   authorized_fingerprints set is the trust anchor), not PKI-based.
   Peer certs are extracted at the TLS layer for fingerprinting;
   peers without certs connect normally.

3. core/acme-integration: TlsIdentity::Acme variant (domains,
   cache_dir, directory, contact) + AcmeDirectory enum. TlsSetup
   two-phase construction: synchronous for X509/RawKey/SelfSigned,
   async for Acme (spawns AcmeState event loop, builds ServerConfig
   with ResolvesServerCertAcme). acme-tls/1 ALPN added when ACME is
   active; dispatch_quinn guard closes challenge connections
   gracefully (challenge is TLS-layer-handled). acme feature gate
   keeps rustls-acme out of non-ACME builds.

Workspace: build/test/clippy green across all 3 feature configs
(quinn-only, quinn+iroh, quinn+acme, all-features). 331 tests, 0
failures, 0 warnings.
2026-06-24 20:29:43 +00:00
glm-5.2 c68050ae0f feat(call): implement CallConnection with imported-ops overlay and call/subscribe/abort (task: call/protocol/call-connection)
Implement CallConnection in protocol/connection.rs with Layer 2 imported-ops
overlay (Arc<RwLock<HashMap>>), register_imported/register_imported_all,
overlay_env() returning an OperationEnv that dispatches to imported ops,
and call()/subscribe()/abort() methods that open a stream, send call.requested,
register in PendingRequestMap, spawn a stream reader, and correlate responses
by ID. Connection drop drops the overlay. Exposed MockConnection +
Connection::from_mock in alknet-core for cross-crate testing. 9 new connection
tests (102 total in alknet-call).

Refs: docs/architecture/crates/call/call-protocol.md
Implements: ADR-012, ADR-017, ADR-024
2026-06-23 15:17:55 +00:00
glm-5.2 ddc6c07fea feat(call): implement CallConnection with imported-ops overlay (Layer 2) and call/subscribe/abort methods
Implements CallConnection in src/protocol/connection.rs representing an
established alknet/call connection (either direction). Holds the Layer 2
imported-ops overlay (ADR-024) as Arc<RwLock<HashMap>>.

- register_imported / register_imported_all add to the connection overlay
- overlay_env returns an OperationEnv dispatching to imported ops; contains()
  returns true only for ops in the overlay
- call() opens a stream, sends call.requested, registers in PendingRequestMap,
  spawns a stream reader, resolves on first call.responded
- subscribe() sends call.requested and yields call.responded until
  call.completed/call.aborted via a SubscriptionStream wrapping the mpsc receiver
- abort() sends call.aborted for the request ID and removes the pending entry
- connection drop drops the overlay (no explicit deregistration needed)

Exposes MockConnection trait and Connection::from_mock in alknet-core so
cross-crate tests can construct mock connections without real QUIC. Removes
two unused test helpers in env.rs that triggered dead-code warnings under
-D warnings. Adds parking_lot dep for the overlay RwLock and pending Mutex.

9 new connection tests (102 total in alknet-call). Clippy clean.
2026-06-23 15:16:10 +00:00
glm-5.2 8d056a2b59 feat(core): implement AlknetEndpoint, HandlerRegistry, accept loops (quinn + iroh), TLS identity (RawKey/X509/SelfSigned), and graceful shutdown (task: core/endpoint) 2026-06-23 15:12:14 +00:00
glm-5.2 a4b4d89d8f feat(core): implement AuthContext, Identity, AuthToken, IdentityProvider, ConfigIdentityProvider (task: core/auth)
Implement authentication types in auth.rs: AuthContext (Clone, 4 fields),
Identity (Clone, PartialEq), AuthToken, IdentityProvider trait (resolve_from_
fingerprint + resolve_from_token), ConfigIdentityProvider (reads from
ArcSwap<DynamicConfig> on every call — hot-reloadable). Fingerprint resolution
via authorized_fingerprints HashSet, token resolution via alk_ prefix + SHA-256
hash + expiry check. Also implemented minimal config.rs types (DynamicConfig,
AuthPolicy, ApiKeyEntry, RateLimitConfig, ConfigReloadHandle) needed by auth —
aligned with architecture docs for the parallel core/config task to extend.

27 unit tests pass; clippy clean.

Refs: docs/architecture/crates/core/auth.md
Implements: ADR-004, ADR-011
2026-06-23 14:10:06 +00:00
glm-5.2 d7d879a3fa vault: spec-conformance fixes from review (task: vault/review-vault-sync)
Review of vault crate against all architecture specs. Fixed 5 deviations:
1. EncryptionKey: removed Clone (now move-only per spec), added redacting Debug
2. EncryptionKey::new made private (cfg(test)), added pub(crate) key_bytes()
3. encrypt/decrypt made pub(crate) per encryption.md, low-level crypto tests
   moved from integration to unit tests
4. CachedKey refactored to wrap DerivedKey with cached_at/last_accessed fields
   per service.md, with key_type()/private_key()/public_key() accessors
5. Mnemonic::to_seed() unwrap() eliminated by storing validated Bip39Mnemonic
   (enabled bip39 zeroize feature for proper zeroization)

All 10 drift items verified resolved. 105 tests pass; clippy clean.

Refs: docs/architecture/crates/vault/README.md (review checklist)
2026-06-23 14:09:36 +00:00
glm-5.2 8dc842b1f4 feat(core): implement AuthContext, Identity, AuthToken, IdentityProvider, ConfigIdentityProvider (task: core/auth)
- auth.rs: Identity, AuthContext, AuthToken, IdentityProvider trait, ConfigIdentityProvider
- ConfigIdentityProvider reads from ArcSwap<DynamicConfig> on every call (hot-reloadable)
- Fingerprint resolution via authorized_fingerprints; token resolution via alk_ prefix + SHA-256 hash + expiry check
- config.rs: minimal DynamicConfig, AuthPolicy (with resolve methods), ApiKeyEntry, RateLimitConfig, ConfigReloadHandle required by auth
- Unit tests: fingerprint resolution (known/unknown/empty), token resolution (valid/expired/unknown/wrong-hash/non-alk), config reload changes results immediately
- Add sha2, hex deps to alknet-core
2026-06-23 14:08:33 +00:00
glm-5.2 41f0fc7843 vault: spec-conformance fixes from review (task: vault/review-vault-sync)
- EncryptionKey: remove Clone (move-only per spec), add custom redacting
  Debug impl, make new() private (cfg(test)), add pub(crate) key_bytes()
  accessor, make encrypt/decrypt pub(crate) module-internal helpers
- CachedKey: refactor to wrap DerivedKey (per service.md) with cached_at
  and last_accessed fields; add key_type()/private_key()/public_key()
  accessors
- Mnemonic: store validated Bip39Mnemonic to eliminate unwrap() in
  to_seed(); enable bip39 zeroize feature so inner is zeroized on drop
- Fix clippy: remove unused import in drop_tracker tests, use struct
  init syntax instead of field reassignment with Default
- Move low-level EncryptionKey round-trip/wrong-key tests from
  integration tests to unit tests (encrypt/decrypt now pub(crate))
2026-06-23 14:07:24 +00:00
glm-5.2 e13a150d9f feat(call): initialize alknet-call crate skeleton (task: call/crate-init)
Create crates/alknet-call with Cargo.toml, lib.rs, and module skeletons
for the registry (spec, context, registration, env, discovery) and
protocol (wire, pending, connection, adapter, abort) subsystems. Add the
crate to the workspace members list. Depends on alknet-core (workspace
path), irpc (workspace dep), tokio, serde, serde_json, async-trait,
tracing, thiserror, uuid, and futures. Implements ProtocolHandler on
ALPN alknet/call per docs/architecture/crates/call.
2026-06-23 13:45:14 +00:00
glm-5.2 7e3300e83a refactor(vault): remove irpc actor dispatch — direct method calls on VaultServiceHandle (task: vault/irpc-removal)
ADR-025 / drift item #4: remove the irpc-based actor dispatch from the vault
crate. VaultServiceHandle (Arc<std::sync::RwLock<>>) is now the sole synchronous
API. Removed: VaultProtocol enum, VaultServiceActor, VaultService wrapper,
Client<VaultProtocol> usage, irpc/irpc-derive/tokio deps, postcard dev-dep,
Serialize/Deserialize on VaultServiceError. lib.rs re-exports match the vault
README Public API. The vault is now local-only by construction with zero async
runtime dependency.

Refs: docs/architecture/crates/vault/README.md drift #4
Implements: ADR-025

# Conflicts:
#	Cargo.lock
2026-06-23 13:22:13 +00:00
glm-5.2 9028fca302 refactor(vault): remove irpc actor dispatch — direct method calls on VaultServiceHandle (ADR-025)
Drop the irpc-based actor dispatch path from alknet-vault and convert to
direct method calls on VaultServiceHandle (drift item #4, ADR-025).

Removed:
- VaultProtocol enum with #[rpc_requests] derive from protocol.rs
- VaultServiceActor (mpsc + oneshot dispatch loop) from service.rs
- VaultService wrapper struct (only the handle is needed)
- Client<VaultProtocol> usage
- irpc, irpc-derive, tokio from [dependencies]
- postcard from [dev-dependencies]
- VaultMessage/VaultProtocol/VaultServiceActor re-exports from lib.rs
- Serialize/Deserialize derives from VaultServiceError
- postcard round-trip tests from protocol.rs
- actor tokio::test tests from service.rs

The vault now has zero async runtime dependency and zero RPC framework
dependency — it is local-only by construction. VaultServiceHandle is the
sole API: Arc<std::sync::RwLock<VaultServiceInner>> with synchronous
methods. lib.rs re-exports match the vault README Public API section.

Also fixes pre-existing clippy field_reassign_with_default warnings in
cache.rs tests so cargo clippy -- -D warnings passes.
2026-06-23 13:20:28 +00:00
glm-5.2 963f3d9532 feat(core): initialize alknet-core crate with module skeleton
Create crates/alknet-core with Cargo.toml (dependencies, feature flags
quinn/iroh), src/lib.rs declaring types/auth/config/endpoint modules, and
skeleton files for each module with doc comments and TODO markers. Add the
crate to the workspace members list.

Both quinn (default-on) and iroh (opt-in) are optional and can be active
simultaneously per ADR-010. Dual license MIT OR Apache-2.0 inherited from
the workspace.
2026-06-23 13:12:49 +00:00
glm-5.1 80128a56e5 refactor: rename alknet-secret to alknet-vault
Rename the crate from alknet-secret to alknet-vault to better reflect its
purpose as a local key vault (seed management, key derivation, encryption)
rather than a network service.

Symbol renames:
- SecretService → VaultService
- SecretServiceHandle → VaultServiceHandle
- SecretServiceActor → VaultServiceActor
- SecretServiceError → VaultServiceError
- SecretProtocol → VaultProtocol
- SecretMessage → VaultMessage
- ServiceLocked → VaultLocked
- alknet_secret → alknet_vault (crate name)

Update ADR-008 with vault access pattern: the vault is a capability source,
not a service endpoint. The CLI injects derived/decrypted material into
operation contexts — handlers never hold vault references.
2026-06-16 11:10:07 +00:00
glm-5.1 b5a4600d74 greenfield: clean slate for ALPN-as-service pivot
Delete old source crates (alknet-core, alknet, alknet-napi), old
architecture docs (ADRs, specs, open questions), old research docs
(phase2, event-sourcing, feasibility, etc.), old tasks, and obsolete
reference material (gitserver/MPL, honker, nats, rustfs, polyglot,
keystone, distributed-identity).

Keep: alknet-secret (standalone, compiles), pivot docs, iroh and ssh
references, rudolfs reference (MIT/Apache, fork candidate), ops docs,
sdd_process.md, and licenses.

Previous implementation preserved at /workspace/@alkdev/alknet-main/
for reference during porting.

Workspace compiles: cargo check + 14 tests pass for alknet-secret.
2026-06-15 12:08:08 +00:00
glm-5.1 470473fbb9 feat(secret): wire SecretProtocol to irpc with SecretServiceActor
Apply #[rpc_requests(message = SecretMessage)] to SecretProtocol enum with
#[rpc(tx=oneshot::Sender<Result<T, SecretServiceError>>)] and #[wrap] attributes
on each variant. Add SecretServiceActor that wraps SecretServiceHandle and
processes SecretMessage variants via mpsc channel. Update DerivedKey
serialization to use is_human_readable() so postcard preserves private_key
bytes while JSON redacts them. Add Serialize/Deserialize to SecretServiceError
for irpc wire format compatibility. Add tokio dependency for actor runtime.
2026-06-10 07:41:53 +00:00
glm-5.1 eae47c366b feat(alknet-secret): make DerivedKey zeroize-on-drop, non-Clone, with redacted serialization
Per ADR-038, DerivedKey.private_key now derives Zeroize with #[zeroize(drop)]
ensuring sensitive key material is zeroized before deallocation. DerivedKey
is now move-only (no Clone), and JSON/debug output redacts private_key as
"[REDACTED]". Deserialization still works for postcard/irpc wire format.

Also fixes clippy needless_borrows_for_generic_args in encryption.rs and
applies cargo fmt to existing code.
2026-06-10 06:16:38 +00:00
glm-5.1 83ea66b5d1 chore: prep Phase 3 tasks and workspace for alknet-secret development
- Add irpc (0.16) and irpc-derive (0.16) as workspace dependencies
- Add irpc, irpc-derive, and secp256k1 (optional) to alknet-secret Cargo.toml
- Clarify encryption-salt-kdf task: Option B (document salt as reserved) is the
  chosen path per spec update, removing Option A acceptance criteria
- Update irpc-secret-protocol-integration task with concrete irpc crate details:
  real crate on crates.io v0.16, #[rpc_requests] macro, workspace config,
  AuthProtocol pattern reference, DerivedKey serialization considerations
- Fix secp256k1-ethereum-derivation task: correct crate name is secp256k1
  (not libsecp256k1), add version pin 0.29
2026-06-10 05:57:27 +00:00
glm-5.1 04e969982e feat(secret): add alknet-secret crate and architecture spec for Phase 3
Create the alknet-secret crate with BIP39 mnemonic generation, SLIP-0010
Ed25519 HD key derivation, AES-256-GCM encryption, and SecretProtocol
irpc service definition. This is Phase 3.1 from the integration plan.

Architecture changes:
- Promote secret-service.md to reviewed status with full spec format
  (crate structure, public API, security model, phase progression,
   ADR/OQ cross-references, wire format compatibility section)
- Add ADR-038 (seed lifecycle and memory security): zeroize for v1,
  mlock deferred to Phase B
- Add OQ-SEC-01 (mlock/VirtualLock for seed RAM) to open-questions.md
- Update README.md with ADR-038 and secret-service status

Crate structure:
- src/mnemonic.rs: BIP39 phrase generation, validation, seed derivation
- src/derivation.rs: SLIP-0010 HD key derivation, path constants (74')
- src/encryption.rs: AES-256-GCM encrypt/decrypt, EncryptedData type
- src/protocol.rs: SecretProtocol irpc enum, DerivedKey, KeyType
- src/service.rs: SecretServiceHandle with Unlock/Lock lifecycle
- 40 passing tests (unit + integration + doc)
2026-06-09 13:49:53 +00:00
glm-5.1 d5d4b3c153 feat(core): add axum HTTP router scaffold with auth middleware and stealth handoff
Add http feature flag with axum, hyper, hyper-util, tower, and http-body-util
dependencies. Create http module with auth middleware (extracts Bearer token,
calls IdentityProvider::resolve_from_token, attaches Identity to extensions)
and router scaffold (default 404 fallback, no operational routes yet). Replace
send_fake_nginx_404 with axum router handoff when http feature is enabled;
fake 404 behavior preserved when http is disabled. Wire HttpInterface with
build_router() method and pass IdentityProvider through Server to handle_connection.
2026-06-09 11:27:27 +00:00
glm-5.1 1f55844935 feat(core): add API keys to DynamicConfig.auth and extend IdentityProvider token resolution 2026-06-09 11:01:33 +00:00
glm-5.1 88a875241a Add NAPI reload API for DynamicConfig and ForwardingPolicy
- Add reloadAuth(), reloadForwarding(), reloadAll() methods to AlknetServer
- Add NAPI type definitions: AuthConfigNapi, ForwardingPolicyConfig, ForwardingRuleConfig
- Refactor NapiServerHandler to use ArcSwap<DynamicConfig> for atomic config swaps
- Add ConfigReloadHandle::dynamic_arc() accessor for sharing ArcSwap between NAPI and accept loop
- Add ipnetwork dependency to alknet-napi for TargetPattern CIDR parsing
- Add builder functions for AuthPolicy and ForwardingPolicy from NAPI config types
- All swaps are atomic via ArcSwap per ADR-030
2026-06-07 15:30:38 +00:00
glm-5.1 0d6f94c24c feat(core): implement OperationContext, OperationRegistry, and OperationSpec 2026-06-07 14:44:26 +00:00
glm-5.1 ee1b3f3819 feat(core): implement StaticConfig/DynamicConfig split with ArcSwap hot-reload
Split alknet-core configuration into StaticConfig (immutable after startup)
and DynamicConfig (hot-reloadable at runtime via ArcSwap).

- Add StaticConfig struct in config/static_config.rs with all fields per ADR-030
- Add DynamicConfig struct with AuthPolicy, ForwardingPolicy, RateLimitConfig
- Add ForwardingPolicy with allow_all()/deny_all() defaults (ADR-031)
- Add ConfigReloadHandle with reload() method for runtime config updates
- Replace Arc<ServerAuthConfig> with Arc<ArcSwap<DynamicConfig>> in ServerHandler
- Add config_reload_handle() to Server for obtaining reload handles
- Add AuthPolicy with authenticate_publickey/authenticate_certificate methods
- All existing tests pass with the new config structure
- Default DynamicConfig produces identical behavior to current code
2026-06-07 14:03:46 +00:00
glm-5.1 596c89ce24 refactor!: rebrand wraith to alknet
Rename all crates, CLI commands, constants, type names, doc comments,
and documentation from wraith to alknet. Includes wire-protocol changes:
ALPN wraith-ssh -> alknet-ssh, reserved destination prefix wraith- ->
alknet-, SSH auth username wraith -> alknet.
2026-06-05 10:04:32 +00:00
glm-5.1 d85c882635 feat!: harden SSH server handler security
- Restrict auth methods to PUBLICKEY only (no none, password, hostbased,
  or keyboard-interactive advertised during negotiation)
- Log all denied channel types (session, x11, forwarded-tcpip) and
  dangerous request types (exec, shell, subsystem, pty, env, x11, agent)
- Explicitly reject all dangerous channel request handlers (exec, shell,
  subsystem, pty, env, x11, agent forwarding) with channel_failure
  responses instead of russh's default silent Ok(()) which leaves clients
  hanging and is a footgun if session channels are ever allowed
- Explicitly reject tcpip_forward, streamlocal_forward with logged warnings
- Log signal requests at debug level (harmless, no response needed)
- Override handlers in both core ServerHandler and NapiServerHandler
- Add tracing dependency to wraith-napi for security event logging
- Set preferred algorithms explicitly (russh::Preferred::DEFAULT which
  uses only modern KEX/cipher/MAC algorithms)
2026-06-03 09:04:01 +00:00
glm-5.1 150b1f3ae5 feat(napi): add TLS and iroh transport support to serve() and connect() 2026-06-03 05:58:05 +00:00
glm-5.1 0fdb6cd782 feat(napi): implement serve() function with WraithServer, WraithServerStream, and ConnectionInfo
Expose NAPI serve() per ADR-016. WraithServer provides close() and
onConnection(callback) for receiving SSH channel streams from
incoming connections. Each connection produces a WraithServerStream
(Duplex-like read/write/close) with ConnectionInfo (remoteAddr,
transportKind). Supports TCP transport with optional authorizedKeys
and certAuthority auth. TLS and iroh transports return helpful errors
indicating future support.
2026-06-02 20:05:13 +00:00