Decompose Phase 2 (Core Bridge) into 8 dependency-ordered tasks
Phase 2 completes the interface-to-protocol bridge and adds core types that external crates depend on. The 8 tasks are organized into 5 generations with clear dependencies: - Gen 1: StreamInterface/MessageInterface trait split (must go first) - Gen 2: SshSession bridge, RawFraming impl, CredentialProvider (parallel) - Gen 3: API keys in DynamicConfig (depends on CredentialProvider) - Gen 4: ListenerConfig HTTP/DNS stubs + axum scaffold - Gen 5: Review gate before Phase 3 Key design decisions: - 2.4a/2.4b split: SecretStoreCredentialProvider deferred to Phase 3 - API keys (2.6) must land before axum scaffold (2.7) - ListenerConfig (2.5) must land before axum scaffold (2.7) - Gen 2 tasks are parallelizable (separate modules)
This commit is contained in:
73
tasks/integration/phase2/api-keys-dynamic-config.md
Normal file
73
tasks/integration/phase2/api-keys-dynamic-config.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
id: api-keys-dynamic-config
|
||||||
|
name: Add API keys to DynamicConfig.auth and extend IdentityProvider token resolution
|
||||||
|
status: pending
|
||||||
|
depends_on: [credential-provider-trait]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Add `[[auth.api_keys]]` support to `DynamicConfig` and extend `ConfigIdentityProvider::resolve_from_token()` to verify API keys alongside existing AuthTokens. API keys are shorter, simpler bearer strings (hash-verified, with optional TTL and scopes) for service accounts and automation — they don't require Ed25519 key pairs like AuthTokens do.
|
||||||
|
|
||||||
|
Per ADR-037 and research/phase2/interface-model.md (Config section):
|
||||||
|
|
||||||
|
**API Key format**: `alk_<random_prefix>_<secret>` (or similar). Storage uses SHA-256 hash of the full key. Lookup is by prefix (first N characters), then hash verification of the full key.
|
||||||
|
|
||||||
|
**Config format**:
|
||||||
|
```toml
|
||||||
|
[[auth.api_keys]]
|
||||||
|
prefix = "alk_dGhl"
|
||||||
|
hash = "sha256:abc123..."
|
||||||
|
scopes = ["relay:connect"]
|
||||||
|
description = "dashboard service account"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key changes**:
|
||||||
|
- Add `ApiKeyEntry` struct: `prefix`, `hash`, `scopes`, `description`, `optional ttl/expires_at`
|
||||||
|
- Add `api_keys: Vec<ApiKeyEntry>` to `AuthPolicy` (or a separate section on `DynamicConfig`)
|
||||||
|
- Extend `ConfigIdentityProvider::resolve_from_token()` to check API keys: prefix match → hash verification → return `Identity`
|
||||||
|
- API keys produce `Identity { id: "<prefix>", scopes: <from entry>, resources: {} }`
|
||||||
|
- The `AuthToken` path (Ed25519 signed timestamp) is unchanged — both go through the same `resolve_from_token()` method, discriminated by format/prefix
|
||||||
|
|
||||||
|
**Why this is Phase 2**: The HTTP interface (task 2.7) needs bearer token auth, and API keys are the simplest mechanism for `IdentityProvider::resolve_from_token()`. Without this, HTTP auth has no config-based auth mechanism.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `ApiKeyEntry` struct defined with `prefix`, `hash`, `scopes`, `description`, `expires_at: Option<u64>` fields
|
||||||
|
- [ ] `AuthPolicy` gains an `api_keys: Vec<ApiKeyEntry>` field (or `DynamicConfig` gains a separate `api_keys` section)
|
||||||
|
- [ ] `ConfigIdentityProvider::resolve_from_token()` checks API keys: matches prefix, verifies SHA-256 hash of the full token, returns `Identity` on success
|
||||||
|
- [ ] API key lookup: tokens starting with `alk_` (or configured prefix) are treated as API keys; others go through the `AuthToken` verification path
|
||||||
|
- [ ] Expired API keys (where `expires_at` is set and in the past) are rejected
|
||||||
|
- [ ] API key scopes propagate to the returned `Identity.scopes` field
|
||||||
|
- [ ] `DynamicConfig::default()` includes an empty `api_keys` list (no behavioral change)
|
||||||
|
- [ ] `ConfigReloadHandle` reloads API keys along with the rest of `AuthPolicy`
|
||||||
|
- [ ] Unit test: valid API key authenticates via `resolve_from_token()`
|
||||||
|
- [ ] Unit test: expired API key is rejected
|
||||||
|
- [ ] Unit test: wrong hash is rejected
|
||||||
|
- [ ] Unit test: unknown prefix is rejected (falls through to AuthToken path)
|
||||||
|
- [ ] Unit test: API key scopes appear in the resolved `Identity`
|
||||||
|
- [ ] All existing auth tests continue to pass (no behavioral change for SSH key auth)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/decisions/037-api-keys-dynamic-config.md — ADR-037
|
||||||
|
- docs/research/phase2/interface-model.md — API keys in config, auth table
|
||||||
|
- docs/research/integration-plan.md — Phase 2.6
|
||||||
|
- crates/alknet-core/src/config/dynamic_config.rs — DynamicConfig, AuthPolicy
|
||||||
|
- crates/alknet-core/src/auth/identity.rs — ConfigIdentityProvider, IdentityProvider trait
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> The prefix match approach means we don't store the full API key in config — just the first ~8 chars for fast lookup and the SHA-256 hash for verification. This mirrors how GitHub/personal access tokens work.
|
||||||
|
|
||||||
|
> Consider whether `api_keys` should live on `AuthPolicy` or be a separate section. Putting it on `AuthPolicy` keeps all auth-related config together and ensures atomic reloads. The `ConfigIdentityProvider` already has access to `Arc<ArcSwap<DynamicConfig>>` so it can read both `authorized_keys` and `api_keys` from the same reload.
|
||||||
|
|
||||||
|
> The `resolve_from_token()` method currently takes `&AuthToken` — API keys are NOT AuthTokens (they're simple bearer strings). The method signature may need to accept a generic `&str` or a new enum that can be either an AuthToken string or an API key string. Alternatively, `resolve_from_token()` can accept `&str` and internally discriminate by prefix/format.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
69
tasks/integration/phase2/axum-http-router-scaffold.md
Normal file
69
tasks/integration/phase2/axum-http-router-scaffold.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
id: axum-http-router-scaffold
|
||||||
|
name: Axum HTTP router scaffold with auth middleware and stealth handoff
|
||||||
|
status: pending
|
||||||
|
depends_on: [api-keys-dynamic-config, listenconfig-http-dns-stubs]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Create an axum HTTP router scaffold behind the `http` feature flag, with auth middleware that extracts `Authorization: Bearer <token>` and calls `IdentityProvider::resolve_from_token()`, and a stealth mode handoff that replaces `send_fake_nginx_404` with routing detected HTTP traffic to the axum router.
|
||||||
|
|
||||||
|
Per the integration plan section 2.7 and research/phase2/tls-transport.md:
|
||||||
|
|
||||||
|
This task creates the structural scaffold for HTTP — auth middleware and stealth handoff only. No operational routes (no `POST /v1/{namespace}/{op}` handlers). The question of how HTTP paths map to operation invocations is intentionally deferred to Phase 5.
|
||||||
|
|
||||||
|
**Key components**:
|
||||||
|
1. **Auth middleware**: Extract `Authorization: Bearer <token>` from HTTP request headers. Call `IdentityProvider::resolve_from_token()`. Attach resolved `Identity` to request extensions. Reject with 401 if token is missing or invalid. Both AuthTokens (Ed25519 signed) and API keys (hash-verified) go through this path.
|
||||||
|
2. **Stealth handoff**: When `ListenerConfig::Http { stealth: true }`, replace `send_fake_nginx_404` with routing the detected-HTTP `BufReader<TlsStream>` to the axum router. The existing `ProtocolDetection` enum already has `Ssh` vs `Http` — the `Http` path currently sends a fake 404 and disconnects.
|
||||||
|
3. **Default 404 handler**: Any unmatched route returns 404. No `/v1/*` routes are registered yet.
|
||||||
|
4. **Dependency**: Add `axum` dependency behind `http` feature flag in `Cargo.toml`.
|
||||||
|
|
||||||
|
**Current state**:
|
||||||
|
- `stealth.rs` has `detect_protocol()` returning `ProtocolDetection::Ssh` or `ProtocolDetection::Http`
|
||||||
|
- `send_fake_nginx_404()` currently sends a fake nginx 404 response
|
||||||
|
- No `axum` dependency exists yet
|
||||||
|
- `IdentityProvider::resolve_from_token()` exists (will be extended with API keys by task 2.6)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `axum` dependency added to `Cargo.toml` behind `http` feature flag
|
||||||
|
- [ ] `crates/alknet-core/src/http/` module created (behind `http` feature flag)
|
||||||
|
- [ ] Auth middleware function: extracts `Authorization: Bearer <token>`, calls `IdentityProvider::resolve_from_token()`, attaches `Identity` to axum request extensions, returns 401 on missing/invalid token
|
||||||
|
- [ ] Auth middleware supports both AuthTokens and API keys (via `resolve_from_token()` which dispatches based on format/prefix)
|
||||||
|
- [ ] Stealth handoff: `stealth.rs` `send_fake_nginx_404` replaced with axum router handoff when `http` feature is enabled. When `http` feature is disabled, the fake 404 behavior remains.
|
||||||
|
- [ ] Default 404 handler for unmatched routes (returns `404 Not Found`)
|
||||||
|
- [ ] Axum `Router` scaffold constructed with auth middleware layer and default 404 fallback
|
||||||
|
- [ ] `HttpInterface` struct from task 1 (stream/message interface split) gets its internal `Router` reference and `IdentityProvider` wired
|
||||||
|
- [ ] `http` feature flag in `Cargo.toml` correctly gates the `axum` dependency and `http` module
|
||||||
|
- [ ] Unit test: auth middleware extracts bearer token from `Authorization` header
|
||||||
|
- [ ] Unit test: auth middleware returns 401 for missing token
|
||||||
|
- [ ] Unit test: auth middleware returns 401 for invalid token
|
||||||
|
- [ ] Unit test: auth middleware attaches `Identity` to request extensions on valid token
|
||||||
|
- [ ] Integration test: stealth mode detection routes HTTP traffic to axum (not fake 404)
|
||||||
|
- [ ] All existing server/stealth tests continue to pass (no behavioral change when `http` feature is disabled)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/integration-plan.md — Phase 2.7
|
||||||
|
- docs/research/phase2/tls-transport.md — Axum integration, stealth handoff, auth middleware
|
||||||
|
- crates/alknet-core/src/server/stealth.rs — Current ProtocolDetection, send_fake_nginx_404
|
||||||
|
- crates/alknet-core/src/auth/identity.rs — IdentityProvider::resolve_from_token()
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> The integration plan explicitly states: "No operational routes yet — the question of how HTTP paths map to operation invocations depends on the from_openapi / spec-generation work and is deferred to Phase 5." This task is a scaffold: auth middleware, stealth handoff, default 404. Full route registrations come later.
|
||||||
|
|
||||||
|
> For the stealth handoff, consider a compile-time approach: the `http` feature flag determines whether `send_fake_nginx_404` or the axum handoff is used. When `http` is disabled, the existing fake 404 behavior should remain unchanged.
|
||||||
|
|
||||||
|
> The axum router is created per-server (not per-request). It holds references to the `IdentityProvider` and `OperationEnv`/`OperationRegistry`.
|
||||||
|
|
||||||
|
> `send_fake_nginx_404` should NOT be deleted — just conditionally bypassed when the `http` feature is enabled and a `ListenerConfig::Http` listener is configured.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
61
tasks/integration/phase2/credential-provider-trait.md
Normal file
61
tasks/integration/phase2/credential-provider-trait.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
id: credential-provider-trait
|
||||||
|
name: Define CredentialProvider trait, CredentialSet enum, and ConfigCredentialProvider implementation
|
||||||
|
status: pending
|
||||||
|
depends_on: [stream-interface-message-interface-split]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Define the `CredentialProvider` trait and `CredentialSet` enum in `alknet_core::credentials`, implementing the outbound authentication abstraction that complements the inbound `IdentityProvider`. This is the "Phase A" / "Phase 2.4a" work — the trait and enum must exist in core before alknet-secret (Phase 3) can wire `SecretStoreCredentialProvider` against them.
|
||||||
|
|
||||||
|
Per ADR-036 and research/phase2/credential-provider.md:
|
||||||
|
|
||||||
|
- `CredentialProvider` resolves **outbound** credentials: "how does alknet authenticate TO external services?"
|
||||||
|
- `CredentialSet` is a structured enum of credential types: `ApiKey`, `Basic`, `Bearer`, `S3AccessKey`, `OidcToken`, `Custom`
|
||||||
|
- `ConfigCredentialProvider` reads API keys and static credentials from `DynamicConfig` — the Phase 2 default (simple, no secret service dependency)
|
||||||
|
- `SecretStoreCredentialProvider` is a **stub** that returns `None` for all lookups until Phase 3 provides the alknet-secret dependency
|
||||||
|
- Wire `CredentialProvider` into `OperationEnv`/`OperationContext` so handlers can access credentials
|
||||||
|
|
||||||
|
**Relationship to IdentityProvider**: These are opposite-direction abstractions. `IdentityProvider` resolves inbound auth (who is calling alknet). `CredentialProvider` resolves outbound auth (how alknet calls others). Both live at the same architectural layer.
|
||||||
|
|
||||||
|
**Relationship to OperationEnv**: Handlers compose through `context.env`. The `OperationEnv` needs access to `CredentialProvider` so that handlers calling external services can resolve credentials. This could be a dedicated field on `OperationContext` or accessible through the `env` — the implementation detail is flexible, but the behavioral contract must match: given a service name, return credentials for that service.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `CredentialProvider` trait defined in `crates/alknet-core/src/credentials/mod.rs` with `get_credentials(&self, service: &str) -> Option<CredentialSet>` and `refresh_credentials(&self, service: &str) -> Option<CredentialSet>`
|
||||||
|
- [ ] `CredentialSet` enum defined with variants: `ApiKey { header_name, token }`, `Basic { username, password }`, `Bearer { token }`, `S3AccessKey { access_key, secret_key, session_token }`, `OidcToken { access_token, refresh_token, expires_at }`, `Custom { scheme, params }`
|
||||||
|
- [ ] `ConfigCredentialProvider` struct implemented — reads credentials from `DynamicConfig.auth` (or a new `DynamicConfig.credentials` section). For Phase 2, this is a simple config-backed lookup returning `CredentialSet::Bearer` or `CredentialSet::ApiKey` entries.
|
||||||
|
- [ ] `SecretStoreCredentialProvider` struct defined as a stub — `get_credentials()` always returns `None`. Full implementation deferred to Phase 3.
|
||||||
|
- [ ] `CredentialProvider` wired into `OperationContext` or `OperationEnv` so handlers can access outbound credentials
|
||||||
|
- [ ] `credentials` module re-exported from `crates/alknet-core/src/lib.rs`
|
||||||
|
- [ ] Unit test: `ConfigCredentialProvider` returns configured credentials for a service name
|
||||||
|
- [ ] Unit test: `ConfigCredentialProvider` returns `None` for unknown service names
|
||||||
|
- [ ] Unit test: `SecretStoreCredentialProvider` stub returns `None` for all service names
|
||||||
|
- [ ] Unit test: `OperationEnv`/`OperationContext` provides access to `CredentialProvider` from handler context
|
||||||
|
- [ ] `CredentialSet` derives `Clone`, `Debug`, `serde::Serialize`, `serde::Deserialize`
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/decisions/036-credentialprovider-core-type.md — ADR-036
|
||||||
|
- docs/research/phase2/credential-provider.md — Full design rationale
|
||||||
|
- docs/research/integration-plan.md — Phase 2.4
|
||||||
|
- crates/alknet-core/src/auth/identity.rs — IdentityProvider (opposite direction, same pattern)
|
||||||
|
- crates/alknet-core/src/call/env.rs — OperationEnv
|
||||||
|
- crates/alknet-core/src/call/context.rs — OperationContext
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This task is "2.4a" — the core types and config-backed implementation. "2.4b" (SecretStoreCredentialProvider backed by SecretProtocol::Decrypt) is deferred to Phase 3 when alknet-secret exists.
|
||||||
|
|
||||||
|
> For `ConfigCredentialProvider`, consider whether to add a `[[credentials]]` section to `DynamicConfig` or to reuse a subsection. The simplest Phase 2 approach is a new `credentials: HashMap<String, CredentialSet>` field on `DynamicConfig` that stores static bearer tokens/API keys from config.
|
||||||
|
|
||||||
|
> The `OperationEnv`/`OperationContext` wiring can follow either pattern: (a) a `credential_provider: Arc<dyn CredentialProvider>` field on `OperationContext`, or (b) `CredentialProvider` accessible through a registry-style `env.credentials(service)` method. The integration plan says "wire into OperationEnv so handlers can access credentials through context.env" — approach (b) aligns with the OperationEnv composition model. This is an implementation detail to resolve during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
76
tasks/integration/phase2/listenconfig-http-dns-stubs.md
Normal file
76
tasks/integration/phase2/listenconfig-http-dns-stubs.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
id: listenconfig-http-dns-stubs
|
||||||
|
name: Update ListenerConfig with Http/Dns variants, add TransportKind::WebTransport tag, restructure InterfaceConfig
|
||||||
|
status: pending
|
||||||
|
depends_on: [stream-interface-message-interface-split]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Add `ListenerConfig::Http` and `ListenerConfig::Dns` variants for message-based interfaces, add `TransportKind::WebTransport` as a tag-only variant, and restructure `InterfaceConfig` into `StreamInterfaceConfig` and `MessageInterfaceConfig` to align with the `StreamInterface`/`MessageInterface` split.
|
||||||
|
|
||||||
|
Per the integration plan section 2.5 and research/phase2/interface-model.md and tls-transport.md:
|
||||||
|
|
||||||
|
**Current state**:
|
||||||
|
- `ListenerConfig` likely has a single variant or is not yet fully defined (Phase 1 added `TransportKind` variants and `InterfaceConfig` but the `ListenerConfig` may need updating)
|
||||||
|
- `TransportKind` has `Tcp`, `Tls`, `Iroh` — no `Dns` (correctly), no `WebTransport`
|
||||||
|
- `InterfaceConfig` has `Ssh(SshInterfaceConfig)` and `RawFraming(RawFramingConfig)` — needs restructuring to `StreamInterfaceConfig`
|
||||||
|
|
||||||
|
**Key changes**:
|
||||||
|
- Add `TransportKind::WebTransport` variant — tag-only, no acceptor implementation. This is a trivial addition that prevents a breaking change later when WebTransport lands in Phase 5.
|
||||||
|
- Confirm `TransportKind::Dns` is NOT in the enum (DNS is a `MessageInterface`, not a transport). If it somehow got added, remove it. (Research confirms it was never added.)
|
||||||
|
- Rename `InterfaceConfig` → `StreamInterfaceConfig` (aligned with the trait rename from task 1)
|
||||||
|
- Add `StreamInterfaceConfig::Ssh` and `StreamInterfaceConfig::RawFraming` variants
|
||||||
|
- Add `MessageInterfaceConfig` enum with `Http` and `Dns` variants (and their config structs)
|
||||||
|
- Add `HttpListenerConfig` struct: `bind_addr`, `tls: bool`, `stealth: bool`
|
||||||
|
- Add `DnsListenerConfig` struct: `bind_addr`, `tls: bool`
|
||||||
|
- Update `ListenerConfig` to have three variants:
|
||||||
|
- `Stream { transport: TransportKind, interface: StreamInterfaceKind }` (existing pattern, renamed)
|
||||||
|
- `Http { config: HttpListenerConfig }`
|
||||||
|
- `Dns { config: DnsListenerConfig }`
|
||||||
|
- `TransportKind::WebTransport` is a tag-only enum variant — no `WebTransportAcceptor` implementation, no feature flag, just the variant existing so that config parsing can reference it
|
||||||
|
|
||||||
|
**Note on stealth mode**: The `Http` variant's `stealth` field means "if true, do byte-peek protocol detection on incoming TLS connections". This connects to the existing `stealth.rs` protocol detection. The axum router scaffold (task 2.7) handles the routing when stealth mode detects HTTP traffic. This task just defines the config types.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `TransportKind::WebTransport { server_name: Option<String> }` variant added as tag-only (no acceptor impl, compiles but has no effect on server behavior)
|
||||||
|
- [ ] `TransportKind::Dns` confirmed absent from the enum (DNS is a `MessageInterface`, not a transport)
|
||||||
|
- [ ] `InterfaceConfig` renamed to `StreamInterfaceConfig` (or `StreamConfig` — aligned with the trait rename) with `Ssh` and `RawFraming` variants
|
||||||
|
- [ ] `MessageInterfaceConfig` enum added with `Http` and `Dns` variants
|
||||||
|
- [ ] `HttpListenerConfig` struct defined with `bind_addr: SocketAddr`, `tls: bool`, `stealth: bool`
|
||||||
|
- [ ] `DnsListenerConfig` struct defined with `bind_addr: SocketAddr`, `tls: bool`
|
||||||
|
- [ ] `ListenerConfig` enum has three variants: `Stream { transport, interface }`, `Http { config: HttpListenerConfig }`, `Dns { config: DnsListenerConfig }`
|
||||||
|
- [ ] `StreamInterfaceKind` enum defined (corresponding to `StreamInterface` implementors: `Ssh`, `RawFraming`)
|
||||||
|
- [ ] `MessageInterfaceKind` enum defined (corresponding to `MessageInterface` implementors: `Http`, `Dns`)
|
||||||
|
- [ ] `is_valid_pair()` validation updated for `Stream` listener configs (only valid Transport/StreamInterface combos allowed)
|
||||||
|
- [ ] `Display` implementations added for all new enums
|
||||||
|
- [ ] Serialization support (`serde::Serialize`/`Deserialize`) for all new config types
|
||||||
|
- [ ] All existing server/transport tests pass unchanged
|
||||||
|
- [ ] Unit test: `TransportKind::WebTransport` variant exists and can be constructed
|
||||||
|
- [ ] Unit test: `ListenerConfig::Http` variant constructs with `HttpListenerConfig`
|
||||||
|
- [ ] Unit test: `ListenerConfig::Dns` variant constructs with `DnsListenerConfig`
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/integration-plan.md — Phase 2.5
|
||||||
|
- docs/research/phase2/interface-model.md — ListenerConfig, TransportKind, InterfaceKind redesign
|
||||||
|
- docs/research/phase2/tls-transport.md — HTTP listener config, stealth mode
|
||||||
|
- crates/alknet-core/src/interface/config.rs — Current InterfaceConfig, InterfaceKind
|
||||||
|
- crates/alknet-core/src/interface/pairs.rs — Valid transport-interface pairs
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Use `#[non_exhaustive]` on `ListenerConfig`, `StreamInterfaceKind`, `MessageInterfaceKind`, and `MessageInterfaceConfig` so future variants (WebSocket, gRPC) don't break downstream.
|
||||||
|
|
||||||
|
> The `stealth` field on `HttpListenerConfig` controls whether the server does byte-peek protocol detection (first bytes → SSH vs HTTP). When `stealth: true` on a listener sharing port 443 with SSH, the accept loop routes based on protocol detection. When `stealth: false`, the HTTP listener receives all traffic directly.
|
||||||
|
|
||||||
|
> The `tls: bool` field is separate from `stealth`. `tls: true` means "use TLS on this listener". `stealth: true` means "peek first bytes to detect SSH vs HTTP". These are orthogonal: you can have TLS + stealth (port 443), TLS without stealth (port 8443), plain HTTP without stealth (port 8080), etc.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
id: raw-framing-interface-implementation
|
||||||
|
name: Implement RawFramingInterface accept/recv/send with first-frame auth
|
||||||
|
status: pending
|
||||||
|
depends_on: [stream-interface-message-interface-split]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `RawFramingInterface` and `RawFramingSession` to handle length-prefixed `EventEnvelope` frames over a byte stream, with first-frame authentication. Currently `RawFramingInterface::accept()` returns an error and `RawFramingSession` stubs exist.
|
||||||
|
|
||||||
|
Per the integration plan section 2.2 and interface.md:
|
||||||
|
|
||||||
|
**RawFramingInterface**: Reads 4-byte length-prefixed JSON `EventEnvelope` frames from a transport stream (TCP, TLS, iroh, etc.). No SSH wrapping — the raw framing interface carries call protocol events directly.
|
||||||
|
|
||||||
|
**First-frame auth**: The first `InterfaceEvent` on a `RawFramingSession` carries an auth token in the `InterfaceEvent.identity` field or a dedicated auth event type. After `IdentityProvider::resolve_from_token()` verifies the token and produces an `Identity`, the session is authenticated. Subsequent frames are call protocol `EventEnvelope` data. If auth fails, the session is terminated immediately.
|
||||||
|
|
||||||
|
**Current state of the code**:
|
||||||
|
- `RawFramingInterface` accepts any `TransportStream` but returns an error
|
||||||
|
- `RawFramingSession` is an empty struct with stub `recv()` (returns `None`) and `send()` (returns error)
|
||||||
|
- `call::frame::{encode, decode, decode_with_remainder}` already implement the wire format
|
||||||
|
- `IdentityProvider::resolve_from_token()` exists but is not yet wired to `AuthToken` verification (that's coming in the API keys task)
|
||||||
|
|
||||||
|
**Implementation approach**:
|
||||||
|
1. `RawFramingInterface::accept()` takes a `TransportStream`, wraps it in a `BufReader` for buffered reading, stores it in `RawFramingSession`. The `RawFramingSession` is created in an "unauthenticated" state.
|
||||||
|
2. `RawFramingSession::recv()` reads frames from the stream:
|
||||||
|
- If unauthenticated: read the first frame, extract the auth token, call `IdentityProvider::resolve_from_token()`. On success, transition to "authenticated" with the resolved `Identity`. On failure, return an error (session terminated).
|
||||||
|
- If authenticated: read `EventEnvelope` frames, wrap in `InterfaceEvent::with_identity(envelope, identity)`.
|
||||||
|
3. `RawFramingSession::send()` writes `EventEnvelope` frames to the stream using `call::frame::encode`.
|
||||||
|
|
||||||
|
The `RawFramingSession` needs:
|
||||||
|
- A `BufReader<Box<dyn TransportStream>>` for reading framed data
|
||||||
|
- A `Box<dyn TransportStream>` (or WriteHalf) for writing framed data
|
||||||
|
- An `Option<Identity>` tracking auth state
|
||||||
|
- A reference to `IdentityProvider` for token resolution
|
||||||
|
- A buffer for partial frame reads (`decode_with_remainder` pattern)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `RawFramingInterface::accept()` takes a `TransportStream` and `StreamInterfaceConfig::RawFraming` config, creates a `RawFramingSession` wrapping the stream
|
||||||
|
- [ ] `RawFramingSession` holds a buffered reader and writer over the transport stream, an auth state (`Option<Identity>`), and a reference to `IdentityProvider`
|
||||||
|
- [ ] `RawFramingSession::recv()` reads length-prefixed `EventEnvelope` frames from the stream using `call::frame::decode_with_remainder`
|
||||||
|
- [ ] First-frame auth: the first `recv()` call resolves the auth token via `IdentityProvider::resolve_from_token()` and stores the resulting `Identity`
|
||||||
|
- [ ] Subsequent `recv()` calls produce `InterfaceEvent::with_identity(envelope, identity)` using the authenticated identity
|
||||||
|
- [ ] Auth failure terminates the session: `recv()` returns an error result on bad tokens
|
||||||
|
- [ ] `RawFramingSession::send()` writes `EventEnvelope` frames to the stream using `call::frame::encode`
|
||||||
|
- [ ] Unit test: `RawFramingInterface::accept()` succeeds with a valid stream
|
||||||
|
- [ ] Unit test: `RawFramingSession` round-trips an `EventEnvelope` through `send()` and `recv()` (after mock auth)
|
||||||
|
- [ ] Unit test: First-frame auth with a valid token transitions to authenticated state
|
||||||
|
- [ ] Unit test: First-frame auth with an invalid token returns an error
|
||||||
|
- [ ] Integration test: `RawFramingSession` over a `tokio::io::duplex` stream (simulated TCP) sends and receives multiple frames
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/integration-plan.md — Phase 2.2
|
||||||
|
- docs/architecture/interface.md — RawFramingInterface, first-frame auth model
|
||||||
|
- crates/alknet-core/src/interface/raw_framing.rs — Current stubs
|
||||||
|
- crates/alknet-core/src/call/frame.rs — Frame encode/decode
|
||||||
|
- crates/alknet-core/src/auth/identity.rs — IdentityProvider, resolve_from_token
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> The frame format is already implemented and tested in `call::frame`. This task is primarily about wiring the frame reader/writer to the `InterfaceSession` trait and adding first-frame auth logic.
|
||||||
|
|
||||||
|
> Consider using `tokio::io::BufReader` for buffered reading and `tokio::io::BufWriter` for buffered writing. The `decode_with_remainder` function handles partial reads by returning how many bytes were consumed — the session needs to maintain a read buffer for reassembly.
|
||||||
|
|
||||||
|
> The `RawFramingInterface` config should include an `Arc<dyn IdentityProvider>` for first-frame auth. This follows the same pattern as `SshInterfaceConfig`.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
61
tasks/integration/phase2/review-core-bridge-phase2.md
Normal file
61
tasks/integration/phase2/review-core-bridge-phase2.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
id: review-core-bridge-phase2
|
||||||
|
name: Review all Phase 2 changes for spec conformance and prepare for Phase 3
|
||||||
|
status: pending
|
||||||
|
depends_on: [stream-interface-message-interface-split, ssh-session-call-protocol-bridge, raw-framing-interface-implementation, credential-provider-trait, api-keys-dynamic-config, listenconfig-http-dns-stubs, axum-http-router-scaffold]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: phase
|
||||||
|
level: review
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Review all Phase 2 implementation for spec conformance, architectural consistency, and completeness before Phase 3 crate development begins. Per integration plan section 4.5, a second doc sync should capture any deviations between spec and implementation.
|
||||||
|
|
||||||
|
This review covers:
|
||||||
|
1. **Spec conformance**: Do implementations match the architecture docs and ADRs (035, 036, 037)?
|
||||||
|
2. **Layer boundary discipline**: Does every component belong to exactly one layer? No call protocol logic in the interface layer, no interface logic in the transport layer.
|
||||||
|
3. **Terminology consistency**: head/worker everywhere (no hub/spoke), StreamInterface/MessageInterface (no bare "Interface" trait), consistent naming.
|
||||||
|
4. **Test coverage**: Do all Phase 2 tasks have tests that verify acceptance criteria?
|
||||||
|
5. **No circular dependencies**: alknet-core doesn't depend on alknet-secret, alknet-storage, or alknet-flowgraph.
|
||||||
|
6. **Doc sync**: Update architecture docs to reflect Phase 2 implementation state. Specifically:
|
||||||
|
- `interface.md` — StreamInterface/MessageInterface split, InterfaceRequest/InterfaceResponse
|
||||||
|
- `auth.md` — API keys, resolve_from_token() changes
|
||||||
|
- `configuration.md` — DynamicConfig additions (api_keys, credentials)
|
||||||
|
- `call-protocol.md` — SshSession bridge, RawFraming auth flow
|
||||||
|
- Any deviations between spec and implementation should be documented
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] All Phase 2 tasks have acceptance criteria verified (each task's AC checklist is complete)
|
||||||
|
- [ ] Layer boundaries are clean: interface layer produces/consumes `InterfaceEvent`; protocol layer handles `EventEnvelope`; transport layer provides byte streams
|
||||||
|
- [ ] No `Interface` trait references remain (all renamed to `StreamInterface`)
|
||||||
|
- [ ] No `TransportKind::Dns` in the enum (DNS is a `MessageInterface`)
|
||||||
|
- [ ] `Cargo.toml` dependency check: alknet-core has no circular deps on external crates
|
||||||
|
- [ ] `http` feature flag correctly gates axum dependency
|
||||||
|
- [ ] Architecture docs updated for Phase 2 state:
|
||||||
|
- [ ] `interface.md` reflects StreamInterface/MessageInterface split
|
||||||
|
- [ ] `auth.md` reflects API keys in DynamicConfig
|
||||||
|
- [ ] `configuration.md` reflects new DynamicConfig sections
|
||||||
|
- [ ] `call-protocol.md` reflects functional SshSession bridge
|
||||||
|
- [ ] All tests pass: `cargo test --all-features`
|
||||||
|
- [ ] No compiler warnings on Phase 2 code
|
||||||
|
- [ ] `taskgraph parallel --path tasks/integration/phase2` shows all tasks completed
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/integration-plan.md — Phase 4.5 doc sync
|
||||||
|
- All Phase 2 ADRs: 035, 036, 037
|
||||||
|
- All Phase 2 implementation tasks (2.1–2.7)
|
||||||
|
- docs/architecture/ — architecture docs to update
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is a quality gate before Phase 3. The review should be thorough but shouldn't block on minor documentation phrasing. Focus on structural conformance: are layers respected, are traits correct, are dependencies acyclic?
|
||||||
|
|
||||||
|
> If any deviations between spec and implementation are found, document them in the relevant architecture doc with a "Deviation from spec" note explaining why.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
72
tasks/integration/phase2/ssh-session-call-protocol-bridge.md
Normal file
72
tasks/integration/phase2/ssh-session-call-protocol-bridge.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
id: ssh-session-call-protocol-bridge
|
||||||
|
name: Bridge SshSession recv/send to call protocol via alknet-control:0 channel
|
||||||
|
status: pending
|
||||||
|
depends_on: [stream-interface-message-interface-split]
|
||||||
|
scope: moderate
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `SshSession::recv()` and `SshSession::send()` to bridge SSH channel data to and from the call protocol's `InterfaceEvent`/`EventEnvelope` frames. Currently both methods are stubs: `recv()` always returns `None` and `send()` silently discards.
|
||||||
|
|
||||||
|
Per the integration plan section 2.1 and interface.md (OQ-IF-01, resolved):
|
||||||
|
|
||||||
|
The bridge works as follows:
|
||||||
|
- When `SshHandler::channel_open_direct_tcpip` detects a destination starting with `alknet-`, it currently accepts the channel but doesn't bridge the data. The `ControlChannelRouter` exists in `control_channel.rs` but has no handler wired.
|
||||||
|
- `SshSession::recv()` should read `EventEnvelope` frames from the `alknet-control:0` channel stream (using the 4-byte length prefix + JSON wire format from `call::frame::{encode, decode}`), wrap them in `InterfaceEvent` with the session's `Identity` (obtained during SSH auth).
|
||||||
|
- `SshSession::send()` should write `EventEnvelope` frames to the `alknet-control:0` channel stream using the same framing format.
|
||||||
|
- The `ControlChannelRouter` should be wired to bridge incoming channel data to the call protocol handler.
|
||||||
|
|
||||||
|
**Current state of the code**:
|
||||||
|
- `SshSession::recv()` returns `None` (stub)
|
||||||
|
- `SshSession::send()` discards silently (stub)
|
||||||
|
- `ControlChannelRouter` in `control_channel.rs` has `route()` and `has_handler()` but no handler is registered
|
||||||
|
- `call::frame::{encode, decode}` functions exist and are well-tested (4-byte BE length prefix + JSON)
|
||||||
|
- `SshHandler` detects `alknet-*` destinations in `channel_open_direct_tcpip` but doesn't bridge data
|
||||||
|
- `SshHandler` stores `authenticated_identity: Option<Identity>` from SSH auth
|
||||||
|
- `InterfaceEvent` struct carries `EventEnvelope` + `Option<Identity>` — already defined
|
||||||
|
|
||||||
|
**Key design considerations**:
|
||||||
|
- The `SshSession` needs access to the SSH channel's data stream to read/write `EventEnvelope` frames. This requires getting the `russh::Channel` data stream and framing it.
|
||||||
|
- The `ControlChannelRouter` currently uses `Box<dyn DuplexStream>` — it can be wired as a `ControlChannelHandler` that reads frames from the stream and produces `InterfaceEvent`s.
|
||||||
|
- The `alknet-control:0` channel is the first SSH direct-tcpip channel with the `alknet-control` destination. Additional `alknet-*` channels may follow.
|
||||||
|
- The session's `Identity` (from SSH auth) must be attached to every `InterfaceEvent` produced by `recv()`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `SshSession::recv()` reads `EventEnvelope` frames from the SSH channel data stream and produces `InterfaceEvent` with the session's `Identity`
|
||||||
|
- [ ] `SshSession::send()` writes `EventEnvelope` frames to the SSH channel data stream using `call::frame::encode`
|
||||||
|
- [ ] `ControlChannelRouter` is wired as the handler for `alknet-control:0` channels, bridging SSH channel data to the call protocol
|
||||||
|
- [ ] Frame encoding matches `call::frame::{encode, decode}` — 4-byte big-endian length prefix + UTF-8 JSON body
|
||||||
|
- [ ] The session's `Identity` (from `SshHandler::authenticated_identity`) is attached to every `InterfaceEvent` produced by `recv()`
|
||||||
|
- [ ] `SshHandler::channel_open_direct_tcpip` correctly routes `alknet-control:0` channels to the `ControlChannelRouter` handler
|
||||||
|
- [ ] Unit test: `SshSession` can round-trip an `EventEnvelope` through `send()` and `recv()` (using a mock channel stream)
|
||||||
|
- [ ] Unit test: `ControlChannelRouter.with_handler()` successfully routes channel data
|
||||||
|
- [ ] All existing server/auth/transport tests continue to pass
|
||||||
|
- [ ] No behavioral changes for non-`alknet-*` channel forwarding (port proxy logic unchanged)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/integration-plan.md — Phase 2.1
|
||||||
|
- docs/architecture/interface.md — OQ-IF-01 resolution, InterfaceEvent model
|
||||||
|
- docs/architecture/call-protocol.md — EventEnvelope, frame encoding
|
||||||
|
- crates/alknet-core/src/interface/ssh.rs — SshSession stubs (recv/send)
|
||||||
|
- crates/alknet-core/src/server/control_channel.rs — ControlChannelRouter
|
||||||
|
- crates/alknet-core/src/call/frame.rs — frame encode/decode
|
||||||
|
- crates/alknet-core/src/interface/session.rs — InterfaceEvent, InterfaceSession traits
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the highest-risk task in Phase 2. The `russh` channel data stream API needs careful handling — getting a `Channel`'s data stream for async reading/writing is non-trivial and may require understanding russh's `data()` callback pattern vs. the `Channel::into_stream()` method.
|
||||||
|
|
||||||
|
> Consider implementing incrementally: first wire the `ControlChannelRouter` handler to produce `InterfaceEvent`s from raw channel data, then connect that to `SshSession::recv()`/`send()`. Each step should have passing tests before proceeding.
|
||||||
|
|
||||||
|
> The `SshSession` struct currently holds a `server::Handle` and a `JoinHandle`. It may need additional fields to track the control channel stream and the authenticated identity for producing `InterfaceEvent`s with identity attached.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
id: stream-interface-message-interface-split
|
||||||
|
name: Rename Interface → StreamInterface, add MessageInterface trait and restructure config types
|
||||||
|
status: pending
|
||||||
|
depends_on: []
|
||||||
|
scope: moderate
|
||||||
|
risk: medium
|
||||||
|
impact: phase
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Rename the current `Interface` trait to `StreamInterface` and add a `MessageInterface` trait for request/response interfaces (HTTP, DNS). This is the most impactful structural change in Phase 2 because all subsequent tasks reference the new trait names and config types.
|
||||||
|
|
||||||
|
Per ADR-035 and research/phase2/interface-model.md:
|
||||||
|
|
||||||
|
- `Interface` → `StreamInterface` (the current trait becomes the stream-specific variant; persistent byte streams)
|
||||||
|
- `InterfaceSession` stays as-is (it's already stream-specific)
|
||||||
|
- Add `MessageInterface` trait: `handle_request(&self, request: InterfaceRequest) -> Result<InterfaceResponse>` (stateless request/response)
|
||||||
|
- Add `InterfaceRequest` and `InterfaceResponse` types that normalize calls across message interfaces
|
||||||
|
- Add `HttpInterface` stub (struct definition, no route implementations yet)
|
||||||
|
- Add `DnsInterface` stub (struct definition only)
|
||||||
|
- Restructure `InterfaceConfig` into `StreamInterfaceConfig` and `MessageInterfaceConfig`
|
||||||
|
- Update `ListenerConfig` to include `Stream`, `Http`, and `Dns` variants per the research docs
|
||||||
|
- Add `TransportKind::WebTransport` as a tag-only variant (no acceptor implementation)
|
||||||
|
- Remove any references to `TransportKind::Dns` (it was never added, so no removal needed — just ensure it's not added)
|
||||||
|
|
||||||
|
**Why this must go first**: Every other Phase 2 task imports and references these types. The rename and new traits must land before SshSession bridge, RawFraming implementation, or HTTP scaffold work begins. The integration plan section 2.3 explicitly states: "This task should be done early in Phase 2 because all subsequent tasks reference the new trait names."
|
||||||
|
|
||||||
|
**Current state of the code**:
|
||||||
|
- `Interface` trait in `crates/alknet-core/src/interface/mod.rs` with `accept()` → `Self::Session`
|
||||||
|
- `InterfaceSession` trait in `crates/alknet-core/src/interface/session.rs` with `recv()` / `send()`
|
||||||
|
- `InterfaceConfig` enum with `Ssh(SshInterfaceConfig)` and `RawFraming(RawFramingConfig)` variants
|
||||||
|
- `InterfaceKind` enum with `Ssh` and `RawFraming` variants
|
||||||
|
- `TransportKind` has `Tcp`, `Tls`, `Iroh` (no `Dns`)
|
||||||
|
- `SshInterface` implements `Interface`, `SshSession` implements `InterfaceSession`
|
||||||
|
- `RawFramingInterface`/`RawFramingSession` are stubs (Phase 1 left them)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `Interface` trait renamed to `StreamInterface` throughout alknet-core (mod.rs, ssh.rs, raw_framing.rs, and all import sites)
|
||||||
|
- [ ] `MessageInterface` trait defined in `crates/alknet-core/src/interface/mod.rs` with `handle_request(&self, request: InterfaceRequest) -> Result<InterfaceResponse>`
|
||||||
|
- [ ] `InterfaceRequest` struct defined with `operation_path`, `input`, `auth_token`, `metadata` fields per interface-model.md
|
||||||
|
- [ ] `InterfaceResponse` struct defined with `result`, `status`, `headers` fields per interface-model.md
|
||||||
|
- [ ] `HttpInterface` stub struct defined (identity_provider, registry, env fields) — no route implementations
|
||||||
|
- [ ] `DnsInterface` stub struct defined (domain, identity_provider, registry, env fields) — no implementation
|
||||||
|
- [ ] `InterfaceConfig` restructured: `StreamInterfaceConfig::Ssh` and `StreamInterfaceConfig::RawFraming` replace current variants; `MessageInterfaceConfig` enum added with `Http` and `Dns` variants (or the variant types are defined)
|
||||||
|
- [ ] `ListenerConfig` enum updated with `Stream { transport, interface }`, `Http { bind_addr, tls, stealth }`, and `Dns { bind_addr, tls }` variants
|
||||||
|
- [ ] `TransportKind::WebTransport` added as tag-only variant (no acceptor, no feature flag beyond the enum variant)
|
||||||
|
- [ ] `is_valid_pair()` / `TransportKindBase` validation updated for StreamInterface pairs only
|
||||||
|
- [ ] All existing tests pass after the rename (SshInterface and RawFramingInterface still compile and pass)
|
||||||
|
- [ ] New `StreamInterface` implementors still use `InterfaceSession` for `type Session`
|
||||||
|
- [ ] `MessageInterface` has at least one compilation test (e.g., a mock struct implements it)
|
||||||
|
- [ ] `HttpInterface` and `DnsInterface` stubs compile and exist in the type system
|
||||||
|
- [ ] `lib.rs` re-exports all new types (`StreamInterface`, `MessageInterface`, `InterfaceRequest`, `InterfaceResponse`, `HttpInterface`, `DnsInterface`)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/decisions/035-streaminterface-messageinterface-split.md — ADR-035
|
||||||
|
- docs/research/phase2/interface-model.md — Full design rationale
|
||||||
|
- docs/research/integration-plan.md — Phase 2.3
|
||||||
|
- crates/alknet-core/src/interface/mod.rs — Current Interface trait
|
||||||
|
- crates/alknet-core/src/interface/session.rs — InterfaceSession, InterfaceEvent
|
||||||
|
- crates/alknet-core/src/interface/config.rs — Current InterfaceConfig
|
||||||
|
- crates/alknet-core/src/interface/pairs.rs — Valid transport-interface pairs
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the most mechanically invasive change in Phase 2 due to the rename, but it's low-risk behaviorally. The `Interface` → `StreamInterface` rename is a find-and-replace operation. The new `MessageInterface` trait and stubs are purely additive. The `ListenerConfig` restructuring is additive since no existing code uses `ListenerConfig::Http` or `ListenerConfig::Dns` yet.
|
||||||
|
|
||||||
|
> The integration plan section 2.3 notes: "Existing `SshInterface` and `RawFramingInterface` become `StreamInterface` implementations. No behavior change for stream-based interfaces."
|
||||||
|
|
||||||
|
> Consider using `#[non_exhaustive]` on the new enums (`MessageInterfaceConfig`, `ListenerConfig`) so future variants (WebSocket, etc.) don't break downstream.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
Reference in New Issue
Block a user