- 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
11 KiB
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:
- Make the change
- Verify:
cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt --check,cargo doc --no-depsif docs changed - Inspect
git statusandgit diffbefore staging — stage only the intended files, never secrets - 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. git push origin main- 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.
-
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"). -
Error handling —
thiserrorfor the library error type (TlsError,#[non_exhaustive], one variant per failure category — the alknet ADR-088 shape). No panics in library code. Nounwrap()orexpect()outside tests. If you reach forunwrap, the error path wasn't specified — stop and decide what should actually happen. For poisonedRwLock/Mutex, useunwrap_or_else(|e| e.into_inner())so a panic in one operation does not cascade to other operations. -
tokiois the async runtime — all I/O is async. The ACME state machine is a spawned task; cert loading is sync file I/O behind anasync fnsignature for API uniformity. Use the wasm-clean tokio subset (rt,sync,macros) — do NOT usefeatures = ["full"]in[dependencies](dev-dependencies may usefull). -
The default crate should stay lean — TLS setup and config types only. Transport-specific wrapping is feature-gated:
noq(thefor_noq()accessors),tcp(tokio-rustls),acme(the ACME state machine, a heavy dep). Therustlsdep 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. -
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::MAXon 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 toringor the process-default provider without an ADR.AcceptAnyCertVerifier'ssupported_verify_schemes()returns ED25519 + ECDSA P-256/P-384 + RSA PSS/PKCS1 (SHA256/384/512), verbatim.acme-tls/1ALPN 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-rootswhen the platform store is empty (alknet ADR-088 §5); a containerized deployment with no system CA bundle must still verify public X.509 remotes.
-
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,
Nonepresents nothing,Acmeis a server-only identity (config error on the client path). -
One identity, N transports; one ACME state machine — the central types are
TlsServerConfig/TlsClientConfig, built once and shared (the innerrustlsconfig is Clone — Arc-shared resolvers).TlsServerConfigis notClone(it holds the ACME task'sJoinHandle); share it viaArc. 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). -
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—TlsErroris the config-construction error type (alknet ADR-088 §6). ACME state-machine runtime errors are stream events, logged in the spawned task, notTlsErrorvariants. Do not growTlsErrorto cover handshake outcomes. -
Feature gates — transport-specific deps are opt-in (
noq,tcp,acme);default = []— the default crate compiles lean (ADR-003). Verifycargo test(default) andcargo test --all-featuresboth pass whenever features are touched. -
Upstream posture — this crate extracts working code from alknet (
crates/alknet-tls,crates/alknet-coreconfig/fingerprint). The alknet spec docs (ADR-082/083/084/086/087/088/089 and thecrates/tlsREADME) 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 fromalknet-core— this crate owns them after the rewrite; do not re-import them from alknet. -
Naming — Rust standard:
snake_casefor functions/variables/ modules,PascalCasefor types/traits,SCREAMING_SNAKE_CASEfor constants. -
Module structure — one module per file under
src/, re-exported fromsrc/lib.rs. Public API surface islib.rsre-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.
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 producedocs/architecture/(overview, ADRs, open-questions tracker) perdocs/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 (TlsErrorshape, root-store fallback), ADR-089 (client dial seam), ADR-034 (verifier selection), ADR-091 (ConnectionCredentials), anddocs/architecture/crates/tls/README.md(the full crate spec — the most complete reference for what this crate must do).
- alknet-tls —
- Key open threads carried from alknet (Phase 0 must resolve or defer
them — see
docs/research/phase-0.md):- The
TlsErrorin 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 inalknet-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.
- The
- 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.