Commit Graph
100 Commits
Author SHA1 Message Date
glm-5.2 df355c53a9 tasks: decompose ADR-029/030/031/032/034/035 source sync into 17 tasks
Decompose the source-to-spec sync for the core and call crates into atomic,
dependency-ordered tasks for implementation agents:

Core (7 tasks + review):
- peer-entry-model: PeerEntry struct, AuthPolicy.peers (ADR-030 keystone)
- credential-store-trait: CredentialStore/InMemoryCredentialStore/StoreError (ADR-031/035)
- identity-store-trait: IdentityStore async write trait (ADR-035)
- config-identity-provider-peerentry: ConfigIdentityProvider PeerEntry resolution (ADR-030)
- fingerprint-normalization: ed25519:hex for raw keys across quinn/iroh (ADR-030 §6)
- three-remote-roles-docs: document ADR-034 roles and verifier selection
- review-core-sync: phase gate before call consumes new identity semantics

Call (9 tasks + review):
- retire-remote-safe: remove ADR-028 machinery, AccessControl is the gate (ADR-029 §3)
- operation-context-forwarded-for: forwarded_for field, wire-ingress only (ADR-032)
- peer-composite-env: PeerCompositeEnv, PeerId=Identity.id, remove UUID (ADR-029/030)
- operation-env-invoke-peer: invoke_peer/peer_contains/PeerRef (ADR-029 §2)
- services-list-accesscontrol-filtered: AccessControl filter, list-peers opt-in (ADR-029 §6)
- call-client-verifier-selection: TLS client-auth, verifier by PeerEntry (OQ-29, ADR-034)
- from-call-forwarded-for: populate forwarded_for, peer-keyed registration (ADR-029 §5, ADR-032)
- dispatch-peer-identity: AccessControl::check(peer_identity), PeerId from resolution (ADR-029 §3, ADR-030 §5)
- review-call-sync: phase gate for the call sync

Validated: 58 tasks, no cycles, logical topo order, two review checkpoints.
2026-06-28 21:08:41 +00:00
glm-5.2 4a52779460 docs(arch): amend call specs for ADR-029/030/032/034 — peer-keyed routing, PeerEntry, forwarded-for, three roles
Sync the call crate specs to the accepted ADRs 029-034:
- operation-registry: PeerCompositeEnv (peer-keyed overlays), invoke_peer/
  PeerRef routing, retire remote_safe/trusted_peer, AccessControl-based peer
  auth, forwarded_for on OperationContext (ADR-029/030/032)
- call-protocol: peer-keyed compose_root_env, forwarded_for in call.requested
  payload, build_root_context forwarded_for parameter (ADR-029/032)
- client-and-adapters: CallClient verifier selection by PeerEntry presence,
  remote_identity: None load-bearing, three remote roles (ADR-034)
- README: ADR-029/030/032/034 in applicable ADRs table
2026-06-28 21:08:26 +00:00
glm-5.2 0de2cebb1d docs(arch): ADR-035 — concrete persistence adapter shapes, resolve OQ-36
Commits the concrete adapter shape deferred by ADR-033: read-sync /
write-async split with honker NOTIFY/LISTEN for no-restart cache
invalidation, against SQLite, in a separate alknet-store-sqlite crate.

Two constraints drive the design: (1) the hot-path read trait
(IdentityProvider::resolve_from_fingerprint, CredentialStore::get) is
sync — called in the accept loop, no .await — so a SQLite-backed
adapter must cache in memory and serve sync reads from the cache; (2)
auth changes must take effect without a restart (an early issue the
project already fixed for ConfigIdentityProvider via ArcSwap config
reload). honker's SQLite NOTIFY/LISTEN (single-digit-ms wake, no
polling) is the cache-invalidation mechanism that makes both hold:
write commits to SQLite + emits NOTIFY, the running process's LISTEN
wakes, the in-memory index reloads and atomically swaps, the next
read sees the new state. Same ArcSwap-reload pattern as config,
generalized from 'config file is source of truth' to 'SQLite is
source of truth, honker signals when it changed.'

New async IdentityStore write trait (put_peer / update_peer /
remove_peer) extends the sync IdentityProvider read trait for peer
mutations. ConfigIdentityProvider does NOT implement it (config
reload is its write path — a posture enforced by the absence of a
backend, not a type-system constraint); SqliteIdentityProvider
implements both. CredentialStore::put/delete refined to async (within
ADR-031's one-way door — the contract was get/put/delete keyed by
provider persisting EncryptedData never decrypting; sync-vs-async was
unspecified). CredentialStoreError renamed to shared StoreError
covering both traits.

alknet-store-sqlite is one crate implementing both IdentityStore and
CredentialStore with shared SQLite connection + honker LISTEN infra
(splitting later is a two-way door). Schema shape committed (one row
per PeerEntry with JSON columns for fingerprints/scopes/resources;
one row per EncryptedData blob keyed by provider); exact DDL is an
implementation-detail two-way door in the adapter crate. The keypal
adapter-factory pattern is intentionally not ported to Rust (runtime
column-mapping is a TS affordance; in Rust each adapter is a concrete
type, cross-cutting concerns are a shared helper module).

Amends ADR-031 (put/delete async refinement, StoreError rename),
ADR-033 (concrete adapter shape now specified, two-crate framing
collapsed to one), ADR-034 (OQ-36 now resolved), auth.md (IdentityStore
section, cache-invalidation summary, OQ-36 reference), config.md (two
write paths note), and the OQ-36/OQ-34 entries in open-questions.md.
Review fixed 4 criticals (error-type name divergence, duplicate
IdentityProvider sketch, upsert/Duplicate ambiguity, 'shape unchanged'
contradiction), 7 warnings, 5 suggestions.
2026-06-28 11:10:31 +00:00
glm-5.2 6cc8715ccf docs(arch): ADR-034 — outgoing-only X.509 and three peer roles, resolve OQ-37
Untangles the conflation of three distinct remote roles under 'X.509
endpoint': (1) public X.509 endpoint — a remote HTTPS/call-over-TLS
server the local node is a client of (no PeerEntry, no PeerId, not in
the peer graph; CA verification + bearer token); (2) transport relay —
iroh's DERP-equivalent, infrastructure, not an alknet peer; (3) hub /
hosting node — an alknet peer that also exposes a public domain + X.509
for browsers (mixed-fingerprint PeerEntry, already supported by
ADR-030).

The load-bearing one-way door is the client-side verifier selection
rule: known peer (PeerEntry present) → fingerprint pin; unknown X.509
remote → CA verification (WebPkiServerVerifier); unknown Ed25519
remote → fails closed. This closes the AcceptAnyServerCertVerifier
security hole OQ-29 flagged, with the peer-model criterion (PeerEntry
presence) made explicit. The 'make PeerEntry symmetric' instinct is
rejected — pure-client connections to public APIs have no stable
logical identity to pin.

Documents that CallCredentials.remote_identity: None is load-bearing
(None = public X.509 endpoint → CA path, not a missing field; Some =
known peer → fingerprint pin), closing a subtle gap where an
implementer could have defaulted to a placeholder or treated None as
skip-verify.

Records WebTransport relay-as-proxy (deferred with h3/WebTransport,
new OQ-HTTP-07) and on-chain/smart-contract peer discovery (fits the
OQ-36 repo/adapter pattern, no auth-model change) so they aren't lost.

Amends auth.md and client-and-adapters.md with the three-role naming,
the verifier selection rule, and the Option semantics; updates OQ-37
to resolved in open-questions.md, README.md, and both crate READMEs.
2026-06-28 10:47:49 +00:00
glm-5.2 3f011cbb82 docs(arch): tighten door-type framing — reversal cost, not deferral
ADR-009, open-questions.md, and the architect agent spec all had the same
conflation: 'two-way door' was phrased as 'can be decided during
implementation,' which reads as 'defer the decision.' That's not what it
means. A two-way door is a decision you make now and can revert later if
wrong — it's about reversal cost, not urgency.

ADR-009: add §'What this framework is NOT' — explicitly separates door
type (reversal cost) from deferral (scope management). State that
architecture decisions are the architect's regardless of door type.
Reword the two-way-door process from 'can be decided during
implementation' to 'pick the simplest option that works, implement it,
revert if needed.'

open-questions.md: reword the header to clarify door type describes
reversal cost, not urgency. Add 'Door type is separate from whether a
decision is made.'

architect.md: add Key Principle #8 (decisions are made, not deferred),
a new 'Door Types and Decision Urgency' section, and two new anti-patterns
(#8: door type as deferral, #9: hedging language in resolved decisions).
2026-06-28 09:19:10 +00:00
glm-5.2 7d812af8f4 docs(arch): multi-credential PeerEntry, resolve OQ-29, dissolve OQ-35, add OQ-37
Amend ADR-030 with three changes from the auth-type analysis:

1. PeerEntry is now multi-credential: fingerprints: Vec<String> (Ed25519
   and/or X.509) + auth_token_hash: Option<String> (bearer token). All
   resolve to the same peer_id. A peer that authenticates via Ed25519
   today and via auth_token tomorrow gets the same PeerId. The 'peer
   bearer vs auth bearer' distinction was wrong — the correct framing is
   the three credential types (Ed25519, X.509, bearer token) and whether
   the token needs a stable logical id across rotation (PeerEntry) or not
   (ApiKeyEntry).

2. Fingerprint normalization (§6): quinn extracts the raw Ed25519 public
   key from the SPKI cert and formats as ed25519:<hex>, matching iroh.
   The same key has the same fingerprint regardless of transport. X.509
   fingerprints stay as SHA256:<hex of DER>. This also simplifies the
   coming WebTransport relay work.

3. The 'API keys' section is replaced with 'Bearer tokens' — correctly
   framing the three auth types and the two bearer-token paths
   (PeerEntry.auth_token_hash vs ApiKeyEntry).

Resolve OQ-29 (CallClient TLS client-auth): wire quinn client-auth (present
Ed25519 key as raw public key client cert — the server-side extraction
already works); key-type-aware server cert verification (raw key =
fingerprint match, X.509 = CA verification via WebPkiServerVerifier —
AcceptAnyServerCertVerifier is only safe for raw keys); fingerprint
normalization. The iroh path already works (RFC 7250 raw keys, both sides
exchange automatically); the gap was quinn-only.

Dissolve OQ-35: the 'API key asymmetry' framing was wrong. PeerEntry
supports multiple credential paths; ApiKeyEntry is for tokens that ARE the
identity.

Add OQ-37: X.509 outgoing-only case — the three auth types and how X.509
server identity fits the peer model. Not blocking the ADR-029 migration;
downstream (HTTP crate phase).

Update auth.md, config.md, client-and-adapters.md, call/README.md,
core/README.md, open-questions.md, README.md, and call_client.rs source
comment.

Workspace green: 326 tests pass, build clean.
2026-06-28 08:49:36 +00:00
glm-5.2 1d94aaea51 docs(arch): resolve call-crate OQs, promote OQ-29 to load-bearing on ADR-030
Resolve the call-crate open questions where the decision is made —
OQ-27 (auto-re-import), OQ-28 (same-peer collision = error), OQ-30
(PeerRef::Any insertion-order first-match), OQ-31 (services/list-peers
opt-in). These were previously marked 'open' with 'v1' hedging language
despite having a decided default. What remains (refresh(), richer routing,
services/list-peers the op) is genuine feature addition, not unmade
architecture.

Reframe OQ-32 (multi-hop) as a feature extension rather than a 'v1'
deferral — the one-hop model is the architectural commitment; extending
to multi-hop doesn't break downstream.

Promote OQ-29 (CallClient TLS client-auth) from medium to high priority
and surface its real interaction with ADR-030. Previously framed as
'additive — two-way-door remainder,' but ADR-030's PeerEntry fingerprint
→ peer_id resolution requires the client to present a TLS client cert.
With with_no_client_auth(), no fingerprint is extracted, the PeerEntry
path is dormant, and PeerCompositeEnv keys on None or the API-key prefix
instead of the stable peer_id. This is the activation path for ADR-030's
primary use case, not an additive feature. Three options laid out: (a)
wire client-auth with the ADR-029 migration, (b) ship token-only and
switch later (the 'compounds into a mess' path), (c) extend PeerEntry
to cover auth_token-based identity. Requires a decision before the
migration lands.

Clarify OQ-36 (concrete adapter shapes): the trait shapes and in-memory
adapters ship with core — the deferral is only for the persistence
adapters (SQLite, etc.). The in-memory adapters are real implementations
of a full repo pattern, not stubs.

Update call_client.rs source comment to reference OQ-29 instead of the
'v1' / 'two-way-door remainder' framing.

Workspace green: 326 tests pass, build clean.
2026-06-28 05:35:52 +00:00
glm-5.2 f224ea998c docs(arch): ADR-030..033 — repo/adapter pattern, PeerEntry, CredentialStore, forwarded-for
Land the storage and auth strategy research (findings.md) as four
accepted ADRs and amend the core and call specs to match:

- ADR-030: PeerEntry and Identity.id decoupling. Replaces
  authorized_fingerprints with peers: Vec<PeerEntry>; Identity.id becomes
  the stable peer_id, decoupled from the rotating fingerprint. Supersedes
  ADR-029 Assumption 1's UUID source (one-way door preserved, source
  changes). Resolves OQ-33 and the storage-boundary half of OQ-34. Records
  the API-key asymmetry as deliberate (OQ-35).

- ADR-031: CredentialStore repo trait + InMemoryCredentialStore default
  adapter in core. Second repo trait alongside IdentityProvider. Vault
  encrypts; the store persists the EncryptedData blob; assembly layer
  loads into Capabilities. EncryptedData core mirror includes salt for
  wire-format compat.

- ADR-032: Forwarded-for identity. forwarded_for field on call.requested
  and OperationContext — metadata only, never read by AccessControl::check
  (enforced structurally via the check signature). The from_call handler
  populates it. Wire-format one-way door, folded into the ADR-029
  migration window.

- ADR-033: Storage boundary and repo/adapter pattern. Core defines repo
  traits + in-memory defaults; persistence adapters are separate crates;
  assembly layer wires. Resolves OQ-34. Concrete adapter shapes deferred
  for exploration (OQ-36).

Amends auth.md, config.md, operation-registry.md, client-and-adapters.md,
open-questions.md, README.md, crates/core/README.md. Marks ADR-029
Accepted (Assumption 1 carries the ADR-030 superseded note). Marks the
research findings doc reviewed.
2026-06-27 12:12:25 +00:00
glm-5.2 347bff257c docs(research): rewrite storage/auth strategy — concrete repo/adapter design, no deferrals
Reworks the storage strategy doc to commit to concrete design, replacing
the 'when storage arrives' / 'future' / 'later' framing that was putting off
important work.

Key changes from the previous draft:
- §4 (Repo/Adapter Pattern): now an explicit design with the trait contracts
  (IdentityProvider, CredentialStore), the adapter contracts
  (ConfigIdentityProvider with PeerEntry update, SqliteIdentityProvider,
  InMemoryCredentialStore, SqliteCredentialStore), and the concrete table
  schemas. Not a pattern description — a design commitment.
- §4: PeerEntry config model — AuthPolicy gains peers: Vec<PeerEntry>
  replacing authorized_fingerprints: HashSet<String>. This is the
  id-fingerprint decoupling (OQ-33) done as a config change, not a storage
  change. ConfigIdentityProvider resolves fingerprint → PeerEntry →
  Identity { id: peer_id } (stable, not the fingerprint).
- §7 (Decomposition): the 'what goes where' table now has a Status column
  (exists / needs adding / needs building / needs PeerEntry update) instead
  of 'future'. The crate graph is a concrete build plan.
- §10 (Build Order): replaces 'What This Means for the Immediate Path' (which
  had 'when storage arrives' framing) with a 4-tier dependency-driven build
  order. Tier 1 = core repo traits + PeerEntry config model. Tier 2 = SQLite
  adapters. Tier 3 = ADR-029 migration + forwarded_for. Tier 4 = alknet-graphs
  (built when a graph-shaped problem exists, not speculatively).
- §10: explicit 'What does NOT get built (dropped, not deferred)' section —
  multi-tenant, accounts/orgs, secrets module, single storage crate are
  dropped, not deferred.
- All 'future' / 'when X arrives' / 'v1' / 'phase n' language removed for
  things that are needed. The only 'when X is needed' language remaining is
  for genuinely non-existent problems (ACL delegation, workflows, taskgraph)
  — those are built when the problem exists, not speculatively.
2026-06-27 10:36:07 +00:00
glm-5.2 19d010cf73 docs(research): storage and auth strategy — repo pattern, per-node ACL, SQLite+honker, metagraph-as-tool
Synthesizes the multi-thread discussion that surfaced during the peer-graph
routing research (ADR-029) and OQ-33/34 resolution. Three separate threads
(peer identity, filesystem POC, old storage spec) converged on the same
question: where does persistent state live in the alknet crate graph, and
what's the shared infrastructure for it.

Key commitments documented:
- SQLite + honker is the foundation (pattern, not a crate — ~20 lines per
  consumer). The metagraph is one tool built on it, for graph-shaped
  problems. Direct tables are another tool, for table-shaped problems.
- IdentityProvider is the auth repo trait (already exists in core, make the
  pattern explicit). Adapters implement it (Config, SQLite, future
  Redis/remote/automerge). PeerStore is adapter-internal, not core.
- Per-node ACL, no 'trusted' flag. Each node authorizes its direct callers
  via AccessControl::check(identity). No global ACL, no replication. The
  hub authorizes the user; the spoke authorizes the hub. Same mechanism.
- Forwarded-for identity as metadata, not authority. The from_call handler
  includes the original caller's identity in the call payload; the spoke's
  ACL authorizes the hub (direct caller), never the forwarded_for. The ACL
  check signature prevents misuse.
- The ACL check stays table-shaped (flat scope match); the delegation graph
  (future) produces effective scopes at resolution time. They compose at the
  IdentityProvider boundary.
- The hub proxy tangle: ACL (authorize), bucket routing (operation input),
  peer routing (PeerRef) are three separate layers. Bucket-level
  authorization is handler logic, not protocol logic.

What the old spec had that's dropped: multi-tenant (each tenant gets own
setup), secrets module (replaced by vault), metagraph-as-foundation (demoted
to tool), single storage crate (split by concern), accounts/orgs (deferred —
v1 is a peers table).

Reference: kepal (/workspace/keypal) — TypeScript repo-pattern example
(Storage interface + adapters) that alknet's IdentityProvider follows.
2026-06-27 10:02:26 +00:00
glm-5.2 99c6dd9483 docs(arch): resolve OQ-26 (AdapterError variants) + OQ-33 (PeerId = logical id) + OQ-34 (persistent peer registry)
OQ-26 (resolved): AdapterError variants decided — DiscoveryFailed,
SchemaParse, Transport, Unauthorized, SamePeerCollision (replaces flat
Conflict per ADR-029 §5). #[non_exhaustive] for downstream extension.
Two-way door; the initial set is the code's return type.

OQ-33 (resolved): PeerId is a logical identifier, NOT Identity.id. The
research's v1 default (PeerId = fingerprint) is overridden: coupling PeerId
to crypto material breaks every in-flight PeerRef::Specific and every ACL
entry on key rotation. v1 source is a connection-assigned UUID — a
no-storage workaround that works for the immediate use case (head→workers,
reconnect produces fresh PeerRef, in-flight gets NOT_FOUND which is correct).
The one-way door: PeerId is logical, not crypto — this determines
PeerCompositeEnv key type and PeerRef::Specific payload. The id source
(UUID vs configured name vs peer registry) is the two-way-door remainder.

OQ-34 (new): the storage dimension OQ-33 surfaced. The core crates are
deliberately DB-free (smaller, fewer deps, simpler testing) — this served
local-only state (vault, registry) well, but peer identity is the first
cross-node state that wants persistence. The real solution (a persistent
peer registry mapping stable logical name → current crypto material,
surviving key rotation) is not a v1 blocker (UUID works), but tracked so the
no-DB posture's limit is deliberate, not accidental. The storage boundary
(core gets a PeerRegistry trait vs stays storage-free) is the one-way door;
the backend choice is two-way. Key-rotation/ACL note: decoupling PeerId from
crypto keeps the door open for ACL entries that persist across key rotation
— when the peer registry is built, ACLs key on the logical name and key
rotation becomes vault-only with no remote-side ACL update.
2026-06-27 06:34:35 +00:00
glm-5.2 77eb35a8a5 docs(arch): ADR-029 peer-graph routing model — supersedes ADR-028
ADR-028's remote_safe/trusted_peer was a parallel, weaker authorization system
that duplicated the existing AccessControl/Identity machinery and couldn't
express the head→N-workers pattern (the primary use case). The flat-namespace
single-peer overlay model (one connection layer in CompositeOperationEnv)
structurally breaks the moment a head has two workers both exposing
/container/exec.

ADR-029 replaces it with:
- Peer-keyed overlays: PeerCompositeEnv { connections: HashMap<PeerId, ...> }
  replaces CompositeOperationEnv's singular connection layer. A head node
  routes invoke_peer() to the right peer via PeerRef::Specific / PeerRef::Any.
- AccessControl-based peer authorization: the existing AccessControl::check
  (peer_identity) gates peer calls — the same mechanism that gates every other
  call. remote_safe/trusted_peer/RemoteFilter/list_operations_peer_scoped/
  services_list_handler_peer_scoped are retired. The op's AccessControl IS the
  peer-authorization policy; no parallel system.
- ScopedPeerEnv: peer-qualified reachability (peer-pinned allowlist) replaces
  from_call's namespace_prefix as the disambiguation mechanism. Cross-peer
  collision dissolves (separate sub-overlays); same-peer collision stays error.
- services/list-peers opt-in for peer-attributed re-export listing.

POC-validated against real types (scratch module written, type-checked,
removed; build clean, 207 tests pass). Petgraph not needed for v1 (one-hop,
shallow); nested HashMap suffices; extends to multi-hop without redesign (OQ-32).

OQ impact: OQ-25 dissolved (no marking); OQ-28 cross-peer dissolved / same-peer
stays; OQ-26/27/29 stay; new OQ-30 (Any routing policy), OQ-31 (list-peers
semantics), OQ-32 (multi-hop federation).

Research: docs/research/alknet-call-peer-routing/findings.md (POC shapes,
prior art — Ray.io actors, Dapr service invocation, full ADR draft).
ADR-028 marked Superseded; ADR-017 DC-1 amendment updated to point at ADR-029.
2026-06-27 06:04:19 +00:00
glm-5.2 f9c0ab092b docs(arch): sync call-completion specs with implementation — Dispatcher/RemoteFilter, ClientError, OQ-29
Post-implementation spec sync after the call-completion batch landed
(commits e4a2594..a3825f5). The sub-agent review flagged no spec drift, but
comparing the implemented types against the spec sketches surfaced five
details the specs didn't name — filled in here so the spec matches what was
built:

- client-and-adapters.md: name the shared Dispatcher (protocol/dispatch.rs)
  + RemoteFilter mechanism that enforces ADR-028's default-deny at dispatch
  time (the load-bearing security gate — checks remote_safe before building
  context, before any capability material reaches the handler). Add
  ClientError/RemoteIdentity types, the spawn_dispatch lower-level API, and
  the services_list_handler_peer_scoped wiring (the assembly layer must
  register the peer-scoped services/list handler for a CallClient's registry,
  not the plain one). Record the v1 TLS client-auth gap (AcceptAnyServerCertVerifier,
  with_no_client_auth) as OQ-29.
- call-protocol.md: point the adapter dispatch-loop description at the shared
  Dispatcher (dispatch.rs) so readers find the mechanism ADR-017 §1 commits to.
- open-questions.md: OQ-29 — CallClient TLS client-auth + remote-identity
  verification is a two-way-door remainder; the no-env-vars invariant is
  unaffected (auth_token flows via call-protocol payload, not TLS).
- READMEs: current-state now reflects completion done + reviewed (207 lib +
  2 integration tests); OQ-29 added to both OQ summaries.
2026-06-26 13:42:42 +00:00
glm-5.2 2fe471ad4e docs(tasks): mark call/review-completion complete with review findings
Records the conformance review for the alknet-call client/adapter
completion batch. All 14 acceptance criteria pass:

- CallClient, from_call, OperationAdapter, from_jsonschema match
  client-and-adapters.md.
- Peer-scoped default-deny (ADR-028) enforced: non-remote-safe ops return
  NOT_FOUND before capabilities are populated (the load-bearing security
  assertion, verified by three capability-exposure tests).
- Shared dispatch loop is genuinely shared (single Dispatcher; CallAdapter
  and CallClient both delegate to run_loop).
- No-env-vars invariant holds (no std::env::var reads; credentials from
  Capabilities).
- Adapter location map respected (no HTTP deps in alknet-call).
- OQ-25..28 two-way-door remainders recorded with v1 defaults.
- No spec drift requiring amendment.
- 207 lib + 2 integration tests pass; clippy + fmt clean.

This closes the call-completion batch. Unblocks every downstream consumer
(runner, container service, bilateral exchange, NAPI, agent cross-node
dispatch) and alknet-http Phase 1 (OperationAdapter trait).

Refs: tasks/call/review-completion.md
2026-06-26 13:27:08 +00:00
glm-5.2 a3825f57cf feat(call): from_call adapter — discover + register remote ops (ADR-017 §3)
The #2 gap in alknet-call: discovers the remote peer's External operations
via services/list + services/schema and registers them in the connection's
Layer 2 overlay as FromCall-provenance leaves with forwarding handlers. The
discovery mechanism was already implemented in registry/discovery.rs;
from_call is the client-side consumer of that API.

src/client/from_call.rs:
- from_call(connection, FromCallConfig) -> Result<Vec<HandlerRegistration>,
  AdapterError>. Calls services/list then services/schema for each op,
  rebuilds OperationSpec from the schema JSON (parsing op_type, visibility,
  error_schemas, access_control), constructs a forwarding handler that calls
  the remote op via CallConnection::call(), and returns FromCall-provenance
  bundles (composition_authority: None, scoped_env: None, empty capabilities,
  remote_safe: false per ADR-028 §4).
- FromCallConfig { namespace_prefix: Option<String>, operation_filter:
  Option<HashSet<String>> } with builder methods.
- v1 defaults (two-way doors recorded in client-and-adapters.md):
  - error-on-collision (DC-3/OQ-28): applying the (possibly empty) prefix
    produces a name already seen -> AdapterError::Conflict, not silent
    overwrite.
  - auto-on-reconnect (DC-2/OQ-27): the overlay is per-connection (Layer 2,
    ADR-024), so re-import on reconnect is naturally scoped; the assembly
    layer calls from_call immediately after connect().
- Forwarding handler captures an Arc<CallConnection> and, on invocation,
  calls the remote op and returns its ResponseEnvelope. The
  parent_request_id participates in the cross-node abort cascade
  (ADR-016 §6) — if the parent is aborted, the cascade reaches this handler
  which sends call.aborted to the remote node; cross-node abort is
  transparent.
- Trust is transitive (recorded in spec): a from_call-imported op executes
  the remote node's code; scoped_env bounds which ops are reachable, not
  what they do.

OperationContext.internal is now pub (was pub(crate)) so downstream
consumers (assembly layer, integration tests) can construct contexts for
overlay-env dispatch.

Tests (207 lib + 2 integration):
- Unit: rebuild_spec name/prefix/op_type/visibility/error_schemas/acl;
  unknown op_type -> SchemaParse; missing op_type -> SchemaParse;
  FromCallConfig builder; from_call against a mock connection returns
  DiscoveryFailed (no transport); FromCall provenance + leaf fields + remote_safe false.
- Integration (tests/two_node_call.rs): from_call over a real QUIC loopback
  — CallClient connects, from_call discovers server/echo, registers the
  bundle in the overlay, and the forwarding handler round-trips an input
  through the overlay env to the remote op and back.

clippy + fmt + test all green.

Refs: tasks/call/client/from-call.md
Refs: docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md §3, §6
Refs: docs/architecture/crates/call/client-and-adapters.md §from_call
2026-06-26 13:25:13 +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 404d00ae1a style(call): apply rustfmt to connection.rs and registration.rs
Pre-existing fmt drift in two files touched during the call-completion
batch (remote_safe field, dispatch helpers). Brings cargo fmt --check
clean for the review gate.
2026-06-26 12:57:14 +00:00
glm-5.2 1e5f94b06b feat(call): OperationAdapter trait + AdapterError + from_jsonschema (ADR-017 §5)
- client module: defines the async OperationAdapter trait
  (import() -> Result<Vec<HandlerRegistration>, AdapterError>) and the
  #[non_exhaustive] AdapterError enum (string-message payloads: DiscoveryFailed,
  SchemaParse, Transport, Unauthorized, Conflict). The trait lives in alknet-call
  where the types live; implementations live with their transport deps.
- from_jsonschema: schema-only registration producing a FromJsonSchema-provenance
  HandlerRegistration with no real handler (placeholder errors if invoked),
  None authority/scoped_env, empty capabilities, remote_safe false (ADR-028 §4).
  Implements OperationAdapter; malformed (non-object) schema returns
  AdapterError::SchemaParse. No network I/O.
- Re-exported from lib.rs.
- Tests: trait compiles for Ok and Err adapters; from_jsonschema bundle shape;
  placeholder handler errors; OperationAdapter import Ok + SchemaParse paths.
  All 178+N tests pass, clippy + fmt clean.

Unblocks alknet-http Phase 1 (from_openapi/from_mcp adapter implementations).

Refs: tasks/call/client/operation-adapter-trait.md, tasks/call/client/from-jsonschema.md
Refs: docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md §5
Refs: docs/architecture/crates/call/client-and-adapters.md
2026-06-26 12:56:28 +00:00
glm-5.2 e4a25947d6 feat(call): remote_safe field on HandlerRegistration (ADR-028)
Adds the v1 data shape for peer-scoped default-deny registry filtering,
the one-way-door piece of the call-completion batch (ADR-028):

- HandlerRegistration gains pub remote_safe: bool, defaulting false across
  all provenance (Local, Session, FromOpenAPI, FromMCP, FromCall,
  FromJsonSchema) per ADR-028 §4. HandlerRegistration::new() keeps its
  existing 6-arg signature (defaults remote_safe: false), so all current
  call sites compile unchanged.
- Chainable HandlerRegistration::remote_safe(bool) setter + a
  OperationRegistryBuilder::remote_safe() helper that marks the
  most-recently-registered op (tracked via last_name, not HashMap
  iteration order which is unspecified).
- Field is data-only here — the filtering behavior (dispatch path +
  services/list hide) is wired in call/client/call-client, not this task.
  services/list is unchanged.
- Tests: default false, setter flips field, all six provenance variants
  default false, builder setter marks last op, existing call sites
  unchanged. 178 tests pass, clippy clean.

Refs: tasks/call/registry/remote-safe-marking.md
Refs: docs/architecture/decisions/028-callclient-peer-scoped-registry-filtering.md
2026-06-26 12:51:18 +00:00
glm-5.2 2649e068e5 docs(arch): call-completion — ADR-028 peer-scoped filtering + client-and-adapters spec + tasks
Resolves the four gap-analysis decisions (DC-1..4) blocking the alknet-call
client/adapter surface specced in ADR-017:

- ADR-028 (new): locks the one-way door for DC-1 — CallClient registry is
  default-deny (remote_safe: bool on HandlerRegistration, default false across
  all provenance); share-global is an explicit trusted-peer opt-in; filtering
  is a dispatch-time read over the single Layer-0 registry, not a copy.
- client-and-adapters.md (new spec): operationally fills the gap ADR-017 left
  to implementation — CallClient, from_call, from_jsonschema, OperationAdapter
  trait, adapter location map, no-env-vars invariant, exchange-of-operations
  pattern. Keeps call-protocol.md and operation-registry.md under the
  700-line split threshold.
- ADR-017 amended: records DC-2/3/4 v1 defaults (auto-on-reconnect,
  error-on-collision, Result error type) and points DC-1 at ADR-028.
- OQ-25..28 (new): two-way-door remainders (remote_safe shape, AdapterError
  variants, re-import trigger, namespace collision) with v1 defaults recorded.
- Index/cross-ref updates across READMEs and the two existing call specs.

Tasks: 6 task files under tasks/call/ decomposing the completion work along
the gap-analysis priority order — remote-safe-marking (one-way door, first)
→ call-client (phase-risk) → from-call → operation-adapter-trait →
from-jsonschema (parallel with call-client) → review-completion. Graph
validated with taskgraph; parallelism designed in (from-jsonschema runs
concurrent with call-client/from-call once the trait lands).
2026-06-26 12:25:13 +00:00
glm-5.2 6940d9858d docs(research): alknet-http phase-0 findings — HTTP server + client + MCP adapters
Phase 0 exploration for alknet-http (greenfield crate, no existing arch):
HTTP server (axum, ProtocolHandler for h2/http1.1, h3 deferred), HTTP client
(reqwest, the from_openapi/from_mcp forwarding handlers), MCP streamable HTTP
(feature-gated, stdio excluded as security position), to_openapi/to_mcp
projections.

Records: 8 design points (DH-3 HTTP→call operation mapping as the load-bearing
one), the settled adapter location map (from alknet-call gap analysis), the
no-env-vars invariant (Capabilities → from_openapi handler → HTTP header as the
credential injection point), and the prerequisite on alknet-call's
OperationAdapter trait being defined first.
2026-06-25 12:46:25 +00:00
glm-5.2 79d8561bb4 docs(research): alknet-call completion gap analysis — CallClient + from_call + OperationAdapter
Gap analysis for completing alknet-call: the server-side core (~5.7k lines,
159 tests) is implemented, but the client side (CallClient), the bilateral
exchange mechanism (from_call), and the adapter contract (OperationAdapter
trait) are specced in ADR-017 and unimplemented.

Records: implementation state (verified against src/), 5 decisions needed
(peer-scoped registry filtering as the load-bearing one), the settled adapter
location map (trait + from_call + from_jsonschema in alknet-call; from_openapi/
from_mcp in alknet-http), the no-env-vars invariant (Capabilities → from_openapi
handler → HTTP header), and the exchange-of-operations runner pattern with
dispatch as the concrete downstream consumer.
2026-06-25 12:44:49 +00:00
glm-5.2 db1dcd362f docs(research): revise alknet-ssh phase-0 — SOCKS5+forwarding in v1, TCP listener for git-over-ssh
Incorporates user clarifications: SOCKS5 and bidirectional port forwarding are
core non-negotiable v1 features (the VPN-like use case + the 3.5k-clones
demand). Adds DP-10 for the bare-TCP SSH listener as a first-class path needed
for future git-over-SSH, with config shape reserved in v1 (off-by-default,
default-deny). Grounds the client/forwarding recommendations in the dispatch
downstream consumer at /workspace/@alkdev/dispatch, which is a textbook russh
SSH client + direct-tcpip forwarder the user wants to replace with this stack.

alknet-ssh now owns both server and client + SOCKS5-server in v1; the SOCKS5
codec may extract to a separate crate later (two-way door).
2026-06-25 08:46:35 +00:00
glm-5.2 d758a71490 docs(research): alknet-ssh phase-0 findings — stream wiring, russh 0.60.2, decision points
Phase 0 exploration for alknet-ssh: confirms SSH-over-QUIC-bistream via
tokio::io::join (no custom adapter needed, per reference impl), russh 0.60.2
generic run_stream/connect_stream, and channel-into-bistream multiplexing.

Surfaces 9 decision points for Phase 1: host key sourcing (vault-derived vs
config), channel policy v1 surface, client + SOCKS5 crate split, crypto
backend, auth method coverage, and a stream-handling POC to close russh's
upstream test gap.
2026-06-25 08:06:45 +00:00
glm-5.2 011db05a52 test: implement coverage #005 Tier-A suggestions (S1-S4, S8)
Add 165 tests covering the directly-testable surface identified in
coverage review #005. Workspace coverage rises 87.1% -> 91.2%
(5759/6615 -> 6505/7135); all 389 tests pass, clippy clean.

- S1 (connection.rs): dispatch_envelope across all five event-type arms
  for Call + Subscribe, plus SubscriptionStream poll_next branches and
  SubscriptionStream::closed.
- S2 (types.rs): map_quinn/iroh_connection_error for TimedOut/Reset/
  ApplicationClosed/other, plus HandlerError + StreamError Debug/Display/
  source for every variant.
- S3 (config.rs): Ed25519SecretKey from_bytes/as_bytes round-trip,
  sign+verify, tampered-message rejection, Debug non-leakage.
- S4 (endpoint.rs): build_rustls_server_config RawKey/SelfSigned/Acme
  arms, build_quinn_server_config_from_rustls, load_private_key/
  load_cert_chain error paths, has_iroh_identity branches,
  AcceptAnyCertVerifier trait methods, Ed25519SigningKey trait impls
  (choose_scheme both branches, algorithm, public_key, sign, scheme),
  RawKeyCertResolver + AlknetEndpoint Debug. endpoint.rs 56% -> 73%.
- S8 (vault protocol.rs): the existing redacted-deserialize test passed
  for the wrong reason (JSON string failed Vec<u8> coercion before the
  guard). Two new tests exercise the guard directly via a [REDACTED] byte
  array (rejected) and a real payload (accepted). protocol.rs -> 100%.

Deferred to follow-up: S5 (loopback quinn integration test, the real
unlock for accept/dispatch/stream paths), S6 (ACME event-loop extraction),
S7 (adapter abort arm). Review #005 updated with the resolution.
2026-06-25 05:43:59 +00:00
glm-5.2 32dcc05658 docs(reviews): add coverage analysis #005
First dedicated coverage pass (cargo-llvm-cov --workspace --all-features).
Workspace at 87.1% line coverage (5759/6615), all 224 tests pass. Vault
and registry layers are essentially fully covered; gaps concentrate in
endpoint.rs (56%), types.rs (57%), and connection.rs (54%), all stemming
from tests using MockConnection whose open_bi/accept_bi return Err.

Eight suggestions (S1-S8) ordered by leverage: pure-function tests for
dispatch_envelope / map_*_connection_error / error Display+Debug (S1-S3),
Tier A directly-callable TLS/rustls helpers in endpoint.rs (S4), one
loopback quinn integration test as the real unlock across four files (S5),
ACME event-loop extraction via synthetic stream (S6, the flagged research
item), and two small remaining gaps (S7-S8). No critical or warning
findings — this is a testing-infrastructure gap, not a logic gap.
2026-06-25 04:32:51 +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 d94d7a132a docs(adr-027): TLS identity redesign — ACME + RawKey decoupling
ADR-027 resolves the architectural gap surfaced when ACME integration
became a concrete target:

1. TlsIdentity::Acme variant — static config data (domains, cache_dir,
   directory, contact) with async AcmeState constructed at endpoint
   setup via two-phase TlsSetup (not stuffed into the Clone-able enum).

2. TlsIdentity::RawKey decoupled from the iroh feature — uses
   Ed25519SecretKey (alknet-core-owned wrapper over ed25519_dalek)
   instead of iroh::SecretKey. Raw-key TLS identity (RFC 7250, the
   default for most alknet nodes) now works in quinn-only builds.
   iroh transport converts via SecretKey::from_bytes.

3. ACME feature-gated behind new acme feature (rustls-acme optional
   dep). Non-ACME builds don't compile it.

4. dispatch_quinn guard for acme-tls/1 challenge connections — TLS-ALPN-01
   is handled at the rustls cert resolver layer during the handshake;
   the guard closes challenge connections gracefully instead of logging
   a misleading "no handler" warning.

Research confirmed QUIC (quinn) handles ACME challenges differently than
TCP (reverse-proxy): quinn gives no ClientHello peek hook, but the
challenge is fully answered at the cert resolution step before the
connection surfaces to the application. No handler registration needed.

Spec updates: config.md, endpoint.md, open-questions.md (OQ-12),
overview.md + README.md (ADR index), ADR-010 (cross-ref).

Tasks: core/rawkey-decouple-from-iroh (gen 1, no deps),
core/acme-integration (gen 2, depends on rawkey). Graph: 36 tasks.
2026-06-24 12:29:24 +00:00
glm-5.2 97216764ea fix: resolve review #004 findings W1-W4 + close review gate
W1 (call/protocol/abort-cascade-wiring): wire AbortCascade into
CallAdapter handle_stream for EVENT_ABORTED. Cascades with
AbortPolicy::AbortDependents, aborts root, no descendant frames on
wire (ADR-016 Decision 2). Two integration tests added.

W2 (core/endpoint-client-fingerprint): extract TLS client cert
fingerprint in dispatch_quinn (SHA256:<hex> of leaf cert DER via
peer_identity) and dispatch_iroh (ed25519:<hex> of peer NodeId).
Fingerprint format documented in auth.md. Server config change
(with_no_client_auth → request-but-don't-require) deferred to new
follow-up task core/endpoint-request-client-cert.

W3 (vault/mnemonic-debug-redaction): replace Mnemonic derive(Debug)
with manual redacting impl (phrase: "[REDACTED]"). Seed confirmed
no Debug impl. Redaction test added.

W4 (core/auth-apikey-resources): Option B — drop entry.resources from
spec. External identities (token/fingerprint) grant scopes only;
resource-scoped ACLs are composition-internal (ADR-015/022). auth.md
corrected + limitation documented. Two tests confirm empty resources.

review-post-impl-fixes: all 4 verified, workspace green (326 tests,
0 failures, 0 clippy warnings). Review #004 status → resolved.

Graph: 34 tasks, 12 gens.
2026-06-24 11:00:54 +00:00
glm-5.2 d149932e2a tasks: decompose review #004 findings into 4 fix tasks + review gate
W1 (call/protocol/abort-cascade-wiring): wire AbortCascade into CallAdapter
handle_stream for EVENT_ABORTED. W2 (core/endpoint-client-fingerprint):
extract TLS client cert fingerprint in dispatch_quinn/dispatch_iroh.
W3 (vault/mnemonic-debug-redaction): replace Mnemonic derive(Debug) with
redacting impl. W4 (core/auth-apikey-resources, level: research): decide
whether ApiKeyEntry should carry resources, then implement or drop from
spec. review-post-impl-fixes gates on all four. Graph: 33 tasks, 12 gens.
2026-06-24 10:02:03 +00:00
glm-5.2 d904dfc243 docs(reviews): add post-implementation sanity check #004
28-task implementation review across alknet-vault, alknet-core, alknet-call.
Zero critical findings; 4 warnings (W1: AbortCascade implemented but never
invoked by CallAdapter; W2: endpoint hard-codes tls_client_fingerprint=None;
W3: Mnemonic Debug leaks seed phrase; W4: ApiKeyEntry missing resources
field); 5 suggestions. Build clean, 332 tests pass, default clippy clean.
2026-06-23 22:13:20 +00:00
glm-5.2 2c83e31e38 tasks: mark call/review-call completed — all 28 tasks done 2026-06-23 15:55:56 +00:00
glm-5.2 4696c9a304 docs(call): record review-call pass — implementation conforms to spec (task: call/review-call)
Review of alknet-call crate against operation-registry.md, call-protocol.md,
and ADRs 005/012/014/015/016/017/022/023/024. All registry types, protocol types,
security constraints, and pattern consistency checks conformant. No source
deviations found. 159 tests pass; build/clippy/fmt/test clean.

Refs: docs/architecture/crates/call/README.md
2026-06-23 15:55:33 +00:00
glm-5.2 23b76a240a docs(call): record review-call pass — implementation conforms to registry/protocol/ADR spec
call/review-call verified the alknet-call crate against operation-registry.md,
call-protocol.md, and ADRs 005/012/014/015/016/017/022/023/024. All
registry types, protocol types, security constraints (Capabilities
non-serializable/zeroized/immutable, metadata non-propagation, internal
ops -> NOT_FOUND, reachability bounds, UUID v4 request IDs), and pattern
consistency (OperationEnv trait, CompositeOperationEnv contains-probe,
authority switch, deadline inheritance) are conformant. 159 tests green;
build/clippy(fmt check) clean. No source changes required.
2026-06-23 15:55:10 +00:00
glm-5.2 93589d4f52 tasks: mark call/protocol/abort-cascade completed 2026-06-23 15:50:38 +00:00
glm-5.2 8aa384ccfa feat(call): implement abort cascade for nested calls (ADR-016) (task: call/protocol/abort-cascade)
Implement AbortCascade in protocol/abort.rs per ADR-016: PendingEntry stores
parent_request_id (Call & Subscribe) and a started flag for tree indexing.
AbortCascade::cascade_abort walks the call tree by parent_request_id and aborts
descendants per AbortPolicy (AbortDependents aborts all; ContinueRunning aborts
only unstarted via mark_started()). Returns sorted list of aborted IDs; unknown
root silently discarded. 20 unit tests covering depth-3 cascade, mixed
Call/Subscribe, determinism, both policies.

Refs: docs/architecture/crates/call/call-protocol.md
Implements: ADR-016
2026-06-23 15:50:05 +00:00
glm-5.2 3317bc8d1a feat(call): implement abort cascade for nested calls (ADR-016) (task: call/protocol/abort-cascade)
- PendingEntry stores parent_request_id (Call and Subscribe) and started flag
  for abort-cascade tree indexing
- register_call/register_subscribe accept optional parent_request_id
- AbortCascade::cascade_abort walks the call tree by parent_request_id and
  aborts descendants per AbortPolicy (AbortDependents: all; ContinueRunning:
  unstarted only). Returns sorted list of aborted request IDs
- call.aborted for unknown request_id silently discarded (empty result)
- Composed child request_ids stay internal (not sent as call.requested)
- mark_started() tracks dispatch state for ContinueRunning decisions
- 20 unit tests covering AbortDependents/ContinueRunning, depth-3 tree,
  unknown root, mixed Call/Subscribe, determinism
2026-06-23 15:49:07 +00:00
glm-5.2 bea19de3cf tasks: mark call/protocol/call-adapter completed 2026-06-23 15:39:54 +00:00
glm-5.2 2ff09a728c feat(call): implement CallAdapter — ProtocolHandler for alknet/call (task: call/protocol/call-adapter)
Implement CallAdapter in protocol/adapter.rs: ProtocolHandler for ALPN
alknet/call with stream handling, per-request identity resolution (auth_token
overrides connection identity, falls back on failure), root context construction
(internal:false, deadline, capabilities+scoped_env from registration bundle),
env composition (CompositeOperationEnv with Layer 0 base + Layer 2 connection
overlay + optional Layer 1 session overlay), operationId leading slash stripped,
ResponseEnvelope→EventEnvelope conversion, PendingRequestMap sweeper, connection
drop fails all pending. SessionOverlaySource trait. 22 unit tests.

Refs: docs/architecture/crates/call/call-protocol.md
Implements: ADR-002, ADR-012, ADR-014, ADR-015, ADR-017, ADR-022, ADR-024
2026-06-23 15:39:28 +00:00
glm-5.2 fc9f93e893 feat(call): implement CallAdapter (ProtocolHandler for alknet/call) with stream handling, identity resolution, root context construction (task: call/protocol/call-adapter)
- CallAdapter struct with registry, identity_provider, session_source, default_timeout (30s)
- new(), with_session_source(), with_timeout() constructors
- SessionOverlaySource trait defined (overlay_for) for agent-crate integration
- ProtocolHandler::alpn() returns b"alknet/call"
- handle() sets connection identity from AuthContext, spawns accept_bi loop,
  reads EventEnvelope frames via FrameFramedReader, dispatches call.requested
  to the operation registry, writes ResponseEnvelope as EventEnvelope via
  FrameFramedWriter
- Per-request identity resolution: AuthContext.identity used by default,
  auth_token in payload overrides via IdentityProvider::resolve_from_token();
  resolution failure falls back to connection-level identity
- build_root_context sets internal: false, deadline (now + default_timeout),
  capabilities and scoped_env from registration bundle, parent_request_id: None
- compose_root_env builds CompositeOperationEnv (Layer 0 curated base +
  Layer 2 connection overlay + optional Layer 1 session overlay)
- operationId leading slash stripped before registry lookup
- ResponseEnvelope -> EventEnvelope conversion (Ok -> call.responded,
  Err -> call.error)
- PendingRequestMap sweeper runs every 10s, evicts expired wire entries
- Connection drop: fail_all pending with INTERNAL "connection closed",
  return Ok(())
- Stream reset: FrameFramedReader error closes stream; other streams unaffected
- Handler panic: stream task isolated via tokio::spawn, sweep cleans entry
- Tests: alpn, constructors, slash strip, identity resolution (override/fallback),
  root context (internal=false, deadline, capabilities, scoped_env), env
  composition (layers aggregate, session overlay), dispatch round-trip,
  internal op from wire -> NOT_FOUND, ACL denied -> FORBIDDEN, auth_token
  overrides connection identity, unknown op -> NOT_FOUND, no-slash resolution,
  ResponseEnvelope -> EventEnvelope conversions
2026-06-23 15:38:50 +00:00
glm-5.2 0d0f0f8da6 tasks: mark core/review-core completed 2026-06-23 15:33:16 +00:00
glm-5.2 061069910b fix(core): gate RawKey TLS helpers on quinn+iroh for clean iroh-only builds (task: core/review-core)
Review of alknet-core found one issue: RawKeyCertResolver/Ed25519SigningKey/
std::path::Path were gated on #[cfg(feature = "iroh")] but only used in the
quinn TLS path — caused clippy -D warnings failures on iroh-only builds.
Re-gated to #[cfg(all(feature = "quinn", feature = "iroh"))]. All 4 feature
combinations now pass clippy -D warnings; 55 tests pass.

Review confirmed: all core types, config, auth, and endpoint implementations
are spec-conformant.
2026-06-23 15:32:23 +00:00
glm-5.2 4774364c72 fix(core): gate RawKey TLS helpers on quinn+iroh for clean iroh-only builds (task: core/review-core)
The RawKeyCertResolver, Ed25519SigningKey, and std::path::Path imports
were gated on #[cfg(feature = "iroh")] but are only used in the quinn
TLS server-config path (build_rustls_server_config RawKey arm). With
iroh-only builds (--no-default-features --features iroh), these became
dead code and triggered clippy -D warnings failures.

Re-gated to #[cfg(all(feature = "quinn", feature = "iroh"))] so they
only compile when both features are active (the combination that
actually uses raw-key TLS via quinn). std::path::Path is now
#[cfg(feature = "quinn")] since it is only used by quinn's
load_cert_chain/load_private_key helpers.

Verified: cargo clippy passes with -D warnings across all four feature
combinations (none, quinn, iroh, quinn+iroh). cargo test --all-features
passes 55 tests. cargo fmt --check clean.
2026-06-23 15:31:42 +00:00
glm-5.2 60556bbe0c tasks: mark core/endpoint and call/protocol/call-connection completed 2026-06-23 15:18:56 +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 7b92749acd tasks: mark core/endpoint completed 2026-06-23 15:16:33 +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 79bc6ffb31 feat(core): implement AlknetEndpoint, HandlerRegistry, accept loops, TLS identity, graceful shutdown (task: core/endpoint)
Implement the ALPN router and endpoint in endpoint.rs: AlknetEndpoint with
quinn+iroh accept loops (both feature-gated, both Option), HandlerRegistry
(new/register/get/alpn_strings with panic-on-duplicate), dispatch via
tokio::spawn by ALPN, AuthContext construction from connection
(alpn/remote_addr/fingerprint/identity), TLS identity modes (RawKey RFC 7250
via on-the-fly cert resolver, X509 from files, SelfSigned via rcgen),
EndpointError enum, graceful shutdown with drain timeout + force close.
ACME deferred as TODO per task spec. 55 tests (--all-features), 52 (default),
47 (no-default); clippy clean across all 3 feature combos.

Refs: docs/architecture/crates/core/endpoint.md
Implements: ADR-010
2026-06-23 15:14:26 +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 3484373d84 tasks: mark call/registry/service-discovery completed 2026-06-23 14:55:42 +00:00
glm-5.2 8cc16de9f0 feat(call): implement services/list and services/schema built-in operations (task: call/registry/service-discovery)
Implement services/list and services/schema in registry/discovery.rs: spec
constructors, factory handlers taking Arc<OperationRegistry>, JSON serialization
of OperationSpec (incl. error_schemas per ADR-023), leading-slash normalization
for services/schema, NOT_FOUND for unknown ops, INVALID_INPUT for missing name.
Both registered as Local provenance with empty authority/env/caps and empty
AccessControl.

Refs: docs/architecture/crates/call/operation-registry.md
Implements: ADR-023
2026-06-23 14:55:09 +00:00
glm-5.2 bb4e32e849 feat(call): implement services/list and services/schema built-in operations (task: call/registry/service-discovery) 2026-06-23 14:54:17 +00:00
glm-5.2 4f10af2295 tasks: mark call/registry/operation-env completed 2026-06-23 14:53:42 +00:00
glm-5.2 99ef22db3e feat(call): implement LocalOperationEnv and CompositeOperationEnv (task: call/registry/operation-env)
Implement LocalOperationEnv (Layer 0 — Arc<OperationRegistry>, reachability check,
authority switch, fresh metadata, inherited deadline/env) and CompositeOperationEnv
(session → connection → base overlay dispatch via contains() probe per ADR-024).
ScopedOperationEnv checked before overlay dispatch. 13 new env tests (93 total).

Refs: docs/architecture/crates/call/operation-registry.md
Implements: ADR-024
2026-06-23 14:53:05 +00:00
glm-5.2 7e824af022 feat(call): implement LocalOperationEnv and CompositeOperationEnv (task: call/registry/operation-env)
Expand the minimal OperationEnv trait from the operation-context task with
concrete dispatch implementations per ADR-024:

- LocalOperationEnv (Layer 0): wraps Arc<OperationRegistry>. invoke_with_policy
  runs the scoped_env reachability check (ADR-015/022), looks up the
  registration, then constructs a child OperationContext with internal: true,
  identity = parent.handler_identity.as_identity() (the ADR-015 authority
  switch), fresh metadata (HashMap::new() — ADR-014 security constraint, no
  parent metadata propagation), inherited deadline (parent.deadline, not a
  fresh 30s), inherited env (parent.env.clone() — Arc::clone per ADR-024), and
  the child's own composition_authority + scoped_env from its registration.
  contains() uses the default impl (returns true — curated registry contains
  everything it can dispatch).

- CompositeOperationEnv (per-call, ADR-024): composes session (Layer 1),
  connection (Layer 2), and base (Layer 0) trait objects. invoke_with_policy
  runs the same reachability check, then probes overlays in order via
  contains() (the overlay-dispatch contract from review #003 C9), dispatching
  to the first overlay that contains the op. contains() aggregates all layers.

The trait-object design is load-bearing: making OperationEnv concrete would
close the session-overlay and connection-overlay patterns. Same integration-
point pattern as IdentityProvider (ADR-004).

Tests cover: allowed/disallowed reachability, internal-flag propagation,
authority switch (child identity = parent handler_identity), fresh metadata,
inherited deadline, composite session-overlay dispatch, composite fall-through
to base, composite connection-overlay dispatch when session lacks op, and
composite contains aggregation.
2026-06-23 14:51:48 +00:00
glm-5.2 31fd8a73ac tasks: mark call/registry/handler-registration completed 2026-06-23 14:42:01 +00:00
glm-5.2 3aae9d1323 feat(call): implement handler registration and operation registry (task: call/registry/handler-registration)
Implement Handler, HandlerRegistration, OperationProvenance, OperationRegistry
(register/registration/invoke/list_operations), and OperationRegistryBuilder
(new/with_local/with_leaf/with_leaf_provenance/with/build) in registry/registration.rs.
invoke enforces visibility (Internal from wire → NOT_FOUND), ACL with authority
switch (internal: true → handler_identity.as_identity(), internal: false → caller
identity), and handler dispatch. 21 unit tests.

Refs: docs/architecture/crates/call/operation-registry.md
Implements: ADR-015, ADR-022, ADR-024
2026-06-23 14:40:50 +00:00
glm-5.2 7345ef5442 Implement handler registration and operation registry
Implements the dispatch core for the call protocol per ADR-022 and ADR-024:

- Handler async closure type alias returning ResponseEnvelope
- HandlerRegistration bundle: spec, handler, provenance, composition
  authority, scoped env, capabilities
- OperationProvenance enum with all 6 variants (Local, FromOpenAPI,
  FromMCP, FromCall, FromJsonSchema, Session)
- OperationRegistry with register/registration/invoke/list_operations
- invoke flow: visibility check (Internal from wire -> NOT_FOUND),
  ACL with authority switch (internal: true -> handler_identity,
  internal: false -> caller identity), handler dispatch
- OperationRegistryBuilder with new/with_local/with_leaf/with_leaf_provenance/with/build
- make_handler helper for boxing async handlers
- 21 unit tests covering invoke, visibility, ACL authority switch,
  builder provenance, and lookup behavior
2026-06-23 14:40:13 +00:00
glm-5.2 c2c88833db tasks: mark call/registry/operation-context completed 2026-06-23 14:30:37 +00:00
glm-5.2 fbc30d281e feat(call): implement OperationContext, AbortPolicy, CompositionAuthority, ScopedOperationEnv (task: call/registry/operation-context)
Implement operation context types in registry/context.rs: OperationContext (10
fields, internal pub(crate) + is_internal()), AbortPolicy (AbortDependents
default), CompositionAuthority (none/new/as_identity for ACL), ScopedOperationEnv
(empty/new/allows), generate_request_id (UUID v4). Added minimal OperationEnv
trait in registry/env.rs (invoke/invoke_with_policy/contains) so the env field
compiles — operation-env task will expand with LocalOperationEnv and
CompositeOperationEnv. 37 unit tests.

Refs: docs/architecture/crates/call/operation-registry.md
Implements: ADR-015, ADR-022, ADR-024
2026-06-23 14:29:18 +00:00
glm-5.2 3b9c480dad Implement OperationContext, AbortPolicy, CompositionAuthority, ScopedOperationEnv
Implements the operation context types in registry/context.rs (ADR-015,
ADR-022, ADR-024): OperationContext with all 10 fields (internal is
pub(crate) for writes, read via is_internal()), AbortPolicy enum with
AbortDependents default, CompositionAuthority with synthetic Identity
projection for ACL, ScopedOperationEnv reachability set, and
generate_request_id() (UUID v4). Adds a minimal OperationEnv trait
forward-declaration in registry/env.rs so the context env field compiles;
the operation-env task will expand it.
2026-06-23 14:27:46 +00:00
glm-5.2 de91f3bdb0 tasks: mark call/protocol/pending-request-map completed 2026-06-23 14:25:58 +00:00
glm-5.2 f92e7af13f feat(call): implement PendingRequestMap for call correlation (task: call/protocol/pending-request-map)
Implement PendingRequestMap in protocol/pending.rs with Call (oneshot) and
Subscribe (mpsc) entries, ID-based correlation (ADR-012), timeout-based eviction,
fail_all for connection close, and silent discard of unknown request IDs. 17 unit
tests.

Refs: docs/architecture/crates/call/call-protocol.md
Implements: ADR-012
2026-06-23 14:24:44 +00:00
glm-5.2 e63a36ede0 Implement PendingRequestMap for call protocol
Correlates call.responded events back to call.requested by request ID
(stream-agnostic per ADR-012). Manages Call (oneshot) and Subscribe
(mpsc) entries with timeout-based eviction and fail_all on connection
close. Unknown request IDs are silently discarded.
2026-06-23 14:23:49 +00:00
glm-5.2 dabb0d8b68 tasks: mark vault/spec-sync-remove-drift completed 2026-06-23 14:17:39 +00:00
glm-5.2 d0f633c71d docs(vault): remove drift tracking artifacts, bump vault docs to stable (task: vault/spec-sync-remove-drift)
Remove the Known Source Drift table from vault/README.md. Remove all 'known
drift'/'current source uses X' prose from Security Constraints in README,
encryption.md, service.md (constraint statements preserved). Remove stale
ADR-025/postcard notes in protocol.md. Bump all 5 vault doc frontmatter to
status: stable. Update architecture/README.md vault doc statuses to stable
and Current State to remove 'pending ADR-025/026 refactor' language.

Refs: docs/architecture/crates/vault/README.md (drift cleanup)
2026-06-23 14:17:10 +00:00
glm-5.2 482901db74 tasks: mark core/config completed 2026-06-23 14:16:38 +00:00
glm-5.2 323ee85d40 docs(vault): remove drift tracking artifacts, bump vault docs to stable
The vault spec-to-implementation sync is complete. Remove the drift
tracking tools that were only needed during sync:

- Remove the Known Source Drift table from vault/README.md
- Remove 'known drift' / 'current source uses X' prose from Security
  Constraints sections in vault/README.md, encryption.md, and service.md.
  The permanent constraint statements (OsRng for IVs, zeroized drop,
  no unwrap, etc.) are preserved.
- Remove the drift paragraph in encryption.md Key Versioning.
- Remove stale 'to be updated per ADR-025' / 'postcard tests to be
  removed' notes in protocol.md References.
- Bump status: draft -> stable in the frontmatter of all vault docs
  (README, mnemonic-derivation, encryption, service, protocol).
- Update architecture/README.md: vault doc status entries to stable,
  Current State paragraph reflects vault implementation complete (no
  'pending ADR-025/026 refactor' language).
2026-06-23 14:15:13 +00:00
glm-5.2 da5646bf46 feat(core): implement StaticConfig, DynamicConfig, AuthPolicy, ApiKeyEntry, RateLimitConfig, ConfigReloadHandle, TlsIdentity, ConfigError (task: core/config)
Implement all configuration types in config.rs: StaticConfig (drain_timeout=2s
default), TlsIdentity (X509/RawKey[iroh-gated]/SelfSigned), DynamicConfig
(Clone/Debug/Default, ArcSwap-reloadable), AuthPolicy (String fingerprints, no
russh), ApiKeyEntry (5 fields), RateLimitConfig (100/5 defaults),
ConfigReloadHandle (reload/dynamic via ArcSwap), ConfigError (thiserror, all
variants). iroh_relay and RawKey feature-gated to iroh. 14 unit tests.

Refs: docs/architecture/crates/core/config.md
Implements: ADR-003, ADR-010

# Conflicts:
#	crates/alknet-core/src/config.rs
2026-06-23 14:14:51 +00:00
glm-5.2 e98cfa77d8 Implement core/config: StaticConfig, DynamicConfig, AuthPolicy, ApiKeyEntry, RateLimitConfig, ConfigReloadHandle, TlsIdentity, ConfigError
- StaticConfig: immutable startup config (listen_addr, tls_identity, iroh_relay, drain_timeout=2s)
- TlsIdentity enum: X509, RawKey (iroh feature-gated), SelfSigned
- DynamicConfig: hot-reloadable via ArcSwap (auth + rate_limits), derives Clone/Debug/Default
- AuthPolicy: authorized_fingerprints (HashSet<String>), api_keys (Vec<ApiKeyEntry>) — no russh dep
- ApiKeyEntry: prefix/hash/scopes/description/expires_at
- RateLimitConfig: max_connections_per_ip=100, max_auth_attempts=5
- ConfigReloadHandle: reload() atomic swap, dynamic() load_full
- ConfigError: thiserror enum with all variants

14 unit tests covering defaults, construction, atomic reload swap, and error displays.
2026-06-23 14:11:07 +00:00
glm-5.2 b93a85a280 tasks: mark vault/review-vault-sync and core/auth completed 2026-06-23 14:10:54 +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 20b5c640ec tasks: mark call/protocol/wire-types completed 2026-06-23 14:08:50 +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 55404e52a3 feat(call): implement wire protocol types and framing (task: call/protocol/wire-types)
Implement EventEnvelope, ResponseEnvelope, CallError, FrameError, and
FrameFramedReader/FrameFramedWriter with 4-byte big-endian length-prefixed JSON
framing in protocol/wire.rs. Added ResponseEnvelope helpers (ok/error/not_found/
forbidden) and ResponseEnvelope→EventEnvelope conversion. 20 unit tests.

Refs: docs/architecture/crates/call/call-protocol.md
Implements: ADR-005, ADR-012, ADR-023
2026-06-23 14:08:03 +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 c9898566b9 Implement call protocol wire types and framing
Implements src/protocol/wire.rs with:
- EventEnvelope (type/id/payload, JSON wire format with leading-slash op ids)
- ResponseEnvelope and CallError (with optional typed details, ADR-023)
- ResponseEnvelope::ok/error/not_found/forbidden helpers
- ResponseEnvelope -> EventEnvelope conversion (Ok -> call.responded, Err -> call.error)
- FrameFramedReader / FrameFramedWriter: 4-byte big-endian length-prefixed JSON frames
- FrameError: Io, Json, ConnectionClosed, InvalidFrame
- 20 unit tests covering round-trip, large payloads, truncated frames, helpers

Builds on the call/crate-init skeleton. See
docs/architecture/crates/call/call-protocol.md and ADR-005/012/023.
2026-06-23 14:06:48 +00:00
glm-5.2 e0ccdc28ac tasks: mark call/registry/operation-spec completed 2026-06-23 14:06:26 +00:00
glm-5.2 6d536a3bf5 feat(call): implement OperationSpec, AccessControl, Visibility, ErrorDefinition (task: call/registry/operation-spec)
Implement operation specification types in registry/spec.rs: OperationSpec (with
path() returning /{name}, namespace derived from name), OperationType (Query,
Mutation, Subscription), Visibility (External, Internal), ErrorDefinition (ADR-023),
AccessControl::check returning AccessResult (AND/OR scope checks, resource checks,
None identity → 'authentication required', empty ACL → Allowed). 9 unit tests.

Refs: docs/architecture/crates/call/operation-registry.md
Implements: ADR-015, ADR-023
2026-06-23 14:04:42 +00:00
glm-5.2 b46fc81dc5 Implement OperationSpec, AccessControl, Visibility, ErrorDefinition 2026-06-23 14:03:27 +00:00
glm-5.2 669feab741 tasks: mark core/core-types completed 2026-06-23 13:54:28 +00:00
glm-5.2 96938092ca feat(core): implement core types — ProtocolHandler, Connection, Capabilities (task: core/core-types)
Implement all core types in types.rs: ProtocolHandler trait (alpn + handle),
HandlerError (4 variants), Connection (quinn/iroh feature-gated enum dispatch,
OnceLock write-once identity, accept_bi/open_bi/close/remote_alpn/remote_addr),
BiStream trait, SendStream/RecvStream AsyncWrite/AsyncRead wrappers, StreamError,
From<StreamError> for HandlerError, Capabilities (Zeroize+ZeroizeOnDrop, immutable
builder API, Secret<String> wrapper, non-serializable), IdentityAlreadySet. Added
minimal Identity/AuthContext in auth.rs as foundation for the auth task.

Notable: quinn::Connection has no alpn() accessor so ALPN is stored separately
(from_quinn_with_alpn); iroh 0.35 types accessed via iroh::endpoint::*; iroh
Connection has no remote_address (returns None per spec).

Refs: docs/architecture/crates/core/core-types.md
Implements: ADR-002, ADR-007, ADR-014, ADR-022
2026-06-23 13:53:47 +00:00
glm-5.2 8611935f1a feat(core): implement core types (ProtocolHandler, Connection, Capabilities)
Implements the foundational types in alknet-core/src/types.rs per the
core-types architecture (ADRs 002, 007, 014, 022):

- ProtocolHandler trait (alpn + async handle) with HandlerError
- Connection wrapping quinn/iroh via internal enum dispatch (feature-gated);
  write-once identity via OnceLock, IdentityAlreadySet on second set
- SendStream/RecvStream concrete wrappers implementing AsyncWrite/AsyncRead
- BiStream convenience trait
- StreamError with canonical From<StreamError> for HandlerError
- Capabilities: non-serializable, Zeroize + ZeroizeOnDrop, immutable builder
  API (new/with_api_key/with_http_token/get) backed by a Secret<String> wrapper
- Minimal Identity and AuthContext in auth.rs as the foundation the auth task
  extends

13 unit tests cover Capabilities (build/get/clone/zeroize/redaction) and
Connection::set_identity (once succeeds, twice errors). Verified across
feature combos (default, no-default, iroh-only): build, clippy -D warnings,
test, fmt --check all clean.

(task: core/core-types)
2026-06-23 13:49:12 +00:00
glm-5.2 016c30691d tasks: mark call/crate-init completed 2026-06-23 13:47:49 +00:00
glm-5.2 51f80e90bb feat(call): initialize alknet-call crate skeleton (task: call/crate-init)
Create crates/alknet-call with Cargo.toml (deps: alknet-core path, irpc workspace,
tokio, serde, serde_json, async-trait, tracing, thiserror, uuid, futures), src/lib.rs,
and module skeletons for registry (spec, context, registration, env, discovery) and
protocol (wire, pending, connection, adapter, abort). Added to workspace members.

Refs: docs/architecture/crates/call/README.md
Implements: ADR-003, ADR-005
2026-06-23 13:46:11 +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 968e3a09ee tasks: mark vault/key-versioning-rotation completed 2026-06-23 13:39:37 +00:00
glm-5.2 9eab93100e vault: version-indexed encryption key paths, bump CURRENT_KEY_VERSION to 2, add rotate (task: vault/key-versioning-rotation)
Drift items #3, #9, #10: implement the version-indexed key rotation mechanism
(ADR-021). Bump CURRENT_KEY_VERSION to 2 (HD-derived per ADR-020). Add
encryption_path_for_version in derivation.rs, derive_encryption_key_for_version
+ version-aware encrypt/decrypt + rotate method on VaultServiceHandle. Each
version maps to a distinct derivation path; the blob carries its own version.

Refs: docs/architecture/crates/vault/README.md drift #3, #9, #10
Implements: ADR-020, ADR-021

# Conflicts:
#	crates/alknet-vault/src/derivation.rs
#	crates/alknet-vault/src/service.rs
2026-06-23 13:39:05 +00:00
glm-5.2 25327b41d4 tasks: mark vault/remove-password-derivation, vault/unlock-new-zeroizing-return, vault/poisoned-lock-recovery completed 2026-06-23 13:36:49 +00:00
glm-5.2 bc8e329f90 vault: replace unwrap() on RwLock with poisoned-lock recovery (task: vault/poisoned-lock-recovery)
Drift item #2: replace all .read().unwrap()/.write().unwrap() calls in
VaultServiceHandle with .unwrap_or_else(|e| e.into_inner()) to recover from
poisoned locks instead of bricking the vault. Added test_poisoned_lock_recovery
that poisons the lock via a panicking thread and verifies the vault remains
usable.

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

# Conflicts:
#	crates/alknet-vault/src/service.rs
2026-06-23 13:35:53 +00:00
glm-5.2 55d356cb4e feat(vault): version-indexed encryption key paths, CURRENT_KEY_VERSION=2, rotate method (ADR-021)
- Bump CURRENT_KEY_VERSION from 1 to 2 (v1 reserved for TS PBKDF2 legacy per ADR-020)
- Add derivation::encryption_path_for_version(version) -> m/74'/2'/0'/{version-2}', returns InvalidPath for version < 2
- Add VaultServiceHandle::derive_encryption_key_for_version(version), cached by path, returns InvalidPath for version < 2
- encrypt/decrypt now derive at encryption_path_for_version(key_version) instead of fixed PATHS::ENCRYPTION
- Add VaultServiceHandle::rotate(encrypted, to_version): decrypt old, re-encrypt new
- Update existing tests to use v2; add round-trip, rotation, partial-rotation, and invalid-version tests

Task: vault/key-versioning-rotation
2026-06-23 13:35:44 +00:00
glm-5.2 ad1174b485 vault: change unlock_new return type to Zeroizing<String> (task: vault/unlock-new-zeroizing-return)
Drift item #8: the mnemonic phrase is the root of trust — it must not linger in
freed heap memory. Changed unlock_new return from String to Zeroizing<String>
(zeroized on drop). Existing tests work via Deref coercion.

Refs: docs/architecture/crates/vault/README.md drift #8
Implements: ADR-025 (resolves W7)
2026-06-23 13:33:55 +00:00
glm-5.2 aec4bc9b87 refactor(vault): remove derive_password and site_password_path (task: vault/remove-password-derivation)
Drift item #7: remove the password-manager pattern (derive_password,
derive_password_string, site_password_path) — not relevant to an RPC system's
vault. Removed methods, path function, doc-table row, all tests, and the
now-unused base64 URL_SAFE_NO_PAD import.

Refs: docs/architecture/crates/vault/README.md drift #7
Implements: ADR-025 (resolves C9)
2026-06-23 13:33:36 +00:00
glm-5.2 9045dd83d3 vault: replace RwLock unwrap with poisoned-lock recovery
Replace all .read().unwrap() and .write().unwrap() calls in
VaultServiceHandle methods with .unwrap_or_else(|e| e.into_inner())
so a panic while holding the lock does not brick the vault for all
subsequent operations. Add unit test that poisons the lock and
verifies the next call recovers.
2026-06-23 13:33:00 +00:00
glm-5.2 685413dee4 vault: return Zeroizing<String> from unlock_new
Change unlock_new return type from String to Zeroizing<String>
so the generated mnemonic phrase is zeroized on drop and does not
linger in freed heap memory. Resolves drift item #8 / review W7.
2026-06-23 13:33:00 +00:00
glm-5.2 06b715322a refactor(vault): remove derive_password and site_password_path (ADR-025)
Drop the password-manager pattern from alknet-vault (drift item #7,
ADR-025, resolves review #002 C9). Site-specific password derivation
is not relevant to an RPC system's vault.

Removed:
- derive_password method from VaultServiceHandle (service.rs)
- derive_password_string method from VaultServiceHandle (service.rs)
- site_password_path function from derivation.rs
- site-password derivation path row from derivation.rs doc table
- All password-derivation tests from service.rs and derivation.rs
- Now-unused base64 URL_SAFE_NO_PAD import from service.rs
2026-06-23 13:32:45 +00:00
glm-5.2 1ac5585f84 tasks: mark vault/derivedkey-serialization completed 2026-06-23 13:32:35 +00:00
glm-5.2 68d2068f36 vault: always-redact DerivedKey serialization, reject redacted payloads on deserialize (task: vault/derivedkey-serialization)
Drift item #5: replace DerivedKey's dual serialization behavior (JSON redacts,
binary preserves) with always-redact. Custom Serialize always redacts private_key
as "[REDACTED]"; custom Deserialize rejects "[REDACTED]" payloads with an
explicit error. Dropped the is_human_readable() branch that preserved bytes in
binary formats (postcard path removed by ADR-025). Debug impl already redacted.

Refs: docs/architecture/crates/vault/README.md drift #5
Implements: ADR-025 (resolves W8)
2026-06-23 13:31:19 +00:00
glm-5.2 bd4c2bc268 vault: always-redact DerivedKey serialization, reject redacted payloads on deserialize
Replace derived Deserialize with a custom impl that rejects
private_key == b"[REDACTED]" with an explicit error, and make the
custom Serialize impl always redact (drop the human-readable-only
branch). Updates the redaction-rejection and debug-no-leak tests.

Resolves drift item #5 (ADR-025 dropped the postcard/remote path).
2026-06-23 13:30:21 +00:00