repo scaffold + AGENTS.md + Phase 0 research
- Cargo scaffold: feature gates (quinn/tcp/acme), lean tokio subset, placeholder lib; Cargo.lock committed with time pinned to 0.3.36 so rust-version = 1.85 is actually satisfiable (rcgen's default time resolution requires 1.88 — alknet-tls fails the same check) - AGENTS.md adapted from alktunnels: TLS-crate conventions (behavior- preservation invariants, fail-closed verifier selection, one ACME state machine, config-construction scope boundary, no wasm target) - .opencode/agents: implementation-specialist conventions + coordinator prompt template + architect deferral examples updated for alktls - docs/research/phase-0.md: extraction inventory with verified invariants (line-referenced), spec-vs-code gaps (TlsError shape, for_tcp_tls, config-type ownership), rewrite requirements, OQ-TLS-01..07, MSRV verification record Verified: cargo test, clippy -D warnings, fmt --check, doc --no-deps, test --all-features, rustup run 1.85 cargo check
This commit is contained in:
@@ -329,7 +329,7 @@ A decision should be `deferred(scope)` when:
|
||||
- The use case isn't concrete (e.g., "we don't know what the agent crate
|
||||
will need from the call protocol")
|
||||
- The options depend on something that doesn't exist yet (e.g.,
|
||||
"depends on the alknet-http crate spec")
|
||||
"depends on the alknet rewrite's dial-seam shape")
|
||||
- The trade-off requires data that can only come from implementation
|
||||
(e.g., "need performance benchmarks to choose between X and Y")
|
||||
- The decision is genuinely not needed for the current scope (e.g., "the
|
||||
@@ -373,10 +373,10 @@ A decision should be `deferred(unclear)` when:
|
||||
(implies it's decided).
|
||||
2. **State the blocking condition** (`deferred(scope)`) or
|
||||
**investigation target** (`deferred(unclear)`) — what specific thing
|
||||
would unblock this? Be concrete: "blocked on: alknet-agent crate spec
|
||||
exists" or "investigation: work through 2+ example outbound-dial use
|
||||
cases (hub→worker, worker→hub) to see how verifier-selection +
|
||||
provider + connector compose."
|
||||
would unblock this? Be concrete: "blocked on: the alknet rewrite's
|
||||
dial-seam shape exists" or "investigation: work through 2+ example
|
||||
outbound-dial use cases (hub→worker, worker→hub) to see how
|
||||
verifier-selection + provider + connector compose."
|
||||
3. **State the impacts** — what does this block downstream? Be
|
||||
specific: "blocks the first hub deployment because the hub dials
|
||||
workers" not "blocks the hub crate." This is the triage signal that
|
||||
|
||||
@@ -191,7 +191,7 @@ also include:
|
||||
Example prompt template:
|
||||
|
||||
```
|
||||
You are an implementation specialist for the @alkdev/alknet project.
|
||||
You are an implementation specialist for the @alkdev/alktls project.
|
||||
|
||||
Your task: {{task}}
|
||||
|
||||
@@ -204,13 +204,14 @@ Your task: {{task}}
|
||||
7. Push: git push origin $(git branch --show-current)
|
||||
8. Notify: worktree({action: "notify", args: {message: "Task completed: {{task}}. <brief summary>", level: "info"}})
|
||||
|
||||
Key project constraints (@alkdev/alknet):
|
||||
Key project constraints (@alkdev/alktls):
|
||||
- Rust: use cargo build, cargo clippy, cargo fmt, cargo test
|
||||
- No comments in code
|
||||
- anyhow::Result for application errors, thiserror for library error types
|
||||
- Feature flags for transports (tls, iroh, acme)
|
||||
- Async via tokio runtime
|
||||
- No panics in library code
|
||||
- thiserror for the library error type (`TlsError`, `#[non_exhaustive]`)
|
||||
- Feature gates for transports (quinn, tcp, acme)
|
||||
- Async via tokio runtime (no `features = ["full"]` in dependencies)
|
||||
- No panics in library code; no unwrap/expect outside tests
|
||||
- Behavior-preservation invariants are load-bearing (see AGENTS.md §5)
|
||||
```
|
||||
|
||||
### Partial Generation Spawning
|
||||
|
||||
@@ -211,69 +211,63 @@ Read `AGENTS.md` at project root for full details. Key rules:
|
||||
|
||||
1. **No comments in code** — Per project convention. Doc comments (`///`, `//!`)
|
||||
are fine and expected on public API. Inline `//` comments only when the user
|
||||
asks or when a non-obvious safety/correctness constraint would otherwise be
|
||||
missed (e.g., "a two-pump tunnel must shut down the opposite sink on pump
|
||||
completion — `try_join!` alone deadlocks").
|
||||
2. **Error handling** — `thiserror` for library error types (`TunnelError`;
|
||||
`HandlerError`/`StreamError` come from alkcall::core). No panics in library
|
||||
code. No `unwrap()` or `expect()` outside tests. For poisoned
|
||||
`RwLock`/`Mutex`, use `unwrap_or_else(|e| e.into_inner())`.
|
||||
3. **`tokio` is the async runtime** — all I/O is async. The tunnel pumps, the
|
||||
channels integration, and the consumer session type are all async. Use
|
||||
`tokio::sync` primitives (`oneshot`, `mpsc`) for lifecycle correlation and
|
||||
per-direction data flow.
|
||||
4. **WASM target is load-bearing** — the default crate (protocol-only) MUST
|
||||
compile to `wasm32-unknown-unknown`. Use the wasm-clean tokio subset
|
||||
(`rt`, `sync`, `io-util`, `macros`, `time`); **do NOT use
|
||||
`features = ["full"]`**. Substrate backends (local TCP/UDP sockets, process
|
||||
listeners) are feature-gated and never imported from the
|
||||
shared/producer/consumer modules.
|
||||
5. **Wire format is stable** — the tunnel payload rides inside channels data
|
||||
channels as raw bytes (channels strips its 8-byte header transparently; the
|
||||
tunnel protocol owns whatever framing it puts inside the `BiStream`). Any
|
||||
negotiation/setup frame is self-contained (length-prefixed JSON per alktty
|
||||
ADR-006 precedent), not alkcall's `EventEnvelope` framing. Wire-format
|
||||
changes after the first consumer are additive-only.
|
||||
6. **Producer/consumer, not server/client** — both sides of a channels
|
||||
connection can initiate. Use "producer"/"consumer" or "accept side"/"connect
|
||||
side," not "server"/"client."
|
||||
7. **Substrate-agnostic by construction** — the protocol layer must not know
|
||||
whether bytes come from TCP, UDP, a Unix socket, or stdio. Target
|
||||
addressing, direction, and lifecycle bookkeeping must not hardcode a
|
||||
substrate.
|
||||
8. **Two-pump shutdown-on-completion is a contract** — each pump MUST shut
|
||||
down the opposite sink when it completes; `tokio::try_join!` alone
|
||||
deadlocks (POC-validated, alknet ADR-078). Emit EOF sentinels (zero-length
|
||||
chunks) on clean sink shutdown.
|
||||
9. **No forced local binding** — a tunnel must not require the producer (or
|
||||
consumer) to bind a local port. Support both SSH `-L` and `-R` style
|
||||
directions and unbound/listen-optional flows; the binding decision belongs
|
||||
to the caller.
|
||||
10. **Backpressure and limits are inherited** — bounded per-channel buffers,
|
||||
the 256-channel cap, monotonic IDs, and zero-length-sentinel EOF are
|
||||
alkcall channels invariants. Do not build a second demux/mux or re-derive
|
||||
limits.
|
||||
11. **Vendored core types come from alkcall** — `Connection`,
|
||||
`ProtocolHandler`, `BiStream`, `BidiStreamSource`, `AuthContext`,
|
||||
`Identity`, `IdentityProvider`, `AccessControl`, `OwnershipProvider`,
|
||||
`HandlerError`, `StreamError` come from `alkcall::core`. Do not vendor
|
||||
copies. Pin `alkcall = "0.4.0"`; bump deliberately; fix issues upstream.
|
||||
12. **BAST document for the wire format** — binary framing (if any beyond
|
||||
pass-through) carries a BAST document under `docs/architecture/`
|
||||
conforming to `https://alk.dev/bast/v1/schema`. alktunnels does not depend
|
||||
on alktype; hand-rolled codecs are fine for trivial formats.
|
||||
13. **Access control** — scope-gate tunnel opens (`TUNNEL_OPEN_SCOPE`, shape
|
||||
following alktty's `TTY_OPEN_SCOPE`); the channels path gets authorization
|
||||
via `ChannelCore::register_openable`, which runs the ACL before the open
|
||||
handler. Optionally consult `OwnershipProvider`
|
||||
(`provider.owns(id_ref, kind, &id, "tunnel")` — the 4-arg shape).
|
||||
14. **Naming conventions** — Rust standard: `snake_case` for functions/variables/
|
||||
asks or when a non-obvious security/correctness constraint would otherwise be
|
||||
missed (e.g., "the root store must never be empty — a container with no
|
||||
system CA bundle still needs to verify public X.509 remotes; alknet
|
||||
ADR-088 §5").
|
||||
2. **Error handling** — `thiserror` for the library error type (`TlsError`,
|
||||
`#[non_exhaustive]`, one variant per failure category — the alknet ADR-088
|
||||
shape). No panics in library code. No `unwrap()` or `expect()` outside
|
||||
tests. For poisoned `RwLock`/`Mutex`, use `unwrap_or_else(|e| e.into_inner())`.
|
||||
3. **`tokio` is the async runtime** — all I/O is async. The ACME state machine
|
||||
is a spawned task; cert loading is sync file I/O behind an `async fn`
|
||||
signature for API uniformity. Use the wasm-clean tokio subset
|
||||
(`rt`, `sync`, `macros`); **do NOT use `features = ["full"]`** in
|
||||
`[dependencies]` (dev-dependencies may use `full`).
|
||||
4. **The default crate stays lean** — TLS setup and config types only.
|
||||
Transport-specific wrapping is feature-gated: `quinn` (the `for_quinn()`
|
||||
accessors), `tcp` (`tokio-rustls`), `acme` (the ACME state machine, a heavy
|
||||
dep). The `rustls` dep is always present. Wasm is not a load-bearing target
|
||||
here (crypto stacks and file I/O are platform code).
|
||||
5. **Behavior-preservation invariants are load-bearing** — `max_early_data_size
|
||||
= u32::MAX` on all server config paths (0-RTT); `aws_lc_rs::default_provider()`
|
||||
on all paths (alknet ADR-084); `AcceptAnyCertVerifier::supported_verify_schemes()`
|
||||
returns ED25519 + ECDSA P-256/P-384 + RSA PSS/PKCS1 verbatim; `acme-tls/1`
|
||||
ALPN appended by the crate for the ACME path only (alknet ADR-027 §7);
|
||||
non-empty root store (merge `webpki-roots` when the platform store is
|
||||
empty — alknet ADR-088 §5).
|
||||
6. **Fail closed** — verifier selection (fingerprint pin / CA / fail closed)
|
||||
must never silently downgrade. Known peer + fingerprint → pin; unknown +
|
||||
X.509 → CA; unknown + raw key → fail closed at handshake (alknet ADR-034).
|
||||
Client-auth cert presentation follows the local identity; `Acme` is a
|
||||
server-only identity (config error on the client path).
|
||||
7. **One identity, N transports; one ACME state machine** — `TlsServerConfig` /
|
||||
`TlsClientConfig` are built once and shared (the inner rustls config is
|
||||
Clone — Arc-shared resolvers). `TlsServerConfig` is not `Clone` (it holds
|
||||
the ACME task's `JoinHandle`); share via `Arc`. Never spawn a second ACME
|
||||
state machine for a domain already being served (alknet ADR-082 §The
|
||||
cert-reuse problem). This crate is the cert provider, not the accept loop.
|
||||
8. **TLS-crate scope boundary** — this crate owns config *construction*.
|
||||
Handshake-time outcomes flow through the transport's connector, not through
|
||||
`TlsError` (alknet ADR-088 §6). ACME runtime errors are stream events,
|
||||
logged in the spawned task. Do not grow `TlsError` to cover handshake
|
||||
outcomes.
|
||||
9. **Feature gates** — transport-specific deps are opt-in (`quinn`, `tcp`,
|
||||
`acme`). Verify `cargo test` (default) and `cargo test --all-features`
|
||||
both pass whenever features are touched.
|
||||
10. **Upstream posture** — this crate extracts working code from alknet
|
||||
(`crates/alknet-tls`, `crates/alknet-core` config/fingerprint). The alknet
|
||||
ADRs and the `crates/tls` README spec are the reference; where this crate
|
||||
deviates, record the deviation as an ADR here rather than silently
|
||||
diverging. Config types (`TlsIdentity`, `Ed25519SecretKey`) are expected
|
||||
to move here from `alknet-core`; do not re-import them from alknet.
|
||||
11. **Naming conventions** — Rust standard: `snake_case` for functions/variables/
|
||||
modules, `PascalCase` for types/traits, `SCREAMING_SNAKE_CASE` for constants.
|
||||
15. **Module structure** — one module per file under `src/`, re-exported from
|
||||
`src/lib.rs`. Public API surface is `lib.rs` re-exports. Producer half
|
||||
(adapter / open-handler), consumer half (typed session/client), shared
|
||||
wire/target-addressing modules; backends feature-gated and never imported
|
||||
from shared/producer/consumer modules.
|
||||
12. **Module structure** — one module per file under `src/`, re-exported from
|
||||
`src/lib.rs`. Public API surface is `lib.rs` re-exports. The alknet shape
|
||||
is the reference: `server.rs` (server config + resolvers), `client.rs`
|
||||
(client config + verifiers), `pem.rs` (cert/key loading), `signing.rs`
|
||||
(shared signing helpers).
|
||||
|
||||
## Key Principles
|
||||
|
||||
|
||||
Reference in New Issue
Block a user