Files
alktls/AGENTS.md
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

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:

  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 handlingthiserror 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 TlsErrorTlsError 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.

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.