Initial Commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
## Context
|
||||
|
||||
The `restore-crypto-keychain-packer-umbrella` change was sliced into four sequential children on 2026-05-03 (`restore-crypto-cleanup-duplicates`, `restore-crypto-fold-keys-vault`, `restore-crypto-opinionated-layer`, `restore-crypto-cli-and-allowlist`). Each child shipped its source-code delta. The umbrella itself was archived to `openspec/archive/2026-05-03-restore-crypto-keychain-packer-umbrella/` with its 20-requirement spec under `specs/crypto/spec.md`. The slice plan implicitly assumed each child would carry its share of the umbrella's spec deltas into `openspec/specs/crypto/spec.md`. That did not happen. The current live spec at `openspec/specs/crypto/spec.md` carries primitive-substrate requirements, the `keychain` CLI surface, the streaming-AEAD façade, and password-blob helpers — but the central "every key carries `(masterVersion, slotVersion)`" invariant is absent at the requirement layer. The supporting source code shipped (verified by direct inspection of `packages/crypto/crypto/src/derivation/{coordinate,layouts}/{coordinate,layouts}.ts` and the test files alongside them).
|
||||
|
||||
In parallel, `openspec/specs/crypto-keys/spec.md` exists as a stub: `Purpose: TBD - created by archiving change crypto-completion`. That stub describes a `BaseKeySchema` device-key surface for a `@suaveplan/crypto-keys` package that no longer exists on genesis (`packages/crypto/crypto-keys/` is absent). Live `openspec/specs/crypto/spec.md:204-207` asserts the package must be deleted; the stub contradicts it.
|
||||
|
||||
This is a documentation-debt change. The code is correct and tested. The spec corpus is incomplete and self-contradictory.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Restore the versioned-key vocabulary (`DerivationCoordinate`, `DerivationLayout`, `masterVersion`, `slotVersion`, eight-slot `Keychain`, validity windows, parent-signed bundles, hierarchical identity, revocation, audit, packer, replay protection, trust-anchor) as named requirements in `openspec/specs/crypto/spec.md`.
|
||||
- Every new requirement carries `Verified by:` pointers to existing test files at concrete `path::"test name"` granularity.
|
||||
- Eliminate the contradiction between `openspec/specs/crypto/spec.md` (says crypto-keys is gone) and `openspec/specs/crypto-keys/spec.md` (still describes it) by removing the orphan stub.
|
||||
- After archive, `openspec validate --strict` is green for the live `crypto` capability and `openspec/specs/crypto-keys/` no longer exists.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- No source-code changes. No new tests. No deleted tests. No moved files in `packages/`.
|
||||
- No new requirements that the existing test suite does not already cover. If the umbrella had a requirement (e.g. a specific CRL fetch endpoint) that has no current test, it is **not** folded — it gets a follow-up implementation change.
|
||||
- No reopening of any of the four archived child changes (`restore-crypto-cleanup-duplicates`, `restore-crypto-fold-keys-vault`, `restore-crypto-opinionated-layer`, `restore-crypto-cli-and-allowlist`). Their archives remain authoritative for their respective slices.
|
||||
- No reorganisation of unrelated requirements (primitive substrate, AEAD façade, password blob, allowlist gate). Their text is unchanged.
|
||||
- No spec changes for `bitpacker-crypto`, `crypto-cli`, `crypto-core`, `slsa`. The umbrella touched their specs too; those folds are out of scope here and tracked as separate follow-ups if found necessary.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: spec-only change, zero code touch
|
||||
|
||||
Rationale: the source already implements every requirement to be folded. Any diff outside `openspec/specs/crypto/` and `openspec/specs/crypto-keys/` would expand scope into a re-audit of `restore-crypto-opinionated-layer`'s archive. By constraining the change to spec text plus deletion of the stub, the review surface is bounded and the risk of accidentally re-implementing already-shipped logic is zero.
|
||||
|
||||
Alternative considered: a combined "spec restore + missing-test backfill" change. Rejected — coverage is already at 100% on this package per the gate, so any "missing test" would be a requirement that was never shipped, which makes it a new feature change, not a fold.
|
||||
|
||||
### Decision 2: fold by direct copy, not by reference
|
||||
|
||||
Each folded requirement's text lives in `openspec/specs/crypto/spec.md` as-written. We do **not** add `See archived umbrella` cross-references. Reasons:
|
||||
|
||||
1. Live specs are the contract. A live spec that delegates to an archived spec for its central invariants is dead-on-arrival when the archive is read in a different context.
|
||||
2. `openspec validate` does not traverse archive paths.
|
||||
3. The umbrella contained more than this change is folding (e.g. `Domain-separation tag on every signed payload` cross-cuts requirements that are out of this change's scope). Direct-copy lets us select cleanly.
|
||||
|
||||
### Decision 3: every folded requirement carries `Verified by:`
|
||||
|
||||
`Verified by: <test-file-path>::"<test name>"` is mandatory on every new requirement. The format is the same one already used in newer parts of `openspec/specs/crypto/spec.md` (e.g. lines 311, 367). The validation step in `tasks.md` greps each cited test file for the exact `it(` / `test(` string before the change is allowed to archive.
|
||||
|
||||
Rationale: a folded requirement without a `Verified by:` pointer is indistinguishable from a forward-looking spec, which is exactly the failure mode that produced the current contradictory corpus.
|
||||
|
||||
### Decision 4: requirements are folded at umbrella granularity, not split
|
||||
|
||||
The umbrella named 14 requirements that are missing from the live spec. Each becomes a single requirement here, with the same name. We do not split a single umbrella requirement into multiple smaller ones (e.g. one per scenario). Reasons:
|
||||
|
||||
1. Bigger requirements with multiple `#### Scenario:` blocks are the existing house style in `openspec/specs/crypto/spec.md`.
|
||||
2. Splitting would reformat the umbrella's authorial intent without adding signal.
|
||||
3. A single requirement per umbrella concept makes the diff between umbrella archive and live spec trivially auditable.
|
||||
|
||||
### Decision 5: delete `openspec/specs/crypto-keys/spec.md`, do not rewrite it
|
||||
|
||||
The stub describes `BaseKeySchema` device keys (`{s, v, m, u, d, o}`) for a deleted package. Two alternatives were considered:
|
||||
|
||||
a. Rewrite the stub as a `crypto-keys` capability that documents the post-fold "the package no longer exists" reality. Rejected — non-existent capabilities are noise, and `openspec/specs/crypto/spec.md` already asserts non-existence.
|
||||
|
||||
b. Move the stub's content (the device-key requirements) into `openspec/specs/crypto/spec.md` under a new requirement. Rejected — the underlying device-key derivation API is **not implemented** on genesis (per `archive/2026-05-03-crypto-completion/tasks.md:60`, the device-key suite is deferred to a `crypto-keys-port` follow-up). Folding it now would re-introduce the same problem this change is fixing: a requirement with no `Verified by:` pointer.
|
||||
|
||||
Chosen: delete the stub. If `crypto-keys-port` ever ships, it gets to add its own requirements with its own implementing tests.
|
||||
|
||||
### Decision 6: name the requirements verbatim from the umbrella, drop the unbacked ones
|
||||
|
||||
The 13 requirement headings will read exactly as they did in the umbrella's `specs/crypto/spec.md`:
|
||||
|
||||
1. Eight-slot Keychain with full-width derivation coordinates
|
||||
2. DerivationLayout registry — pluggable u64 layout
|
||||
3. Validity windows enforced at every verify call
|
||||
4. Domain-separation tag on every signed payload
|
||||
5. Public bundles are parent-signed and carry an issuer reference
|
||||
6. Chain bundle traversal — concatenated parent-signed links
|
||||
7. Wire-format schemas exposed and committed
|
||||
8. Authority — signing abstraction over hierarchical identity
|
||||
9. Hierarchical identity — Root and Server
|
||||
10. RevocationList — root-signed CRL
|
||||
11. AuditLog — append-only Merkle log
|
||||
12. Packer — encrypt-and-sign envelope
|
||||
13. ChunkedPacker — per-chunk-signed P2P fragmentation
|
||||
|
||||
The umbrella's `Trust-anchor bootstrap` requirement is **dropped** from this fold. Direct inspection of `packages/crypto/crypto/src/` confirms there is no `trust-anchors/` directory, no `loadTrustAnchor()` / `loadTrustAnchorFromEnv()` symbol, and no `SUAVEPLAN_TRUST_ANCHOR_PATH` consumer. Folding the requirement now would create a spec entry whose `Verified by:` pointer cites a test that does not exist — exactly the failure mode this change exists to fix. Trust-anchor bootstrap needs an implementation change first; this change leaves it explicitly out of scope.
|
||||
|
||||
The umbrella's `Vault` and `Replay protection via issuedAt + nonce` requirements are **not** added either — their content overlaps with the existing live requirement `Opinionated layer modules — Identity, Revocation, Audit, Packer, Vault` (line 209) plus the replay scenarios that ride on the new `Packer` requirement. Folding them as separate requirements would create cross-cutting duplication. The live `Opinionated layer modules` requirement is left intact as a higher-level overview; the 13 new requirements add the missing per-module detail.
|
||||
|
||||
The umbrella's `Wire-format KAT vectors`, `Property-based round-trip coverage`, `OpenTelemetry instrumentation with key-byte redaction`, and `Structured error codes with stable identifiers` requirements are **not** folded either: KAT and property-coverage are implementation gates already enforced by the existing 100%-coverage requirement; OTel redaction is covered by the live "Structured errors never leak secrets" requirement at line 139; structured error codes are covered by the same live requirement. Adding any of them would duplicate.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Risk] A `Verified by:` pointer cites a test that does not actually assert what the requirement says. → Mitigation: the validation step in `tasks.md` greps each cited test file for the exact `it(` / `test(` string and reports any miss as a hard failure before archive. A second mitigation: every requirement's scenario block is hand-copied from the umbrella, where each scenario was originally written to map to a planned test name.
|
||||
|
||||
- [Risk] The umbrella's wording references symbols (`KEYCHAIN.UNKNOWN_LAYOUT`, `TRUST_ANCHOR.MISMATCH`, `DOMAIN_PUBLIC_KEYCHAIN_V1`) that may have been renamed in source after archive. → Mitigation: `tasks.md` includes a grep step that confirms each cited symbol exists in `packages/crypto/crypto/src/`. A miss either reveals a stale-spec bug (which fails the change) or a stale-source bug (which gets a separate fix).
|
||||
|
||||
- [Risk] Folding 14 requirements expands `openspec/specs/crypto/spec.md` substantially, raising the chance of merge conflicts with parallel changes touching `crypto`. → Mitigation: this change is small in absolute terms (~600 lines of spec text) and ships before any other open `crypto` change can land. We do not block on adjacent capabilities.
|
||||
|
||||
- [Risk] Removing `openspec/specs/crypto-keys/spec.md` breaks any external doc/link that references it. → Mitigation: a grep over the genesis tree will be run as part of `tasks.md`. If any consumer references the path, the change blocks until those references are updated. Empirically (per the search done while drafting this proposal), only `archive/2026-05-03-crypto-completion/` references `crypto-keys`, and archives are immutable.
|
||||
|
||||
- [Trade-off] Spec-only changes are cheap to write and risky to review — they look low-stakes but encode contracts. → Counterweight: every requirement has a verifiable test pointer, so reviewers can run `bunx vitest run <test-file> --reporter=verbose` and confirm the cited scenarios pass. The contract is testable, not just readable.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. **Add deltas**: write `specs/crypto/spec.md` (`## ADDED Requirements` section, 14 entries) and `specs/crypto-keys/spec.md` (`## REMOVED Requirements` section listing every requirement currently in `openspec/specs/crypto-keys/spec.md`). The delta files live under `openspec/changes/crypto-spec-restore-versioning/specs/`.
|
||||
2. **Validate**: `bun openspec validate crypto-spec-restore-versioning --strict` passes.
|
||||
3. **Verified-by audit**: for each new requirement, grep its `Verified by:` pointer against the cited test file and assert match. Block on any miss.
|
||||
4. **Symbol audit**: grep each cited symbol (`DERIVATION_LAYOUT_V1`, `DOMAIN_VAULT_V1`, `PUBLIC_BUNDLE_MAGIC`, etc.) against `packages/crypto/crypto/src/` and assert presence.
|
||||
5. **External-reference audit**: grep `crypto-keys` against the live tree (excluding `openspec/archive/`) and assert no consumer breaks.
|
||||
6. **Apply**: fold the deltas into `openspec/specs/crypto/spec.md`; delete `openspec/specs/crypto-keys/`; archive `openspec/changes/crypto-spec-restore-versioning/` to `openspec/archive/<date>-crypto-spec-restore-versioning/`.
|
||||
7. **Post-archive validation**: `bun openspec validate --strict` passes for the entire spec corpus.
|
||||
|
||||
**Rollback strategy**: revert the single archive commit. The change is one commit by design.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `Domain-separation tag on every signed payload` be folded here, or already covered by the existing live "Structured errors never leak secrets" + "Opinionated layer modules" pair? Default per Decision 6: fold it. Reviewer can pull it if the duplication is judged worse than the explicit mention.
|
||||
- Should `Wire-format schemas exposed and committed` reference the KAT-fixture directory `packages/crypto/crypto/test-fixtures/wire-format/` if it exists, or stay symbol-only? Default: stay symbol-only; if a `test-fixtures/` dir is found during the symbol-audit step, add a scenario that asserts byte-stability against it.
|
||||
Reference in New Issue
Block a user