tasks: decompose Phase 0a ADR foundation and mark prior tasks completed

Add 10 new tasks under tasks/architecture/ for Phase 0a (ADR writing):
- 9 ADR tasks (026-034) with dependency-ordered structure
- 1 review checkpoint task before Phase 0b spec writing

ADR dependency graph (3 generations):
  Gen 1 (parallel): 026, 029, 030, 031, 032, 034
  Gen 2 (depends on 029): 027, 028
  Gen 3 (depends on 027+028): 033
  Gen 4: review checkpoint

Also mark all 34 prior implementation tasks as completed — they
were finished but still showing as pending in the taskgraph.
This commit is contained in:
2026-06-07 08:55:33 +00:00
parent 6db1266672
commit 5c820a41e9
37 changed files with 538 additions and 27 deletions

View File

@@ -0,0 +1,63 @@
---
id: architecture/adr-026-transport-interface-separation
name: Write ADR-026 — Transport/interface separation (three-layer model)
status: pending
depends_on: []
scope: moderate
risk: high
impact: project
level: implementation
---
## Description
Write ADR-026 establishing the three-layer model: Transport (Layer 1), Interface (Layer 2), Protocol (Layer 3). This is the most architecturally significant new ADR — it redefines SSH as an interface (not a transport) and enables the DNS control channel, raw framing, and future WebTransport as (Transport, Interface) pairs.
The three layers:
- **Layer 1: Transport** — produces byte streams. TCP, TLS, iroh, DNS (as byte carrier), WebTransport. A `Transport` still produces `AsyncRead + AsyncWrite + Unpin + Send`.
- **Layer 2: Interface** — consumes a `Transport::Stream` and produces call protocol events (sessions). SSH is an interface. Raw framing (4-byte length prefix + JSON EventEnvelope) is an interface. DNS control channel is a (DNS transport, raw framing interface) pair.
- **Layer 3: Protocol** — carries semantics. Call protocol events, operation registry, service calls. Protocol is agnostic to both Transport and Interface below it.
A **connection** is always a (Transport, Interface) pair. The valid combinations are enumerated:
- (TLS, SSH) — standard alknet tunnel
- (TCP, SSH) — plain SSH tunnel
- (iroh, SSH) — P2P SSH tunnel
- (DNS, raw framing) — DNS control channel
- (WebTransport, SSH) — browser SSH tunnel (future)
- (WebTransport, raw framing) — browser call protocol (future)
- (TCP, raw framing) — direct call protocol, local mesh
Key changes from current architecture:
- SSH is an interface, not a transport. Currently deeply embedded in ServerHandler.
- The `TransportKind` enum gains `Dns` and `WebTransport` variants (initially tags only).
- Raw framing (4-byte BE length prefix + JSON) is an interface without SSH wrapping.
- DNS control channel carries call protocol frames directly — it does NOT wrap SSH inside DNS.
This ADR requires careful review because it's the foundation for Phase 1.8 (Interface Abstraction), which is the most invasive code change.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/026-transport-interface-separation.md` exists
- [ ] ADR follows established format
- [ ] Context explains why SSH is currently tangled with transport and why separating them matters (enables DNS, raw framing, WebTransport without SSH)
- [ ] Decision states: three layers; SSH is Layer 2 not Layer 1; Transport trait produces byte streams unchanged; Interface trait consumes Transport::Stream and produces call protocol sessions; connection = (Transport, Interface) pair; valid pairs enumerated
- [ ] Shows the Interface trait signature (consume stream, produce sessions)
- [ ] Lists the valid (Transport, Interface) combinations
- [ ] Consequences: enables DNS control channel without SSH wrapping; enables raw framing for service mesh; SSH becomes pluggable; ServerHandler is refactored into SshInterface
- [ ] DNS control channel carries call protocol directly (NOT SSH inside DNS) — explicitly stated
- [ ] References: research/core.md DNS section, integration-plan.md Phase 1.8
## References
- docs/research/core.md — transport layer, DNS transport section
- docs/research/integration-plan.md — Phase 1.8, three-layer model, DNS as (DNS transport, raw framing interface)
- docs/architecture/transport.md — current Transport trait (unchanged at Layer 1)
- docs/architecture/server.md — current ServerHandler (will become SshInterface)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,57 @@
---
id: architecture/adr-027-crate-decomposition
name: Write ADR-027 — Crate decomposition
status: pending
depends_on:
- architecture/adr-029-identity-core-type
scope: moderate
risk: medium
impact: project
level: implementation
---
## Description
Write ADR-027 defining the crate decomposition for the alknet project: what crates exist, what each contains, and crucially what the dependency graph looks like (which must be acyclic).
Crate structure:
- **alknet-core**: transport, SSH, call protocol, config, auth types, identity, OperationSpec, Interface trait. Depends on: russh, tokio, irpc (feature-gated), serde. Does NOT depend on: alknet-secret, alknet-storage, alknet-flowgraph.
- **alknet-secret**: BIP39, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM, SecretProtocol irpc service. Depends on: bip39, ed25519-bip32 (or rust-bip32-ed25519), aes-gcm, sha2, irpc. Does NOT depend on: alknet-core, alknet-storage.
- **alknet-storage**: SQLite-backed metagraph, identity tables, ACL graph, honker integration, StorageProtocol irpc service. Depends on: rusqlite, honker, petgraph, jsonschema, irpc. Does NOT depend on alknet-core (but implements alknet-core's IdentityProvider trait via the trait, not a crate dep). Does NOT depend on alknet-secret (but references EncryptedData type format).
- **alknet-flowgraph**: FlowGraph<N,E> over petgraph, operation graph, call graph, type compatibility. Depends on: petgraph, serde, jsonschema. Does NOT depend on: alknet-core, alknet-storage, alknet-secret.
- **alknet-napi**: Node.js native addon. Depends on: alknet-core.
- **alknet** (CLI binary): Assembles everything. Depends on: alknet-core, alknet-secret (feature), alknet-storage (feature), alknet-flowgraph (feature), toml.
The narrow interface points: `Identity` type, `IdentityProvider` trait, and `OperationSpec` are in alknet-core. External crates implement core traits or serialize to formats core understands.
This ADR must also address the irpc feature flag question (OQ: resolved — irpc is behind a feature flag in alknet-core, independent in other crates) and the storage/secret irpc dependency question (resolved — each crate depends on irpc independently).
## Acceptance Criteria
- [ ] `docs/architecture/decisions/027-crate-decomposition.md` exists
- [ ] ADR follows established format
- [ ] Context explains why decomposition is needed: core shouldn't depend on heavy services; different deployment topologies need different subsets; circular dependencies prevent clean builds
- [ ] Decision states: the six crates, their contents, and their dependencies
- [ ] Includes the dependency graph ASCII art from integration-plan.md
- [ ] States the narrow interface points: Identity, IdentityProvider, OperationSpec
- [ ] States that irpc is a feature flag in alknet-core and an independent dep elsewhere
- [ ] States that alknet-storage implements IdentityProvider via the trait (not a crate dependency on alknet-core)
- [ ] States that alknet-storage references alknet-secret's EncryptedData wire format (type-level compatibility, not crate dep)
- [ ] Consequences: core is lean; services are pluggable; no circular deps; deployment topology determines which crates to include
- [ ] References: integration-plan.md dependency graph, ADR-029
## References
- docs/research/integration-plan.md — Phase 2, dependency graph
- docs/research/core.md — alknet-core contents
- docs/research/services.md — service protocols
- docs/research/storage.md — alknet-storage contents
- docs/research/flow.md — alknet-flowgraph contents
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,49 @@
---
id: architecture/adr-028-auth-irpc-service
name: Write ADR-028 — Auth as irpc service
status: pending
depends_on:
- architecture/adr-029-identity-core-type
scope: narrow
risk: medium
impact: phase
level: implementation
---
## Description
Write ADR-028 establishing that auth verification is provided via an irpc service protocol, with the `IdentityProvider` trait as the interface contract and `ConfigIdentityProvider` (ArcSwap-backed) as the default implementation.
This ADR defines the relationship between the trait-based path and the irpc path:
1. `IdentityProvider` trait in `alknet_core::auth` — the contract that callers depend on
2. `ConfigIdentityProvider` — default impl, reads from `ArcSwap<DynamicConfig>`, no database needed
3. `AuthProtocol` irpc service enum — `VerifyPubkey`, `VerifyToken`, `ReloadKeys`, `CheckAccess` — behind `irpc` feature flag
4. Future: `StorageIdentityProvider` (in alknet-storage) backed by SQLite — additive, not replacing the trait
The critical design point: callers go through `IdentityProvider`. The irpc service is one way to satisfy the trait. Feature-gating (`irpc` feature) means nodes that only do SSH tunneling don't need the service layer overhead. Both paths produce the same result — an `Identity` or rejection.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/028-auth-irpc-service.md` exists
- [ ] ADR follows established format
- [ ] Context explains why a service layer is needed: for head nodes serving many users, in-memory key lookup doesn't scale; irpc provides async boundary for database-backed auth
- [ ] Decision states: IdentityProvider trait is the contract; ConfigIdentityProvider is the default; AuthProtocol irpc service is behind feature flag; irpc path and trait path produce identical Identity results; StorageIdentityProvider in alknet-storage is a future additive impl
- [ ] Shows AuthProtocol enum (`VerifyPubkey`, `VerifyToken`, `ReloadKeys`, `CheckAccess`) and AuthResult type
- [ ] Consequences: minimal deployments use ArcSwap without irpc; production deployments wire SQLite-backed service; feature flag keeps core lean
- [ ] References: research/services.md AuthProtocol, auth.md, research/configuration.md auth service approach, ADR-029
## References
- docs/research/services.md — AuthProtocol definition
- docs/architecture/auth.md — IdentityProvider trait, Identity struct
- docs/research/configuration.md — auth service approach
- docs/research/integration-plan.md — ADR 028 entry, Phase 1.4
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,43 @@
---
id: architecture/adr-029-identity-core-type
name: Write ADR-029 — Identity as core type
status: pending
depends_on: []
scope: single
risk: low
impact: project
level: implementation
---
## Description
Write ADR-029 establishing `Identity` struct and `IdentityProvider` trait as core types in `alknet-core`.
The `Identity` struct and `IdentityProvider` trait are already defined in `auth.md` (the draft architecture spec). This ADR formalizes the decision that they live in `alknet-core` — not in alknet-storage, not in alknet-services — so that core auth, forwarding policy, and call protocol all reference the same type without circular dependencies.
The key constraint: alknet-core defines the trait, external crates provide implementations. `ConfigIdentityProvider` (ArcSwap-backed, in core) is the default. `StorageIdentityProvider` (SQLite-backed, in alknet-storage) is the production impl. Core never depends on storage.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/029-identity-core-type.md` exists
- [ ] ADR follows established format
- [ ] Context explains why Identity must be in core: auth, forwarding, call protocol all need it; can't have circular deps
- [ ] Decision states: `Identity { id, scopes, resources }` and `IdentityProvider` trait live in `alknet_core::auth`; `id` is a fingerprint (config-based auth) or account UUID (database-backed auth); derivation and storage are external concerns; default `ConfigIdentityProvider` reads from `DynamicConfig.auth`; production `StorageIdentityProvider` is in alknet-storage
- [ ] Consequences: alknet-core has no database dependency; alknet-storage implements the core trait; the `id` field serves dual purpose (fingerprint or UUID)
- [ ] Resolves OQ-18: IdentityProvider owns scopes, ForwardingPolicy uses scopes from Identity
- [ ] References: auth.md, research/services.md Identity section, research/integration-plan.md
## References
- docs/architecture/auth.md — Identity and IdentityProvider trait definitions
- docs/research/services.md — Identity section
- docs/research/integration-plan.md — ADR 029 entry, Phase 1.2
- docs/architecture/open-questions.md — OQ-18
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,49 @@
---
id: architecture/adr-030-static-dynamic-config-split
name: Write ADR-030 — Static/dynamic config split
status: pending
depends_on: []
scope: narrow
risk: low
impact: phase
level: implementation
---
## Description
Write ADR-030 establishing the split between `StaticConfig` (immutable after startup) and `DynamicConfig` (hot-reloadable at runtime) in alknet-core.
This is largely a promotion from the well-analyzed research in `docs/research/configuration.md`. The ADR records why this split matters, what goes in each config, and how reload works.
Key points:
- StaticConfig: transport mode, listen addr, TLS config, iroh config, host key, stealth mode, max auth attempts, max connections per IP — everything that requires socket/TLS renegotation to change
- DynamicConfig: auth policy (authorized keys, cert authorities), forwarding policy, rate limits — everything checked per-connection or per-channel
- ArcSwap for lock-free hot reload of DynamicConfig
- ServeOptions builder pattern is preserved; StaticConfig is constructed from ServeOptions
- TOML config file is an optional convenience input format (amends ADR-011, doesn't replace programmatic API)
- ConfigReloadHandle with `reload(DynamicConfig)` method
- NAPI exposes `reloadAuth()`, `reloadForwarding()`, `reloadAll()` on AlknetServer
## Acceptance Criteria
- [ ] `docs/architecture/decisions/030-static-dynamic-config-split.md` exists
- [ ] ADR follows established format
- [ ] Context explains the three failures: no hot reload of auth, no forwarding policy, no structured config beyond CLI flags
- [ ] Decision states: StaticConfig vs DynamicConfig split; ArcSwap for DynamicConfig; ServeOptions preserved as builder; TOML as optional convenience; ConfigService wraps reloads; amends ADR-011
- [ ] Lists what's in StaticConfig and what's in DynamicConfig
- [ ] Consequences: auth and forwarding can be reloaded without restart; config file users get TOML format; programmatic-first API preserved
- [ ] References: research/configuration.md, ADR-011
## References
- docs/research/configuration.md — full analysis, nearly spec-ready
- docs/architecture/decisions/011-no-ssh-config-programmatic-api.md — programmatic-first decision (amended, not superseded)
- docs/research/integration-plan.md — ADR 030 entry, Phase 1.1
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: architecture/adr-031-forwarding-policy
name: Write ADR-031 — Forwarding policy
status: pending
depends_on: []
scope: narrow
risk: low
impact: phase
level: implementation
---
## Description
Write ADR-031 establishing the forwarding policy model for `channel_open_direct_tcpip` access control.
Currently any authenticated client can open a channel to any destination. This ADR defines `ForwardingPolicy`, `ForwardingRule`, and `TargetPattern` as part of `DynamicConfig` (reloadable without restart).
Key design decisions from the research:
- Default-allow for migration compatibility (preserves current behavior)
- Default-deny is recommended for production
- Rules are evaluated per-channel-open, matched against the authenticated `Identity` from `IdentityProvider`
- `TransportKind` match in rules enables transport-specific restrictions (e.g., WebTransport clients restricted to alknet-* channels)
- OQ-12 resolved: start with global rules + principal matching from Identity.scopes; per-user scope from peer_credentials.metadata.scopes via IdentityProvider
- OQ-16 resolved: add TransportKind match in ForwardingRule; WebTransport clients can be scoped
- OQ-18 resolved: IdentityProvider owns scopes, ForwardingPolicy consumes them
## Acceptance Criteria
- [ ] `docs/architecture/decisions/031-forwarding-policy.md` exists
- [ ] ADR follows established format
- [ ] Context explains the security gap: any authenticated client gets unrestricted access
- [ ] Decision states: ForwardingPolicy with allow/deny rules, TargetPattern matching, default-allow for migration, TransportKind-aware rules, ForwardingPolicy is part of DynamicConfig (reloadable), Identity.scopes consumed by policy
- [ ] Includes ForwardingRule and TargetPattern type signatures
- [ ] Consequences: operators can restrict access per identity, per destination, per transport; default-allow preserves backward compatibility
- [ ] Resolves OQ-12, OQ-16, OQ-18 (reference in ADR)
- [ ] References: research/configuration.md, auth.md, open-questions.md
## References
- docs/research/configuration.md — ForwardingPolicy section
- docs/architecture/auth.md — Identity.scopes and IdentityProvider
- docs/architecture/open-questions.md — OQ-12, OQ-16, OQ-18
- docs/research/integration-plan.md — ADR 031 entry, Phase 1.3
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: architecture/adr-032-event-boundary-discipline
name: Write ADR-032 — Event boundary discipline
status: pending
depends_on: []
scope: single
risk: low
impact: project
level: implementation
---
## Description
Write ADR-032 establishing event boundary discipline as a hard architectural constraint.
The research (services.md, storage.md) identifies three distinct communication patterns with clear boundaries:
1. **Domain events** (Honker streams) — internal to the owning service, for state reconstruction. Never cross service boundaries without projection.
2. **irpc service calls** — synchronous request-response, within a node or cluster. Internal to the system.
3. **Call protocol events** (EventEnvelope) — cross-node, cross-language integration events. These are what cross boundaries.
The ADR must state this as a hard constraint, not a suggestion. Conflating these three patterns is an anti-pattern that leads to leaky event stores and coupling.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/032-event-boundary-discipline.md` exists
- [ ] ADR follows established format (Status, Context, Decision, Consequences, References)
- [ ] Context explains the three patterns and why conflating them is harmful
- [ ] Decision states: domain events stay within the owning service; irpc calls are synchronous internal boundaries; call protocol events are the only events that cross node boundaries; projection from domain events to integration events is required when crossing boundaries
- [ ] Consequences include: prevents leaky event stores, services are independently deployable, Honker and irpc are implementation details not exposed across boundaries
- [ ] References: research/services.md, research/storage.md, integration-plan.md
## References
- docs/research/services.md — event boundary discipline section
- docs/research/storage.md — Honker integration, event boundaries
- docs/research/integration-plan.md — ADR 032 entry
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,62 @@
---
id: architecture/adr-033-operationenv-irpc-call-protocol
name: Write ADR-033 — OperationEnv, irpc, and call protocol relationship
status: pending
depends_on:
- architecture/adr-028-auth-irpc-service
- architecture/adr-027-crate-decomposition
scope: moderate
risk: high
impact: project
level: implementation
---
## Description
Write ADR-033 establishing OperationEnv as the universal composition mechanism that unifies irpc services and call protocol operations from the handler's perspective.
This is the most conceptually complex ADR. It must clearly establish:
1. **OperationEnv is not an implementation detail from @alkdev/operations** — it's the universal composition mechanism. Handlers compose through `context.env[namespace][op](input)` regardless of dispatch path.
2. **Three dispatch paths**, all producing the same result:
- **Local dispatch**: Direct function call through the operation registry. Zero serialization.
- **Service dispatch (irpc)**: In-cluster, Rust-to-Rust. Postcard serialization over tokio channels (local) or QUIC streams (remote). Domain-level.
- **Remote dispatch (call protocol)**: Cross-node, cross-language. JSON EventEnvelope over any (Transport, Interface) pair. Integration-level.
3. **irpc is one dispatch backend for OperationEnv**, not a replacement for it. The call protocol is another dispatch backend. Both are Layer 3, at different scope boundaries.
4. **The behavioral contract**: namespace + operation name → invoke with input, return output. The Rust implementation can use typed method dispatch or a registry internally, but the handler-facing API must preserve this contract.
5. **An irpc service CAN be exposed as a call protocol operation** — the registry maps the path to a handler that internally calls the irpc service.
This ADR resolves the potential confusion where "service" could mean irpc service protocol, call protocol operation, or external service. The consistent naming is: irpc service (in-cluster, Rust-to-Rust, postcard), operation (path-based, call protocol, cross-node, JSON), external service (any reachable endpoint). OperationEnv unifies them.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/033-operationenv-irpc-call-protocol.md` exists
- [ ] ADR follows established format
- [ ] Context explains the confusion: irpc service vs call protocol operation vs external service are different dispatch mechanisms that need unification
- [ ] Decision states: OperationEnv is the universal composition mechanism; three dispatch paths (local, irpc, remote) all produce ResponseEnvelope; handlers don't know the dispatch path; irpc is one backend, not a replacement; an irpc service can back a call protocol operation
- [ ] Shows the dispatch diagram: OperationEnv → local | irpc service | remote call protocol
- [ ] Shows the composition layers: Call Protocol (Layer 3, external, JSON) → irpc Service (Layer 3, internal, postcard) → Honker Streams (domain events)
- [ ] Shows OperationEnv wiring example for minimal and production deployments
- [ ] Defines the consistent naming: irpc service / operation / external service
- [ ] Consequences: handlers compose through a single interface regardless of deployment; adapters (MCP, HTTP, DNS) map to operations through this interface; irpc gives type-safe efficient in-cluster calls; call protocol gives universal cross-language cross-node calls
- [ ] Hard constraint explicitly stated: the OperationEnv composition model must match the behavioral contract from @alkdev/operations
- [ ] References: research/services.md, @alkdev/operations, integration-plan.md
## References
- docs/research/services.md — OperationContext, OperationEnv, irpc service layer
- docs/research/integration-plan.md — ADR 033 entry, the three-layer clarification, OperationEnv section
- docs/architecture/call-protocol.md — OperationSpec, OperationRegistry, call protocol events
- @alkdev/operations — TypeScript OperationEnv implementation
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,41 @@
---
id: architecture/adr-034-head-worker-terminology
name: Write ADR-034 — Head/worker terminology
status: pending
depends_on: []
scope: single
risk: trivial
impact: project
level: implementation
---
## Description
Write ADR-034 formalizing the decision to use head/worker terminology instead of hub/spoke throughout the project.
This decision has already been applied in practice — `call-protocol.md`, `auth.md`, `open-questions.md`, and `napi-and-pubsub.md` were all updated to head/worker. The existing ADRs (024, 025) retain their original hub/spoke language because ADRs are historical records. ADR-018 is noted as superseded/extended by ADR-024 and the three-layer model.
The ADR exists to formally record the decision so future contributors understand why and can reference it.
## Acceptance Criteria
- [ ] `docs/architecture/decisions/034-head-worker-terminology.md` exists
- [ ] ADR follows the established format (Status, Context, Decision, Consequences, References)
- [ ] Context explains why hub/spoke is being replaced (mesh topologies, a head is also a worker)
- [ ] Decision states: head/worker everywhere in new specs and code; ADRs retain original language as historical records
- [ ] Consequences note: natural mesh formation, consistency with integration plan terminology
- [ ] References: integration-plan.md, ADR-024, ADR-025
## References
- docs/research/integration-plan.md — Phase 0 ADR 034 entry, inconsistencies section item 1
- docs/architecture/decisions/024-bidirectional-call-protocol.md — uses hub/spoke historically
- docs/architecture/decisions/025-handler-spec-separation.md — uses hub/spoke historically
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: architecture/review-adr-foundation
name: Review Phase 0a ADRs — foundation decisions before spec writing
status: pending
depends_on:
- architecture/adr-034-head-worker-terminology
- architecture/adr-032-event-boundary-discipline
- architecture/adr-029-identity-core-type
- architecture/adr-030-static-dynamic-config-split
- architecture/adr-031-forwarding-policy
- architecture/adr-028-auth-irpc-service
- architecture/adr-027-crate-decomposition
- architecture/adr-026-transport-interface-separation
- architecture/adr-033-operationenv-irpc-call-protocol
scope: broad
risk: low
impact: project
level: review
---
## Description
Review all Phase 0a ADRs (026-034) before proceeding to spec writing (Phase 0b). This is the critical checkpoint where we validate that the architectural decisions are consistent and complete before downstream specs reference them.
This review should happen before any spec documents are created or updated, since specs will reference ADR numbers.
## Acceptance Criteria
- [ ] All 9 ADRs (026-034) are written and follow the established format
- [ ] ADRs cross-reference each other correctly (e.g., ADR-028 references ADR-029, ADR-027 references ADR-029)
- [ ] No ADR contradicts another (e.g., ADR-026's Interface trait must be compatible with ADR-033's OperationEnv)
- [ ] Crate dependency graph from ADR-027 is acyclic (core depends on nothing heavy)
- [ ] Layer boundaries from ADR-026 cleanly separate Transport, Interface, and Protocol
- [ ] OperationEnv dispatch model from ADR-033 is consistent with ADR-028 (auth service) and ADR-027 (crate decomposition)
- [ ] Terminology is consistent (head/worker everywhere per ADR-034)
- [ ] Open questions referenced in ADRs have proposed resolutions consistent with the decision
- [ ] Each ADR has clear consequences (both positive and negative)
- [ ] ADRs are added to the README.md ADR table
## References
- docs/architecture/decisions/ — all existing ADRs 001-025 for format reference
- docs/research/integration-plan.md — Phase 0 review checklist
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion