Initial Commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# restore-crypto-opinionated-layer — build the missing modules in `@suaveplan/crypto`
|
||||
|
||||
**Packages:** `@suaveplan/crypto`
|
||||
**Status:** PROPOSED
|
||||
**Type:** feature (additive; no breaking changes to existing modules)
|
||||
**Carved from:** `restore-crypto-keychain-packer` umbrella (2026-05-03)
|
||||
**Depends on:** `restore-crypto-cleanup-duplicates` and `restore-crypto-fold-keys-vault` must land first
|
||||
|
||||
## Why
|
||||
|
||||
After slices 1 and 2, `@suaveplan/crypto` holds the keychain, derivation, bundles, domain-tags, authority shell, errors, and folded keypair / BIP39 / legacy-Vault surface. The big gap is the *behavioural* opinionated layer — the actual signing hierarchy, signed envelopes, and append-only structures that turn primitives into a coherent product. Without these modules, the package is a label without a substance.
|
||||
|
||||
This slice ships the seven missing modules:
|
||||
|
||||
1. `Authority` — concrete in-memory backend behind the existing interface; signs under a `DerivationCoordinate` slot, rejects revoked slots, enforces validity windows, zeroises on `dispose()`
|
||||
2. `RootIdentity` / `ServerIdentity` and `createRoot` / `createServer` / `rollServer` / `revokeServer` — typed wrappers around the keychain pair and the hierarchy operations that were modelled by upstream `nx/libs/keys/src/lib/keys-common.ts`
|
||||
3. `Vault` (three-regime) — replaces the transitional `legacy-vault` from slice 2 with the spec-defined three-regime serialisation: public bundle (parent-signed, plaintext), private-key blob (password-AEAD, rotatable via `changePassword` without re-derivation), vault payload (auth-box-sealed under keypair, parent-signed under `DOMAIN_VAULT_V1`)
|
||||
4. `Packer` / `Unpacker` — schema-driven encrypt-and-sign envelope over `@suaveplan/bitpacker`; `issuedAt` + `nonce` for replay-window protection
|
||||
5. `ChunkedPacker` / `ChunkedUnpacker` — per-chunk-signed P2P fragmentation; tampered chunks fail at reassembly
|
||||
6. `RevocationList` — root-signed CRL; append-only; verify-against-anchor
|
||||
7. `AuditLog` — append-only Merkle log; root-signed root; tamper-detection on past entries
|
||||
8. `wire-format/` — eight schemas committed with golden-bytes KAT fixtures: `DERIVATION_COORDINATE_SCHEMA`, `KEY_SLOT_PRIVATE_SCHEMA`, `KEY_SLOT_PUBLIC_SCHEMA`, `VAULT_PAYLOAD_SCHEMA`, `PUBLIC_BUNDLE_SCHEMA`, `CHAIN_BUNDLE_SCHEMA`, `CRL_ENTRY_SCHEMA`, `AUDIT_ENTRY_SCHEMA`
|
||||
|
||||
Property-based round-trip tests using `fast-check` accompany every schema.
|
||||
|
||||
## What changes
|
||||
|
||||
### Added — `@suaveplan/crypto/src/`
|
||||
|
||||
- `authority/in-memory-authority/` — concrete `InMemoryAuthority` implementing the existing `Authority` interface
|
||||
- `identity/root-identity/`, `identity/server-identity/`, `identity/operations/` — `createRoot`, `createServer`, `rollServer`, `revokeServer`
|
||||
- `vault/vault.ts` (three-regime), `vault/vault.types.ts`, `vault/changePassword.ts`, `vault/rotateApplicationData.ts` — replaces `legacy-vault/`
|
||||
- `packer/packer.ts` (encrypt-and-sign envelope), `packer/replay-window.ts`
|
||||
- `chunked-packer/chunked-packer.ts`, `chunked-packer/reassembly.ts`
|
||||
- `revocation/revocation-list.ts`
|
||||
- `audit-log/audit-log.ts`, `audit-log/merkle.ts`
|
||||
- `wire-format/{derivation-coordinate,key-slot-private,key-slot-public,vault-payload,public-bundle,chain-bundle,crl-entry,audit-entry}-schema.ts` (eight schemas)
|
||||
- `test-fixtures/wire-format/*.bin` — eight golden-bytes KAT fixtures
|
||||
- Co-located `.md` for every implementation file (≥ 200 words, basic + advanced examples, full API reference)
|
||||
|
||||
### Modified
|
||||
|
||||
- `vault/legacy-vault/` — deleted in this slice once the three-regime `Vault` ships and any internal callers migrate
|
||||
- `crypto/src/index.ts` — re-exports the new modules
|
||||
- `crypto/src/errors/codes/codes.ts` — add codes for `PACKER.*`, `CHUNK.*`, `CRL.*`, `AUDIT.*`, `VAULT.PAYLOAD_*`, `AUTHORITY.SLOT_REVOKED`, etc.
|
||||
|
||||
### Tests added
|
||||
|
||||
- Per-feature `*.test.ts` for happy-path + every documented failure mode
|
||||
- Per-feature `*.spec-conformance.test.ts` for type-level + observability + lifecycle scenarios
|
||||
- `tests/wire-format-kat.test.ts` golden-bytes assertion against every committed fixture
|
||||
- `tests/property-roundtrips.test.ts` — `fast-check` property tests for every pack/unpack pair
|
||||
- Tampered-vault, tampered-chunk, expired-key, revoked-key, replay-rejection scenarios each pinned by a named `it(...)`
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `@suaveplan/crypto-cli` package — slice 4
|
||||
- `@suaveplan/slsa` migration to `Authority` — slice 4
|
||||
- Import-allowlist conformance gate — slice 4
|
||||
- HSM / threshold / YubiKey backends for `Authority` (the *interface* allows them; concrete drivers are dedicated future changes)
|
||||
|
||||
## Source mapping
|
||||
|
||||
The modules in this slice fold the contents of `restore-crypto-keychain-packer/specs/crypto/spec.md` requirements 7 (`Vault`), 8 (`Packer`), 9 (`ChunkedPacker`), 11 (`RevocationList`), 12 (`AuditLog`), 13 (`Authority` concrete backend), 14 (`Hierarchical identity`), 18 (`Wire-format KAT vectors`), and 19 (`Property-based round-trip`).
|
||||
|
||||
| Requirement | Umbrella spec source | Notes |
|
||||
|---|---|---|
|
||||
| `InMemoryAuthority` | umbrella `### Requirement: Authority — signing abstraction over hierarchical identity` | Concrete backend behind existing interface |
|
||||
| `RootIdentity` / `ServerIdentity` / hierarchy ops | umbrella `### Requirement: Hierarchical identity — Root and Server` | Verbatim |
|
||||
| Three-regime `Vault` | umbrella `### Requirement: Vault — three-regime serialisation with BIP39 restore and arbitrary application data` | Replaces transitional legacy-vault |
|
||||
| `Packer` / `Unpacker` | umbrella `### Requirement: Packer — encrypt-and-sign envelope` | Verbatim |
|
||||
| `ChunkedPacker` / `ChunkedUnpacker` | umbrella `### Requirement: ChunkedPacker — per-chunk-signed P2P fragmentation` | Verbatim |
|
||||
| `RevocationList` | umbrella `### Requirement: RevocationList — root-signed CRL` | Verbatim |
|
||||
| `AuditLog` | umbrella `### Requirement: AuditLog — append-only Merkle log` | Verbatim |
|
||||
| Wire-format KAT vectors | umbrella `### Requirement: Wire-format KAT vectors` | Eight golden-bytes fixtures |
|
||||
| Property-based round-trip | umbrella `### Requirement: Property-based round-trip coverage` | fast-check |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- 100/100/100/100 coverage on every new module
|
||||
- Per-feature `it(...)` names match the `**Verified by:**` lines in the spec deltas
|
||||
- KAT fixtures committed and asserted byte-for-byte on every CI run
|
||||
- Property tests run 100+ iterations per pack/unpack pair
|
||||
- `bun run check` clean across monorepo
|
||||
- `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/crypto` green
|
||||
|
||||
## Migration impact
|
||||
|
||||
- The transitional `vault/legacy-vault/` is deleted in this slice. Any internal code (no out-of-package consumer today) that imported from `@suaveplan/crypto/vault/legacy-vault` migrates to the three-regime `Vault` at `@suaveplan/crypto/vault`.
|
||||
- No public API breaks for anything else.
|
||||
@@ -0,0 +1,162 @@
|
||||
# Capability Spec Delta: crypto (restore-crypto-opinionated-layer)
|
||||
|
||||
**Package:** `@suaveplan/crypto`
|
||||
**Status:** PROPOSED
|
||||
|
||||
> Each ADDED requirement carries a `**Verified by:**` line. The full umbrella spec at `restore-crypto-keychain-packer/specs/crypto/spec.md` enumerates per-scenario detail; this slice's spec covers the seven new modules.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: `InMemoryAuthority` concrete backend
|
||||
|
||||
The package SHALL ship `InMemoryAuthority`, a concrete implementation of the `Authority` interface that signs under a `DerivationCoordinate` slot, rejects revoked slots, enforces the slot's validity window against the injected `Clock`, and zeroises every secret on `dispose()`.
|
||||
|
||||
#### Scenario: signing with an in-window slot succeeds
|
||||
|
||||
- **WHEN** a caller asks `authority.sign(domainTag, payload)` against a slot whose `notBefore <= clock() < notAfter`
|
||||
- **THEN** a verifiable signature is returned
|
||||
|
||||
#### Scenario: signing with a revoked slot fails
|
||||
|
||||
- **WHEN** the same call is issued against a slot whose `revokedAt` is in the past
|
||||
- **THEN** `CryptoError(AUTHORITY.SLOT_REVOKED)` is thrown
|
||||
|
||||
#### Scenario: dispose zeroises secrets
|
||||
|
||||
- **WHEN** `authority.dispose()` is called
|
||||
- **THEN** every slot's `secretKey` byte buffer is overwritten with zeros and subsequent sign calls throw `AUTHORITY.DISPOSED`
|
||||
|
||||
**Verified by:** `src/authority/in-memory-authority/in-memory-authority.test.ts::"sign-with-in-window-slot succeeds"` AND `::"sign-with-revoked-slot fails"` AND `::"dispose-zeroises-secrets"`
|
||||
|
||||
### Requirement: Hierarchical identity operations
|
||||
|
||||
The package SHALL ship `RootIdentity`, `ServerIdentity`, `createRoot`, `createServer`, `rollServer`, and `revokeServer`. `createServer` produces a server identity signed under the supplied root authority; `rollServer` increments the version, emits a CRL entry for the prior version, and appends an audit-log entry; `revokeServer` appends CRL + audit entries without re-issuing.
|
||||
|
||||
#### Scenario: createRoot self-verifies
|
||||
|
||||
- **WHEN** `createRoot(rng, clock, options)` runs
|
||||
- **THEN** the produced root's public bundle verifies under its own sign slot
|
||||
|
||||
#### Scenario: rollServer increments version and emits CRL
|
||||
|
||||
- **WHEN** `rollServer(rootAuthority, serverIdentity, options)` runs
|
||||
- **THEN** the new server's version is `prior.version + 1`, a CRL entry exists for `prior.version`, and an audit-log entry exists for the rotation
|
||||
|
||||
**Verified by:** `src/identity/operations/operations.test.ts::"createRoot self-verifies"` AND `::"createServer is signed by root"` AND `::"rollServer increments version + emits CRL"` AND `::"revokeServer appends without re-issue"`
|
||||
|
||||
### Requirement: Three-regime `Vault` replaces the legacy implementation
|
||||
|
||||
The package SHALL ship a three-regime `Vault`: (a) public bundle (parent-signed plaintext), (b) private-key blob (password-AEAD, rotatable via `changePassword` without re-derivation), (c) vault payload (auth-box-sealed under keypair, parent-signed under `DOMAIN_VAULT_V1`, carries `master`/`seedCount`/`keychain`/`data: Uint8Array`). The transitional `legacy-vault/` from slice 2 MUST be deleted in this slice.
|
||||
|
||||
#### Scenario: round-trip preserves payload data
|
||||
|
||||
- **WHEN** a `Vault` packs a payload containing `data: Uint8Array([1,2,3])` and is then unpacked
|
||||
- **THEN** `data` round-trips byte-identically
|
||||
|
||||
#### Scenario: changePassword does not re-derive the keychain
|
||||
|
||||
- **WHEN** a `Vault` is unlocked, `changePassword(newPassword)` is called, and the vault is re-locked then re-unlocked with `newPassword`
|
||||
- **THEN** the recovered keychain's slots are byte-identical to the original (no re-derivation occurred)
|
||||
|
||||
#### Scenario: lock zeroises secrets
|
||||
|
||||
- **WHEN** `vault.lock()` is called
|
||||
- **THEN** every secret byte buffer reachable from the vault is overwritten with zeros
|
||||
|
||||
**Verified by:** `src/vault/vault.test.ts::"round-trip preserves payload data"` AND `::"changePassword without re-derive"` AND `::"lock zeroises secrets"` AND `::"BIP39 restore re-derives identical keychain"` AND `::"Argon2id parameter floor enforced"`
|
||||
|
||||
### Requirement: `Packer` / `Unpacker` envelope with replay protection
|
||||
|
||||
The package SHALL ship `Packer.pack(value, schema, recipientAuthority, senderAuthority, domainTag)` producing an encrypt-then-sign envelope including `issuedAt` and `nonce`. `Unpacker.unpack(envelope, recipientAuthority, senderPublicBundle, domainTag, options)` MUST reject envelopes whose `issuedAt` is older than `options.replayWindowMs` or whose `nonce` was already seen.
|
||||
|
||||
#### Scenario: round-trip preserves the value
|
||||
|
||||
- **WHEN** Packer.pack then Unpacker.unpack with matching parties and domain tag
|
||||
- **THEN** the unpacked value is structurally equal to the original
|
||||
|
||||
#### Scenario: replay outside window rejected
|
||||
|
||||
- **WHEN** an envelope produced 1 hour ago is unpacked with `replayWindowMs = 60_000`
|
||||
- **THEN** `CryptoError(PACKER.REPLAY_REJECTED)` is thrown
|
||||
|
||||
#### Scenario: cross-protocol domain tag rejected
|
||||
|
||||
- **WHEN** an envelope packed under `DOMAIN_PACKER_V1` is unpacked with `DOMAIN_VAULT_V1`
|
||||
- **THEN** `CryptoError(PACKER.DOMAIN_TAG_MISMATCH)` is thrown
|
||||
|
||||
**Verified by:** `src/packer/packer.test.ts::"round-trip preserves the value"` AND `::"replay outside window rejected"` AND `::"cross-protocol domain tag rejected"`
|
||||
|
||||
### Requirement: `ChunkedPacker` per-chunk-signed fragmentation
|
||||
|
||||
The package SHALL ship `ChunkedPacker.packFileToChunks` and `ChunkedUnpacker.reassembleFile`. Each chunk MUST carry an independent signature; tampering with any single chunk's bytes MUST cause reassembly to fail.
|
||||
|
||||
#### Scenario: round-trip reassembles byte-identical
|
||||
|
||||
- **WHEN** a 10 KiB stream is packed into 4 chunks then reassembled
|
||||
- **THEN** the reassembled stream is byte-identical to the input
|
||||
|
||||
#### Scenario: tampered chunk rejected at reassembly
|
||||
|
||||
- **WHEN** any byte of any single chunk is flipped before reassembly
|
||||
- **THEN** `CryptoError(CHUNK.TAMPERED)` is thrown with the offending chunk index
|
||||
|
||||
**Verified by:** `src/chunked-packer/chunked-packer.test.ts::"round-trip reassembles byte-identical"` AND `::"tampered chunk rejected at reassembly"`
|
||||
|
||||
### Requirement: `RevocationList` root-signed CRL
|
||||
|
||||
The package SHALL ship a `RevocationList` that is append-only, root-signed, and `verify`-able against a root public bundle. Mutating any past entry MUST cause the list-level signature to fail verification.
|
||||
|
||||
#### Scenario: append then verify succeeds
|
||||
|
||||
- **WHEN** a CRL is appended to and then verified against the issuing root's public bundle
|
||||
- **THEN** verification succeeds
|
||||
|
||||
#### Scenario: mutating a past entry invalidates the list
|
||||
|
||||
- **WHEN** any byte of an existing CRL entry is altered post-append
|
||||
- **THEN** `CryptoError(CRL.MUTATED)` is thrown on next verify
|
||||
|
||||
**Verified by:** `src/revocation/revocation-list.test.ts::"append then verify succeeds"` AND `::"mutating a past entry invalidates the list"`
|
||||
|
||||
### Requirement: `AuditLog` append-only Merkle log
|
||||
|
||||
The package SHALL ship an `AuditLog` whose root is signed by a root authority. Tampering with any past entry MUST invalidate the root signature; an inclusion proof MUST be derivable for any appended entry.
|
||||
|
||||
#### Scenario: append then root signature verifies
|
||||
|
||||
- **WHEN** events are appended and the log root is signed
|
||||
- **THEN** `auditLog.verify(rootPublicBundle)` returns true
|
||||
|
||||
#### Scenario: tampering with a past entry invalidates the root
|
||||
|
||||
- **WHEN** any byte of an appended event is flipped
|
||||
- **THEN** `auditLog.verify(rootPublicBundle)` throws `CryptoError(AUDIT.ROOT_MISMATCH)`
|
||||
|
||||
**Verified by:** `src/audit-log/audit-log.test.ts::"append then root signature verifies"` AND `::"tampering with a past entry invalidates the root"` AND `::"inclusion proof verifies for appended entries"`
|
||||
|
||||
### Requirement: Eight wire-format schemas with golden-bytes KAT vectors
|
||||
|
||||
The package SHALL define and module-export eight wire-format schemas (`DERIVATION_COORDINATE_SCHEMA`, `KEY_SLOT_PRIVATE_SCHEMA`, `KEY_SLOT_PUBLIC_SCHEMA`, `VAULT_PAYLOAD_SCHEMA`, `PUBLIC_BUNDLE_SCHEMA`, `CHAIN_BUNDLE_SCHEMA`, `CRL_ENTRY_SCHEMA`, `AUDIT_ENTRY_SCHEMA`) and ship a golden-bytes KAT fixture per schema under `test-fixtures/wire-format/`. CI MUST assert byte-for-byte equality between the schema's encoding of canonical inputs and the committed fixtures.
|
||||
|
||||
#### Scenario: every schema is module-exported
|
||||
|
||||
- **WHEN** the test imports every named schema from `@suaveplan/crypto/wire-format`
|
||||
- **THEN** every import resolves
|
||||
|
||||
#### Scenario: KAT bytes match committed fixtures
|
||||
|
||||
- **WHEN** each schema encodes its canonical input
|
||||
- **THEN** the bytes are equal to the matching `test-fixtures/wire-format/*.bin`
|
||||
|
||||
**Verified by:** `tests/wire-format-kat.test.ts::"every wire-format schema is module-exported"` AND `tests/wire-format-kat.test.ts::"KAT bytes match committed fixtures for each schema"`
|
||||
|
||||
### Requirement: Property-based round-trip coverage
|
||||
|
||||
Every pack/unpack pair in the package SHALL be covered by a `fast-check` property test asserting `unpack(pack(v)) ≡ v` over at least 100 generated inputs per property.
|
||||
|
||||
#### Scenario: round-trip is identity for every pack/unpack pair
|
||||
|
||||
- **WHEN** a fast-check property runs over 100 generated inputs for any pack/unpack pair
|
||||
- **THEN** every iteration round-trips structurally
|
||||
|
||||
**Verified by:** `tests/property-roundtrips.test.ts::"round-trip identity for keychain"` AND `::"round-trip identity for vault"` AND `::"round-trip identity for public-bundle"` AND `::"round-trip identity for chain-bundle"` AND `::"round-trip identity for packer-envelope"` AND `::"round-trip identity for chunk"` AND `::"round-trip identity for crl-entry"` AND `::"round-trip identity for audit-entry"`
|
||||
@@ -0,0 +1,111 @@
|
||||
# Tasks — restore-crypto-opinionated-layer
|
||||
|
||||
## 1. Authority concrete backend
|
||||
|
||||
- [ ] 1.1 Implement `InMemoryAuthority` against the existing `Authority` interface (sign under `DerivationCoordinate` slot, validity-window check, revoked-slot rejection)
|
||||
- [ ] 1.2 `dispose()` zeroises every slot's secret material
|
||||
- [ ] 1.3 Tests: sign-success, sign-with-revoked-slot-fails, sign-with-out-of-window-key-fails, dispose-zeroises, AbortSignal cancellation
|
||||
- [ ] 1.4 Co-located `.md`
|
||||
|
||||
## 2. Identity hierarchy
|
||||
|
||||
- [ ] 2.1 `RootIdentity` and `ServerIdentity` typed wrappers
|
||||
- [ ] 2.2 `createRoot(rng, clock, options)` — generates root keychain + signs its own public bundle
|
||||
- [ ] 2.3 `createServer(rootAuthority, options)` — generates server keychain, signed under root
|
||||
- [ ] 2.4 `rollServer(rootAuthority, serverIdentity, options)` — version-bump rotation; emits CRL entry for prior version; appends audit-log entry
|
||||
- [ ] 2.5 `revokeServer(rootAuthority, serverRef, reason)` — appends CRL + audit entries; no re-issue
|
||||
- [ ] 2.6 Tests: createRoot self-verifies; createServer parent-signed; rollServer increments version + emits CRL; revokeServer appends without re-issue
|
||||
- [ ] 2.7 Co-located `.md` per file
|
||||
|
||||
## 3. Three-regime Vault
|
||||
|
||||
- [ ] 3.1 `Vault` class with three regimes: public bundle (parent-signed plaintext), private-key blob (password-AEAD), vault payload (auth-box-sealed under keypair, parent-signed under `DOMAIN_VAULT_V1`)
|
||||
- [ ] 3.2 `pack` / `unpack` round-trip
|
||||
- [ ] 3.3 `changePassword` re-encrypts the private-key blob without re-deriving the keychain
|
||||
- [ ] 3.4 `rotateApplicationData` re-seals payload only (keys untouched)
|
||||
- [ ] 3.5 `lock()` zeroises secrets
|
||||
- [ ] 3.6 BIP39 restore re-derives identical keychain
|
||||
- [ ] 3.7 Argon2id parameter floor enforced
|
||||
- [ ] 3.8 Tests: round-trip, tamper-detect, wrong-password-distinct-from-tamper, BIP39-restore, lock-zeroises, Argon2id-floor, changePassword-without-re-derive, payload-data-round-trip, rotateApplicationData-keys-untouched
|
||||
- [ ] 3.9 Delete `vault/legacy-vault/` after the new `Vault` ships and any internal callers migrate
|
||||
- [ ] 3.10 Co-located `.md` (≥ 200 words) covering the three regimes
|
||||
|
||||
## 4. Packer / Unpacker
|
||||
|
||||
- [ ] 4.1 `Packer.pack(value, schema, recipientAuthority, senderAuthority, domainTag)` — encrypt-then-sign envelope
|
||||
- [ ] 4.2 `Unpacker.unpack(envelope, recipientAuthority, senderPublicBundle, domainTag, options)` — verify-then-decrypt
|
||||
- [ ] 4.3 Replay-window check via `issuedAt` + `nonce`; rejects envelopes outside window or with seen nonce
|
||||
- [ ] 4.4 Tests: round-trip, recipient-verifies-sender, tamper-detect, replay-rejection, mismatched-domain-tag-fails
|
||||
- [ ] 4.5 Co-located `.md`
|
||||
|
||||
## 5. ChunkedPacker / ChunkedUnpacker
|
||||
|
||||
- [ ] 5.1 `ChunkedPacker.packFileToChunks(stream, recipient, sender, options)` — per-chunk-signed fragmentation
|
||||
- [ ] 5.2 `ChunkedUnpacker.reassembleFile(chunks, options)` — assemble; rejects on tampered chunk
|
||||
- [ ] 5.3 Tests: 1-chunk, N-chunk, tampered-chunk-rejected, missing-chunk-rejected, out-of-order-chunks-handled
|
||||
- [ ] 5.4 Co-located `.md`
|
||||
|
||||
## 6. RevocationList
|
||||
|
||||
- [ ] 6.1 `RevocationList` with `append(entry, rootAuthority)`, `verify(rootPublicBundle)`, `check(serverRef)`
|
||||
- [ ] 6.2 Append-only invariant (mutating past entries invalidates the list)
|
||||
- [ ] 6.3 Tests: append-then-verify, mutate-past-entry-fails, check-revoked-server-true, check-non-revoked-server-false
|
||||
- [ ] 6.4 Co-located `.md`
|
||||
|
||||
## 7. AuditLog
|
||||
|
||||
- [ ] 7.1 `AuditLog` append-only Merkle log; root signed by `rootAuthority`
|
||||
- [ ] 7.2 `append(event)`, `verify(rootPublicBundle)`, `inclusion(eventId)` proof
|
||||
- [ ] 7.3 Tampering with any past entry invalidates the root signature
|
||||
- [ ] 7.4 Tests: append-roots-correctly, tamper-past-entry-fails, inclusion-proof-verifies
|
||||
- [ ] 7.5 Co-located `.md`
|
||||
|
||||
## 8. Wire-format schemas + KAT fixtures
|
||||
|
||||
- [ ] 8.1 Define `DERIVATION_COORDINATE_SCHEMA`
|
||||
- [ ] 8.2 Define `KEY_SLOT_PRIVATE_SCHEMA`, `KEY_SLOT_PUBLIC_SCHEMA`
|
||||
- [ ] 8.3 Define `VAULT_PAYLOAD_SCHEMA`
|
||||
- [ ] 8.4 Define `PUBLIC_BUNDLE_SCHEMA` (`magic("SVPB") ‖ formatVersion ‖ issuerRef ‖ issuedAt ‖ nonce ‖ subjectRef ‖ slots[] ‖ parentSignature[64]`)
|
||||
- [ ] 8.5 Define `CHAIN_BUNDLE_SCHEMA` (`magic("SVCB") ‖ formatVersion ‖ linkCount ‖ links[]`)
|
||||
- [ ] 8.6 Define `CRL_ENTRY_SCHEMA`, `AUDIT_ENTRY_SCHEMA`
|
||||
- [ ] 8.7 Commit eight golden-bytes fixtures under `test-fixtures/wire-format/`
|
||||
- [ ] 8.8 `tests/wire-format-kat.test.ts` byte-for-byte assertion
|
||||
- [ ] 8.9 Co-located `.md` per schema
|
||||
|
||||
## 9. Property-based round-trip
|
||||
|
||||
- [ ] 9.1 `tests/property-roundtrips.test.ts` using `fast-check`
|
||||
- [ ] 9.2 Cover every pack/unpack pair: keychain, vault, public bundle, chain bundle, packer envelope, chunk, CRL entry, audit entry
|
||||
|
||||
## 10. Error codes
|
||||
|
||||
- [ ] 10.1 Add `PACKER.ENCRYPT_FAILED`, `PACKER.SIGN_FAILED`, `PACKER.REPLAY_REJECTED`, `PACKER.DOMAIN_TAG_MISMATCH`
|
||||
- [ ] 10.2 Add `CHUNK.TAMPERED`, `CHUNK.MISSING`, `CHUNK.OUT_OF_ORDER`
|
||||
- [ ] 10.3 Add `CRL.NOT_VERIFIED`, `CRL.MUTATED`
|
||||
- [ ] 10.4 Add `AUDIT.ROOT_MISMATCH`, `AUDIT.INCLUSION_PROOF_FAILED`
|
||||
- [ ] 10.5 Add `VAULT.PAYLOAD_DECRYPT_FAILED`, `VAULT.PAYLOAD_VERIFY_FAILED`
|
||||
- [ ] 10.6 Add `AUTHORITY.SLOT_REVOKED`, `AUTHORITY.SLOT_OUT_OF_WINDOW`
|
||||
- [ ] 10.7 Each code thrown by ≥ 1 production code path (asserted by `tests/codes-thrown.test.ts`)
|
||||
|
||||
## 11. Verification gates
|
||||
|
||||
- [ ] 11.1 `bun run check` — Biome 0/0/0
|
||||
- [ ] 11.2 `bunx turbo lint typecheck test --filter=@suaveplan/crypto` green
|
||||
- [ ] 11.3 `bunx turbo test:coverage --filter=@suaveplan/crypto` 100/100/100/100
|
||||
- [ ] 11.4 `bunx turbo test --filter=...@suaveplan/crypto` (dependent sweep) green
|
||||
- [ ] 11.5 OpenSpec conformance script clean
|
||||
|
||||
## 12. Pre-archive 7-row checklist (AGENTS.md Section 13)
|
||||
|
||||
- [ ] 12.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim
|
||||
- [ ] 12.2 Zod schemas wired at every declared boundary
|
||||
- [ ] 12.3 Every error code thrown by at least one production path
|
||||
- [ ] 12.4 Strict-mode behaviours tested both on and off
|
||||
- [ ] 12.5 OTel instruments emitted from production paths and asserted with a real `MeterProvider`
|
||||
- [ ] 12.6 Lifecycle dedup tested where claimed (replay-window nonces, CRL append-only)
|
||||
- [ ] 12.7 No retroactive tick
|
||||
|
||||
## 13. Archive
|
||||
|
||||
- [ ] 13.1 Fold spec deltas into `openspec/specs/crypto/spec.md`
|
||||
- [ ] 13.2 Move `openspec/changes/restore-crypto-opinionated-layer/` → `openspec/archive/<yyyy-mm-dd>-restore-crypto-opinionated-layer/`
|
||||
Reference in New Issue
Block a user