Files
glm-5.3-flash d74a27f764 phase 1: architecture spec — overview, server/client, ADR-001..006
- ADR-001: inherit the alknet TLS design as the baseline; deviations
  recorded as alktls ADRs
- ADR-002: TlsError ships the ADR-088 six-variant shape from day one
  (typed #[from] sources; NoqWrap; no string catch-all)
- ADR-003: the QUIC feature is noq (iroh's extracted fork), pre-
  consumer rename; default = [] per the lean-crate convention
  (corrects the extracted code's default = ["quinn"])
- ADR-004: complete accessors — for_tcp_tls() adopted, rustls_config()
  adopted; server accessors borrow (&self), client accessors consume
- ADR-005: identity + credentials + fingerprint types move into
  alktls; auth layer stays out
- ADR-006: eight-module layout; seed tests + integration invariant
  pins (exact nine-scheme list, client enable_early_data)
- specs: overview (transport picture, terminology), server.md (ACME
  lifecycle, invariants), client.md (verifier selection matrix, root-
  store fallback); open-questions.md promotes OQ-TLS-01..08 (all
  resolved at entry)
- Cargo.toml: quinn feature -> noq (per ADR-003); AGENTS.md aligned

Architecture review pass done: 0 critical, 2 major (ADR-002 AcmeConfig
doc comment contradiction; ADR-003 unrecorded default deviation) and
8 minors all addressed; cross-references verified against alknet ADRs,
rustls/noq/iroh sources.

Verified: cargo test, test --all-features, clippy -D warnings,
fmt --check, doc --no-deps
2026-09-10 05:37:55 +00:00

210 lines
11 KiB
Markdown

# AGENTS.md
Operating instructions for opencode agents working in this repo. opencode
auto-loads this file as instructions, overriding the built-in defaults for
this project. Custom agents in `.opencode/agents/` inherit these rules
unless their own prompts say otherwise.
## Git Workflow
**Commit and push when reasonable.** When a change is complete and
verified (build + lint + tests pass), commit and push to `origin/main`
without asking. This overrides the built-in default of "only commit when
explicitly asked."
The workflow:
1. Make the change
2. Verify: `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check`, `cargo doc --no-deps` if docs changed
3. Inspect `git status` and `git diff` before staging — stage only the
intended files, never secrets
4. Write a concise commit message matching the repo style (see `git log
--oneline -10`). For multi-point changes, use a summary line plus a
body with bullet points and a verification block.
5. `git push origin main`
6. Report the commit hash and the verification summary
Exceptions — **do not** commit or push without asking:
- The change is exploratory / speculative (you're not sure the user wants
it kept)
- The user is actively reviewing the diff and may ask for changes
- The change touches a public API surface that consumers implement or
compile against (one-way doors — see "Public API shapes are one-way
doors" below; once consumers exist, those signatures are stable
contracts)
- You'd be force-pushing, amending a published commit, creating an empty
commit, or skipping hooks
Never commit secrets, keys, or credentials. If a commit fails or hooks
reject it, fix the issue and create a new commit — do not amend the
failed one.
Git identity is preconfigured (`glm-5.3-flash <glm-5.3-flash@alk.dev>`). Do
not change `git config`, skip hooks, or use `git commit -i`.
## Project Conventions (Rust / TLS crate)
This is the TLS crate — shared TLS setup types for the alk* stack:
server and client `rustls` configs, cert resolvers, verifiers, and
ACME state-machine wiring, transport-agnostic and shareable across
transports (noq, `tokio-rustls` TCP+TLS, and anything else that
consumes a `rustls` config). It is the extraction of the TLS handling
from alknet (alknet ADR-082/087/088 and the `crates/tls` spec are the
prior art). The conventions below apply to all work in `src/` and
`tests/`. They mirror `.opencode/agents/implementation-specialist.md`
§Project Conventions and are repeated here so they apply to every
session, not just spawned implementation agents.
1. **No comments in code** unless the user explicitly asks. This is a
project-wide convention. Doc comments (`///`, `//!`) are fine and
expected on public API. Inline `//` comments only when the user 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;
see 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. If you reach for `unwrap`, the error
path wasn't specified — stop and decide what should actually happen.
For poisoned `RwLock`/`Mutex`, use `unwrap_or_else(|e| e.into_inner())`
so a panic in one operation does not cascade to other operations.
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 should stay lean** — TLS setup and config types
only. Transport-specific wrapping is feature-gated: `noq` (the
`for_noq()` accessors), `tcp` (`tokio-rustls`), `acme` (the ACME
state machine, a heavy dep). The `rustls` dep is always present —
it is the core library. Unlike the sibling protocol crates
(alktunnels, alktty), wasm is not a load-bearing target here: the
crypto stacks (`aws-lc-rs`) and file I/O are platform code. If a
design decision ever makes the default crate heavier than "rustls +
cert material + tokio spawn," that is an ADR-worthy decision, not an
accident.
5. **Behavior-preservation invariants are load-bearing** — the extraction
from alknet must preserve these TLS behaviors exactly. An
implementation that omits any of these compiles and passes
type-checks but silently changes TLS behavior (alknet ADR-082 §
Behavior-preservation invariants):
- **`max_early_data_size = u32::MAX`** on all server config paths —
enables 0-RTT / early data. Omitting it silently breaks 0-RTT
clients.
- **`rustls::crypto::aws_lc_rs::default_provider()`** as the crypto
provider on all paths (alknet ADR-084). Do not switch to `ring` or
the process-default provider without an ADR.
- **`AcceptAnyCertVerifier`'s `supported_verify_schemes()`** returns
ED25519 + ECDSA P-256/P-384 + RSA PSS/PKCS1 (SHA256/384/512),
verbatim.
- **`acme-tls/1` ALPN append** for the ACME path only, done by the
TLS crate, not the caller (alknet ADR-027 §7).
- **Non-empty root store** — the client CA path merges
`webpki-roots` when the platform store is empty (alknet ADR-088
§5); a containerized deployment with no system CA bundle must
still verify public X.509 remotes.
6. **Fail closed** — verifier selection (fingerprint pin / CA / fail
closed) must never silently downgrade. An unknown raw-key remote
fails at handshake, not to CA verification. Identity precedence and
verifier selection follow the alknet ADR-034/ADR-091 shape: known
peer + fingerprint → pin; unknown + X.509 → CA; unknown + raw key →
fail closed. Client-auth cert presentation follows the local
identity: raw key/X.509 present the cert, `None` presents nothing,
`Acme` is a server-only identity (config error on the client path).
7. **One identity, N transports; one ACME state machine** — the central
types are `TlsServerConfig` / `TlsClientConfig`, 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 it via `Arc`. Never spawn a second ACME state
machine for a domain already being served — duplicate orders risk
Let's Encrypt rate limits and cert-cache divergence (alknet ADR-082
§The cert-reuse problem). This crate is the cert provider, not the
accept loop — transport accept loops live in the consumers
(assembly layer / endpoint crates).
8. **TLS-crate scope boundary** — this crate owns config *construction*.
Handshake-time outcomes (a rejected cert, the unknown-raw-key
fail-closed) flow through the transport's connector, not through
`TlsError` — `TlsError` is the config-construction error type (alknet
ADR-088 §6). ACME state-machine runtime errors are stream events,
logged in the spawned task, not `TlsError` variants. Do not grow
`TlsError` to cover handshake outcomes.
9. **Feature gates** — transport-specific deps are opt-in (`noq`,
`tcp`, `acme`); `default = []` — the default crate compiles lean
(ADR-003). 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 spec docs (ADR-082/083/084/086/087/088/089 and the
`crates/tls` README) 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` — this crate owns them after the
rewrite; do not re-import them from alknet.
11. **Naming** — Rust standard: `snake_case` for functions/variables/
modules, `PascalCase` for types/traits, `SCREAMING_SNAKE_CASE` for
constants.
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).
## Verification Commands
Run these before committing. All must pass.
```bash
cargo test # full suite
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
cargo test --all-features # if features are added/touched
cargo publish --dry-run --allow-dirty # before a release
```
## Architecture Context
- `docs/research/phase-0.md` — the Phase 0 research findings (current
phase). Phase 1 will produce `docs/architecture/` (overview, ADRs,
open-questions tracker) per `docs/sdd_process.md`.
- The prior art for this crate:
- **alknet-tls** — `/workspace/@alkdev/alknet/crates/alknet-tls/` —
the working extraction source (`server.rs`, `client.rs`, `pem.rs`,
`signing.rs`); this crate supersedes it in the alknet rewrite.
- **alknet docs** — `/workspace/@alkdev/alknet/docs/architecture/`:
ADR-082 (extraction), ADR-083 (endpoint takes no TLS config),
ADR-084 (aws-lc-rs provider), ADR-027 (identity model, `acme-tls/1`),
ADR-086 (endpoint types / split ALPN lists), ADR-087
(`TlsClientConfig`), ADR-088 (`TlsError` shape, root-store
fallback), ADR-089 (client dial seam), ADR-034 (verifier
selection), ADR-091 (`ConnectionCredentials`), and
`docs/architecture/crates/tls/README.md` (the full crate spec —
the most complete reference for what this crate must do).
- Key open threads carried from alknet (Phase 0 must resolve or defer
them — see `docs/research/phase-0.md`):
- The `TlsError` in the extracted code is the simplified 3-variant
enum; the ADR-088 six-variant `#[non_exhaustive]` shape is the
recorded target and was never implemented.
- The config types (`TlsIdentity`, `Ed25519SecretKey`,
`ConnectionCredentials`) still live in `alknet-core`; where they
land for the rewrite is an alktls-side decision.
- ADR-029's fingerprint question (`029-callclient-tls-client-auth...`)
and the client-auth/remote-identity verification seams.
- If a TODO references a design direction that an ADR has since decided
against, the TODO is stale — remove it and align with the ADR. Do not
implement the rejected design.