phase 2: task decomposition — 8-task port graph
- crate-init: skeleton, feature gates (ADR-003), TlsError (ADR-002) - three parallel foundation ports: identity types, fingerprint, pem+signing - port-server / port-client in parallel (each consumes the foundation; client also carries credentials.rs per ADR-005 co-location) - integration-suite: invariant pins + feature-matrix + seam round-trips - review-impl: spec-conformance gate — the API-freeze point before the alknet rewrite consumes the crate - validated: taskgraph topo (5 generations), no cycles, critical path len 5, risk-path 1.45; medium risk concentrated on the two broad ports + the suite (each a diff against a fixed extraction source, not open-ended work)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
---
|
||||
id: crate-init
|
||||
name: Crate skeleton — module files, feature gates, error type, re-exports
|
||||
status: pending
|
||||
depends_on: []
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: project
|
||||
level: implementation
|
||||
tags: [scaffold, crate-init]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Initialize the alktls module skeleton per ADR-006: replace the
|
||||
placeholder `src/lib.rs` with the eight-module layout
|
||||
(`identity`, `credentials`, `fingerprint`, `server`, `client`, `pem`,
|
||||
`signing` + `lib.rs`), the feature gates per ADR-003
|
||||
(`default = []`; `noq`, `tcp`, `acme`), and the `TlsError` type per
|
||||
ADR-002. Modules may be stubs (`pub fn` bodies `todo!`-free — empty
|
||||
with doc comments) but the module map, features, deps, and error enum
|
||||
land now so subsequent tasks are additive, not structural.
|
||||
|
||||
### What lands here
|
||||
|
||||
- `Cargo.toml` final shape: deps per ADR-003's TOML block (`noq` +
|
||||
`noq-proto` optional, default-features off; `tokio-rustls`;
|
||||
`rustls-acme`; the always-present set per the overview's dependency
|
||||
posture).
|
||||
- `src/lib.rs`: crate docs (the overview's shape), `TlsError` (the
|
||||
ADR-002 enum verbatim, including the `noq`-gated `NoqWrap` variant),
|
||||
and the re-export block (the documented public API surface).
|
||||
- Empty module files with `//!` doc headers only — no function bodies.
|
||||
- `.taskgraph.toml` not needed (default `./tasks` works).
|
||||
|
||||
### What does NOT land here
|
||||
|
||||
- Any ported logic (subsequent tasks).
|
||||
- Any test beyond a compile assertion that the error enum matches.
|
||||
|
||||
## Work
|
||||
|
||||
1. Rewrite `Cargo.toml` features/deps per ADR-003.
|
||||
2. Write `src/lib.rs` with the ADR-002 `TlsError` + module
|
||||
declarations + re-exports.
|
||||
3. Create the seven module files with doc headers.
|
||||
4. Verify the feature matrix compiles (all four combos below).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `cargo build` (default, `default = []`) passes
|
||||
- [ ] `cargo test --all-features` passes (noq + tcp + acme all resolve)
|
||||
- [ ] Each feature alone: `cargo check --features noq`, `--features tcp`,
|
||||
`--features acme`
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
- [ ] `TlsError` is `#[non_exhaustive]` with exactly the ADR-002
|
||||
variants (`CertLoad`, `SelfSigned`, `Rustls`, `VerifierBuild`,
|
||||
`NoqWrap` noq-gated, `AcmeConfig`)
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] The module map matches ADR-006 (one module per file, re-exported
|
||||
from `lib.rs`; public API surface is the re-export block)
|
||||
- [ ] Feature gates match ADR-003 exactly
|
||||
- [ ] No `todo!`/`unimplemented!`/panics in library code — stubs are
|
||||
empty or error-returning, never panicking
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/decisions/002-tlserror-shape.md (the enum)
|
||||
- docs/architecture/decisions/003-noq-replaces-quinn.md (features, deps)
|
||||
- docs/architecture/decisions/006-module-layout-and-tests.md (module map)
|
||||
- docs/architecture/overview.md (dependency posture)
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation. Document any decisions,
|
||||
> deviations from architecture, or relevant context discovered.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion. Brief description of what was
|
||||
> implemented, files changed, and any follow-up needed.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: integration-suite
|
||||
name: Integration suite — invariant pins + feature-matrix + seam round-trips (tests/)
|
||||
status: pending
|
||||
depends_on: [port-server, port-client]
|
||||
scope: broad
|
||||
risk: medium
|
||||
impact: phase
|
||||
level: implementation
|
||||
tags: [tests, integration, invariants, phase-gate]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
The `tests/` integration suite per ADR-006: the cross-module surfaces
|
||||
the in-module seed tests cannot cover. A full-crate crate has no
|
||||
workspace consumers to lean on, so this suite is the invariant gate
|
||||
before the alknet rewrite consumes the crate.
|
||||
|
||||
### The suites
|
||||
|
||||
1. **Server seam round-trips** — for each `TlsIdentity` variant
|
||||
(X509 via generated PEM files in a tempdir, RawKey via
|
||||
`Ed25519SecretKey::generate`, SelfSigned):
|
||||
`TlsServerConfig::new` → `for_noq()` (all-features) /
|
||||
`for_tcp_tls()` / `rustls_config()` all succeed; the rustls config
|
||||
carries the expected ALPN list and `max_early_data_size`.
|
||||
2. **Client seam round-trips** — `TlsClientConfig::new` per
|
||||
credentials cell → `for_noq()` / `into_rustls_config()`;
|
||||
`enable_early_data` pinned true; ALPN single-value list.
|
||||
3. **Verifier-selection matrix** — the full `local_identity` ×
|
||||
`remote_identity` construction matrix (the port-client unit
|
||||
assertions promoted to integration level, exercising the public API
|
||||
only).
|
||||
4. **The nine-scheme exact-list pin** — the regression-proof shape
|
||||
(exact vec equality, not membership).
|
||||
5. **Root-store fallback** — `load_platform_root_cert_store` (or the
|
||||
config built from it) yields a non-empty store even when the
|
||||
platform store is empty (webpki-roots merge; assert non-emptiness
|
||||
and count > 0).
|
||||
6. **ACME lifecycle** (`--features acme`): construct an `Acme` identity
|
||||
with staging URL + tempdir cache; assert spawn-and-return semantics,
|
||||
`acme-tls/1` in the ALPN list, and the resolver wiring (no network
|
||||
I/O — do NOT hit Let's Encrypt).
|
||||
7. **Feature-matrix gate**: `default = []` compiles + tests pass;
|
||||
`--features noq`; `--features tcp`; `--features acme`;
|
||||
`--all-features` (the gate is also a CI-verifiable convention, but
|
||||
the suite includes the combination that exercises every
|
||||
feature-gated accessor).
|
||||
|
||||
### Out of scope (explicitly)
|
||||
|
||||
Real handshakes (transport crates' job — the scope boundary), network
|
||||
ACME orders, iroh (no iroh dep exists here), SOCKS5, dispatch.
|
||||
|
||||
## Work
|
||||
|
||||
1. Write the `tests/` files (one per suite group is fine).
|
||||
2. Promote any in-module assertions that exercise cross-module
|
||||
behavior to integration level (keep the in-module ones too).
|
||||
3. Wire the exact-list and `enable_early_data` pins.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Full suite green across all five feature combos
|
||||
- [ ] The nine-scheme test asserts exact list equality
|
||||
- [ ] `enable_early_data == true` asserted on a client config
|
||||
- [ ] ACME test performs no network I/O (staging URL is constructed,
|
||||
never contacted)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`,
|
||||
`cargo doc --no-deps` (doc comments render)
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All 7 suites exist and pass
|
||||
- [ ] The suite runs green with a single `cargo test --all-features`
|
||||
- [ ] `tests/` layout documented in the task Summary
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/decisions/006-module-layout-and-tests.md (the test
|
||||
plan)
|
||||
- docs/architecture/server.md + client.md (the invariants)
|
||||
- Prior art: the extracted crate's in-module tests
|
||||
(`crates/alknet-tls/src/*.rs` `#[cfg(test)]` blocks)
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
id: port-client
|
||||
name: Port client side — TlsClientConfig, verifier selection, client auth (src/client.rs)
|
||||
status: pending
|
||||
depends_on: [port-identity-types, port-fingerprint, port-pem-signing]
|
||||
scope: broad
|
||||
risk: medium
|
||||
impact: phase
|
||||
level: implementation
|
||||
tags: [client, verifiers, port, invariants]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Port `src/client.rs` from alknet-tls per the ADRs: `TlsClientConfig`,
|
||||
`select_server_verifier`, `build_client_auth`,
|
||||
`RawKeyClientCertResolver`, `NoClientCertResolver`,
|
||||
`FingerprintPinVerifier`, `load_platform_root_cert_store`.
|
||||
|
||||
### The API deltas (from the extracted code)
|
||||
|
||||
- Types rewire: `ConnectionCredentials`/`RemoteIdentity` come from this
|
||||
crate's `credentials.rs` (ADR-005); fingerprint helpers from this
|
||||
crate's `fingerprint.rs`; the identity from this crate's
|
||||
`identity.rs`.
|
||||
- `for_noq(self)` replaces `for_quinn(self)` (consuming — ADR-004);
|
||||
`into_rustls_config(self)` unchanged.
|
||||
|
||||
### Credentials module (small, rides here)
|
||||
|
||||
`src/credentials.rs` — port `ConnectionCredentials` (`local_identity:
|
||||
Option<TlsIdentity>`, `remote_identity: Option<RemoteIdentity>`, the
|
||||
builder methods) and `RemoteIdentity` (`fingerprint: String`) from
|
||||
alknet-core `credentials.rs`, including the load-bearing doc comments
|
||||
(`None` is the public-X.509-endpoint state, not a placeholder — the
|
||||
`Option`s drive verifier selection). This task owns the module because
|
||||
the verifier selection is the semantic consumer of the bundle; the
|
||||
co-location prevents semantic drift (ADR-005).
|
||||
|
||||
### The invariants / selection matrix (test-pinned here)
|
||||
|
||||
- Every config: `enable_early_data = true` (the client half of the
|
||||
0-RTT invariant — ADR-001; pinned as a test) and the aws-lc-rs
|
||||
provider.
|
||||
- Verifier selection matrix: `Some(fingerprint)` →
|
||||
`FingerprintPinVerifier`; `None` → `WebPkiServerVerifier` over the
|
||||
root store. Fail-closed for unknown raw-key remotes is structural
|
||||
(the CA verifier is what `None` installs; a raw-key remote cannot
|
||||
satisfy it — the failure manifests at handshake, never via
|
||||
`TlsError`).
|
||||
- Root-store fallback: platform certs first; if empty, merge
|
||||
`webpki-roots` (never empty); native-certs load errors logged, not
|
||||
returned. Testable by asserting the merge path (construct with an
|
||||
empty platform store simulation if the API permits; otherwise assert
|
||||
the fallback branch by construction — `load_platform_root_cert_store`
|
||||
returns a non-empty store).
|
||||
- Client-auth presentation: RawKey → RFC 7250 SPKI cert with
|
||||
`only_raw_public_keys()` auto-detected from the DER; X509 → loaded
|
||||
chain via `CertifiedKey::from_der` (errors → `TlsError::Rustls` per
|
||||
ADR-002); `SelfSigned`/`None` → `NoClientCertResolver`
|
||||
(`has_certs() == false`); `Acme` → `TlsError::AcmeConfig`.
|
||||
- `FingerprintPinVerifier`: pin match / mismatch on
|
||||
`verify_server_cert`; TLS 1.2/1.3 signature verification routes
|
||||
Ed25519 SPKI through `verify_tls13_signature_with_raw_key`; a
|
||||
mismatched pin fails verification.
|
||||
|
||||
## Work
|
||||
|
||||
1. Port `credentials.rs` (types + builders + doc comments).
|
||||
2. Port `client.rs`; apply the deltas; rewire imports.
|
||||
3. Port the extracted in-module tests.
|
||||
4. Add the selection-matrix integration test (below).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Selection-matrix test: all four client-auth presentations × both
|
||||
verifier branches construct and select the expected resolver
|
||||
types (inspect via the config's client-auth/verifier state where
|
||||
the API permits; otherwise assert construction success/error
|
||||
kind per cell)
|
||||
- [ ] `Acme` local identity → `TlsError::AcmeConfig`
|
||||
- [ ] `enable_early_data == true` pinned
|
||||
- [ ] Root store non-empty (fallback exercised)
|
||||
- [ ] FingerprintPinVerifier unit tests ported (pin match, mismatch,
|
||||
raw-key signature routing)
|
||||
- [ ] `cargo test` (default), `cargo test --all-features`,
|
||||
`cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `for_noq(self)` / `into_rustls_config(self)` per ADR-004
|
||||
- [ ] The selection matrix has no fourth path (fail-closed structural)
|
||||
- [ ] `lib.rs` re-exports the client surface + `ConnectionCredentials`
|
||||
/ `RemoteIdentity`
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/client.md (the normative doc)
|
||||
- docs/architecture/decisions/002-tlserror-shape.md, 004, 005
|
||||
- alknet ADR-034 §3, ADR-088 §5, ADR-091
|
||||
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-tls/src/client.rs`,
|
||||
`/workspace/@alkdev/alknet/crates/alknet-core/src/credentials.rs`
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: port-fingerprint
|
||||
name: Port fingerprint helpers + DER parser (src/fingerprint.rs)
|
||||
status: pending
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: component
|
||||
level: implementation
|
||||
tags: [fingerprint, port]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Port the fingerprint module from alknet-core
|
||||
(`crates/alknet-core/src/fingerprint.rs`) into `src/fingerprint.rs`
|
||||
per ADR-005: `fingerprint_from_cert_der(&[u8]) -> Option<String>`
|
||||
(`ed25519:<hex>` for RFC 7250 Ed25519 SPKI, `SHA256:<hex>` for
|
||||
anything else — the normalized formats from alknet ADR-030 §6),
|
||||
`extract_ed25519_raw_key_from_spki(&[u8]) -> Option<[u8; 32]>`, and
|
||||
the private manual DER parser (`DerParser`).
|
||||
|
||||
### Invariants
|
||||
|
||||
- Production code stays `sha2` + manual DER — no `rustls::` imports in
|
||||
the module's non-test code (the extracted module's purity; ADR-006).
|
||||
- The Ed25519 OID constant is `[0x2b, 0x65, 0x70]` (`1.3.101.112`);
|
||||
the SPKI BIT STRING is 33 bytes (one unused-bits `0x00` + the
|
||||
32-byte key). These are the RFC 7250 wire facts the parser encodes.
|
||||
- `extract_ed25519_raw_key_from_spki` returns `None` for non-Ed25519
|
||||
SPKI / malformed DER / X.509 certs; `fingerprint_from_cert_der`
|
||||
falls back to SHA-256-hashing the full DER (returns `None` only for
|
||||
empty input).
|
||||
- Port the extracted in-module DER parser tests verbatim (they cover
|
||||
the malformed-input edges: truncated headers, long-form lengths,
|
||||
wrong OIDs, bad BIT STRING lengths).
|
||||
|
||||
## Work
|
||||
|
||||
1. Port the module wholesale (it is self-contained).
|
||||
2. Port the extracted tests; assert `ed25519:<hex>` and `SHA256:<hex>`
|
||||
normalization on representative inputs.
|
||||
3. Confirm no `rustls::` import outside `#[cfg(test)]`.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Ported tests green (`cargo test fingerprint`)
|
||||
- [ ] Round-trip: an Ed25519 SPKI built by `signing.rs`'s
|
||||
`spki_public_key()` yields `ed25519:<hex>` matching the source
|
||||
key (integration assert — this pins the normalization across
|
||||
the raw-key paths)
|
||||
- [ ] Malformed-DER inputs yield `None` / SHA fallback (ported edge
|
||||
tests)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] The module compiles without `rustls` in production code
|
||||
- [ ] Both normalized fingerprint formats are test-pinned
|
||||
- [ ] `lib.rs` re-exports the two public functions
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/decisions/005-config-types-move-into-alktls.md
|
||||
- docs/architecture/decisions/006-module-layout-and-tests.md
|
||||
- alknet ADR-030 §6 (fingerprint normalization — the reference)
|
||||
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-core/src/fingerprint.rs`
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: port-identity-types
|
||||
name: Port identity types — TlsIdentity, Ed25519SecretKey, AcmeDirectory (src/identity.rs)
|
||||
status: pending
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: component
|
||||
level: implementation
|
||||
tags: [identity, types, port]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Port the identity types from alknet-core `config.rs` into
|
||||
`src/identity.rs` per ADR-005: `TlsIdentity` (four variants: `X509 {
|
||||
cert, key }`, `RawKey(Ed25519SecretKey)`, `SelfSigned`, `Acme {
|
||||
domains, cache_dir, directory, contact }`), `Ed25519SecretKey`, and
|
||||
`AcmeDirectory` (`Production` / `Staging` / `Custom(String)` with
|
||||
`url()`).
|
||||
|
||||
### The load-bearing surface (do not change)
|
||||
|
||||
- `Ed25519SecretKey`: `generate()`, `from_bytes(&[u8; 32])`,
|
||||
`as_bytes() -> [u8; 32]`, `public() -> ed25519_dalek::VerifyingKey`,
|
||||
`sign(&self, message) -> ed25519_dalek::Signature`. The byte surface
|
||||
is what iroh's `iroh_base::SecretKey` consumes (ADR-005; verified
|
||||
against iroh 1.1 in Phase 0). Backed by `ed25519_dalek::SigningKey`.
|
||||
- `Debug` must NOT leak key material (the extracted type formats as
|
||||
`Ed25519SecretKey(..)` — keep it).
|
||||
- `AcmeDirectory::Production` / `Staging` URLs are pinned strings
|
||||
(Let's Encrypt production + staging) — assert them in tests.
|
||||
- Doc comments carry the OQ-TLS-02 resolution: `SelfSigned` on the
|
||||
client path presents nothing (documented on the variant).
|
||||
|
||||
### What moves vs stays
|
||||
|
||||
`TlsIdentity`/`Ed25519SecretKey`/`AcmeDirectory` move here wholesale.
|
||||
`PeerEntry`, `AuthPolicy`, `Identity`, fingerprint → peer-id
|
||||
resolution stay OUT (auth layer — ADR-005's carve-out). No
|
||||
`serde` derives unless the extracted code has them (check; do not add
|
||||
new surface beyond ADR-005's list without noting it).
|
||||
|
||||
## Work
|
||||
|
||||
1. Port the three types + their inherent methods from
|
||||
`crates/alknet-core/src/config.rs`.
|
||||
2. Port the associated in-module tests (`generate`/`from_bytes`
|
||||
round-trip, `AcmeDirectory` URL assertions).
|
||||
3. Add the Debug-no-leak test if not present in the extracted tests.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `cargo test -p alktls identity` passes (ported tests green)
|
||||
- [ ] `as_bytes`/`from_bytes` round-trip asserted
|
||||
- [ ] `AcmeDirectory` URLs asserted (production + staging + custom)
|
||||
- [ ] `Debug` output contains no key bytes (test: format then assert
|
||||
hex key absent)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `src/identity.rs` holds exactly the ADR-005 type set; no auth
|
||||
layer types present
|
||||
- [ ] The byte surface matches the load-bearing list above verbatim
|
||||
- [ ] `lib.rs` re-exports the three types
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/decisions/005-config-types-move-into-alktls.md
|
||||
- docs/architecture/decisions/006-module-layout-and-tests.md (module map)
|
||||
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-core/src/config.rs`
|
||||
(lines 33–80: `Ed25519SecretKey`, `TlsIdentity`, `AcmeDirectory`)
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: port-pem-signing
|
||||
name: Port PEM loading + Ed25519 signing helper (src/pem.rs, src/signing.rs)
|
||||
status: pending
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: component
|
||||
level: implementation
|
||||
tags: [pem, signing, port]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Port the two shared helper modules from alknet-tls per ADR-005/ADR-006:
|
||||
|
||||
- `src/pem.rs` — `load_cert_chain(&Path) ->
|
||||
Result<Vec<CertificateDer<'static>>, TlsError>` and
|
||||
`load_private_key(&Path) -> Result<PrivateKeyDer<'static>, TlsError>`.
|
||||
Read + `rustls_pemfile` parse; errors funnel through `TlsError` (the
|
||||
extracted `io::Error` paths map to `TlsError::CertLoad` per
|
||||
ADR-002's `#[from]`).
|
||||
- `src/signing.rs` — `Ed25519SigningKey`: the rustls
|
||||
`SigningKey` + `Signer` adapter over `Ed25519SecretKey`
|
||||
(`choose_scheme` returns Some only for ED25519; `algorithm()`
|
||||
is ED25519; `spki_public_key()` builds the SubjectPublicKeyInfoDer
|
||||
via `rustls::sign::public_key_to_spki`). One copy shared by server +
|
||||
client resolvers.
|
||||
|
||||
### Notes
|
||||
|
||||
- The extracted `pem.rs` maps a no-key-found file to an
|
||||
`io::ErrorKind::InvalidData` error — under ADR-002 that surfaces as
|
||||
`TlsError::CertLoad` (the `#[from] io::Error`). Keep the error kind;
|
||||
the variant mapping is automatic.
|
||||
- `Ed25519SigningKey` now wraps *this crate's* `Ed25519SecretKey` (from
|
||||
`port-identity-types`), not alknet-core's — the import is the one
|
||||
intentional change from the extraction source.
|
||||
- Port the extracted tests: choose_scheme Some/None, algorithm,
|
||||
SPKI non-empty, 64-byte signature, Debug-no-leak; PEM error paths
|
||||
(missing file, empty file).
|
||||
|
||||
## Work
|
||||
|
||||
1. Port `pem.rs` + tests (adjust error construction to this crate's
|
||||
`TlsError`).
|
||||
2. Port `signing.rs` + tests (rewire to the local identity type).
|
||||
3. Cross-module test: `load_cert_chain`/`load_private_key` on a
|
||||
generated self-signed PEM pair (rcgen) round-trip.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `cargo test pem signing` green (ported + round-trip tests)
|
||||
- [ ] Signature length is 64 bytes; scheme is ED25519
|
||||
- [ ] PEM error paths return `TlsError::CertLoad` (not panics)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Both modules match the extracted source modulo the identity-type
|
||||
import and error mapping
|
||||
- [ ] `lib.rs` re-exports `load_cert_chain`, `load_private_key`,
|
||||
`Ed25519SigningKey`
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/decisions/002-tlserror-shape.md (the error mapping)
|
||||
- docs/architecture/decisions/005-config-types-move-into-alktls.md
|
||||
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-tls/src/pem.rs`,
|
||||
`.../src/signing.rs`
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
id: port-server
|
||||
name: Port server side — TlsServerConfig, resolvers, ACME path (src/server.rs)
|
||||
status: pending
|
||||
depends_on: [port-identity-types, port-pem-signing]
|
||||
scope: broad
|
||||
risk: medium
|
||||
impact: phase
|
||||
level: implementation
|
||||
tags: [server, acme, port, invariants]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Port `src/server.rs` from alknet-tls per the ADRs: `TlsServerConfig`,
|
||||
`build_rustls_server_config`, `RawKeyCertResolver`,
|
||||
`AcceptAnyCertVerifier`, `SelfSignedCert` / `generate_self_signed_cert`,
|
||||
and the ACME branch. This is the biggest port task; the deltas against
|
||||
the extraction source are all ADR-pinned, everything else is a port.
|
||||
|
||||
### The API deltas (from the extracted code)
|
||||
|
||||
- Accessors borrow: `for_noq(&self)` (ADR-004), `for_tcp_tls(&self)`
|
||||
(adopted — did not exist in the extraction), `rustls_config(&self)`
|
||||
(adopted — the field was `pub(crate)`; now the re-exported accessor).
|
||||
- `for_noq` replaces `for_quinn`:
|
||||
`noq::ServerConfig::with_crypto(Arc::new(
|
||||
noq::crypto::rustls::QuicServerConfig::try_from(inner)?))`; the
|
||||
failure maps to `TlsError::NoqWrap` (ADR-002/003).
|
||||
- `build_rustls_server_config`'s `TlsIdentity::Acme` arm: the
|
||||
extracted `unreachable!` becomes a `TlsError::AcmeConfig` return
|
||||
(ADR-006; no panics in library code). The internal dispatch
|
||||
invariant (Acme is handled by `new_acme`) is preserved — the arm is
|
||||
defensive.
|
||||
- Field name: `acme_handle` (ADR-006 unified on the alknet-spec name).
|
||||
|
||||
### The behavior-preservation invariants (all must be test-pinned here)
|
||||
|
||||
- `max_early_data_size = u32::MAX` on every path (X509, RawKey,
|
||||
SelfSigned, ACME branch).
|
||||
- `aws_lc_rs::default_provider()` on every path (constructed via
|
||||
`builder_with_provider`; no process-default fallback).
|
||||
- `AcceptAnyCertVerifier::supported_verify_schemes()` returns the
|
||||
nine schemes verbatim (ED25519; ECDSA P-256/P-384; RSA PSS
|
||||
256/384/512; RSA PKCS1 256/384/512).
|
||||
- `acme-tls/1` appended by the crate on the ACME path only.
|
||||
- Verifier behavior: `offer_client_auth() == true`,
|
||||
`client_auth_mandatory() == false`, empty `root_hint_subjects`,
|
||||
accepts any client cert.
|
||||
- `RawKeyCertResolver`: `only_raw_public_keys() == true`, resolves the
|
||||
SPKI-backed `CertifiedKey`.
|
||||
|
||||
### ACME path (feature `acme`)
|
||||
|
||||
Port `new_acme` per server.md: `rustls_acme::AcmeConfig` + `DirCache`
|
||||
+ directory URL + contacts; `state.resolver()` wired in; `acme-tls/1`
|
||||
appended; the event-loop task spawned (`EventOk`/`EventError` matched
|
||||
to `tracing` logs — port the extracted log lines); returns immediately.
|
||||
Handle stored in `acme_handle`, never aborted (detached; OQ-TLS-06).
|
||||
Without the feature, `Acme` identities return `TlsError::AcmeConfig`.
|
||||
|
||||
## Work
|
||||
|
||||
1. Port the module; apply the deltas above; rewire imports to this
|
||||
crate's types (identity, signing, pem, TlsError).
|
||||
2. Port the extracted in-module tests (they assert most invariants).
|
||||
3. Add the exact nine-scheme list pin (the extracted test only checks
|
||||
two schemes' membership).
|
||||
4. Feature-combo verification (below).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Invariant pins green: `max_early_data_size` per path,
|
||||
nine-scheme exact list, resolver behavior, verifier behavior
|
||||
- [ ] ACME branch (with `--features acme`): spawns, returns
|
||||
immediately, appends `acme-tls/1` (test with a staging URL +
|
||||
tempdir cache; do NOT hit Let's Encrypt — construct and assert
|
||||
config state, assert the ALPN list)
|
||||
- [ ] `Acme` identity without the `acme` feature → `TlsError::AcmeConfig`
|
||||
- [ ] `for_noq` / `for_tcp_tls` round-trip per identity variant
|
||||
(construction-level; handshakes are out of scope)
|
||||
- [ ] `cargo test` (default), `cargo test --all-features`,
|
||||
`cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All five server invariants are test-asserted (not just compiled)
|
||||
- [ ] `for_noq`, `for_tcp_tls`, `rustls_config` exist with ADR-004
|
||||
signatures (`&self`; `for_tcp_tls` infallible)
|
||||
- [ ] No `unreachable!`/panics in library code
|
||||
- [ ] `lib.rs` re-exports the server surface
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/server.md (the normative doc)
|
||||
- docs/architecture/decisions/002-tlserror-shape.md, 003, 004
|
||||
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-tls/src/server.rs`
|
||||
(port source; note every delta above)
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
id: review-impl
|
||||
name: Review alktls v1 implementation for spec conformance (pre-rewrite gate)
|
||||
status: pending
|
||||
depends_on: [integration-suite]
|
||||
scope: moderate
|
||||
risk: low
|
||||
impact: phase
|
||||
level: review
|
||||
tags: [review, phase-gate, spec-conformance]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Review the completed port against the architecture docs before the
|
||||
alknet rewrite consumes the crate. This is the one-way-door gate: the
|
||||
public API surface freezes here (the rewrite compiles against it).
|
||||
|
||||
### The checklist
|
||||
|
||||
1. **API surface == ADR-004** — every accessor signature matches the
|
||||
ADR sketches exactly (`&self` server / `self` client; `for_tcp_tls`
|
||||
infallible; `rustls_config` borrows). Re-export block is the
|
||||
documented surface.
|
||||
2. **`TlsError` == ADR-002** — six variants, `#[non_exhaustive]`, no
|
||||
string catch-all, `#[source]` chains intact.
|
||||
3. **Invariants == ADR-001/server.md/client.md** — all five server
|
||||
invariants + the client 0-RTT half + fail-closed structure, each
|
||||
with a passing test (check the test asserts the behavior, not
|
||||
that a compile succeeded).
|
||||
4. **Deltas vs the extraction source** — diff `src/` against
|
||||
`crates/alknet-tls/src/` + the moved alknet-core modules; every
|
||||
difference maps to an ADR-pinned delta (identity-type rewire,
|
||||
error mapping, `for_noq`, borrowed accessors, no `unreachable!`).
|
||||
Any un-pinned divergence is either fixed or recorded.
|
||||
5. **Feature hygiene** — `default = []` lean (AGENTS.md convention 4);
|
||||
tokio subset (`rt`, `sync`, `macros`) in `[dependencies]`; doc
|
||||
comments on the public API; no inline `//` comments outside the
|
||||
convention.
|
||||
6. **Docs sync** — if the port revealed a spec mismatch, the ADR/spec
|
||||
gets an amendment note (not silent divergence).
|
||||
|
||||
## Work
|
||||
|
||||
1. Run the checklist against the code.
|
||||
2. File findings (fix-forward for small ones; blockers get Safe Exit
|
||||
treatment).
|
||||
3. Update `docs/architecture/README.md` statuses (Draft → Reviewed for
|
||||
the specs) when the checklist passes.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] The full checklist passes with findings recorded
|
||||
- [ ] `cargo test`, `cargo test --all-features`,
|
||||
`cargo clippy --all-targets -- -D warnings`,
|
||||
`cargo fmt --check`, `cargo doc --no-deps` all green
|
||||
- [ ] `cargo publish --dry-run --allow-dirty` passes (packaging
|
||||
readiness: metadata, license files, exclude list)
|
||||
- [ ] Spec statuses advanced where the gate passes
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Zero un-pinned divergences from the architecture docs
|
||||
- [ ] The API surface is declared frozen for the rewrite (README
|
||||
lifecycle note)
|
||||
- [ ] Findings + resolutions documented in the task Summary
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/ (all ADRs + specs — the conformance target)
|
||||
- docs/research/phase-0.md §Prior art (the extraction deltas)
|
||||
- AGENTS.md (conventions 1–12)
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
Reference in New Issue
Block a user