Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
# Add @suaveplan/server-keys — full-parity port of the 18-bin CLI
**Package**: `@suaveplan/server-keys`
**Status**: PROPOSED
**Type**: migration (full parity with main) + enterprise stack additions
- **Spec delta**: ./specs/server-keys/spec.md
- **Target path**: `packages/security/server-keys/`
## Why
Operator-facing CLI bundle for the Suaveplan server-key lifecycle: root + server keychain generation (with hierarchical `ChainedKeySigner` chains via `@suaveplan/crypto-keys`), encrypted vault persistence, key rotation, vault/key viewing, signed file packet preparation/serving/validation, and a license-key issuance bin (uses the just-shipped `@suaveplan/license`). Upstream at `../suaveplan/packages/server/server-keys/` ships 18 bin entry points + 3 command modules + a shared prompt utility (3,122 LOC total). This migration ports all 18 bins verbatim at the CLI-surface level so operator scripts that invoke `create-root-keys`, `roll-server-keys-chain`, `prepare-file`, etc. continue to work byte-compatibly.
Genesis enterprise stack layered on top: `SuaveplanError`-typed errors with a `SERVER_KEYS.*` code registry, Zod boundary validation for CLI options, OpenTelemetry spans on the long-running operations (file prep/serve/validate, keychain ops), sibling `.md` docs ≥200 words per implementation file, fast-check property tests where pure-data functions exist (packet manifests, key-chain encode/decode), and 100/100/100/100 coverage. Upstream uses `@suaveplan/keys` (the old umbrella package); genesis-side calls are re-pointed to `@suaveplan/crypto` + `@suaveplan/crypto-keys` (the post-split surface) without changing the wire formats.
## What changes
Net-new package at `0.1.0`, ships under `packages/security/server-keys/`. ADDED:
### 18 bin entry points (verbatim CLI surface)
- **Key + chain bins** (`@suaveplan/crypto-keys` chain signer): `create-root-keys`, `create-root-keys-chain`, `create-server-keys`, `create-server-keys-chain`, `roll-server-keys`, `roll-server-keys-chain`, `verify-key-chain`
- **Viewer bins**: `view-keys`, `view-vault`, `view-public-key-file`, `view-chain-public`
- **File transport bins** (`@suaveplan/packer` + `@suaveplan/storage`): `prepare-file`, `serve-file`, `validate-file`, `get-file-stats`, `validate-packet`
- **License bin** (`@suaveplan/license`): `license` — issues / validates / inspects software-license keys
- **Top-level dispatcher**: `suaveplan` (single `commander` entry routing to all 17 sub-commands)
### Internal command modules (the library surface the bins call into)
- `commands/keys/keys.ts` — `createRootKeysCommand`, `createServerKeysCommand`, `rollServerKeysCommand`, `createRootKeysChainCommand`, `createServerKeysChainCommand`, `rollServerKeysChainCommand`, `verifyKeyChainCommand`, viewers
- `commands/files/files.ts` — `prepareFileCommand`, `validateFileCommand`, `getFileStatsCommand`, `validatePacketCommand`
- `commands/files/serve-file.ts` — `serveFileCommand`
- `commands/license/license.ts` — `licenseCommand` (wraps `@suaveplan/license` `LicenseManager`)
- `prompt/prompt.ts` — `promptForPassword`, `promptForNewPassword` (`@inquirer/prompts`)
- `run-function.ts` — top-level error-handling wrapper for `.action(...)` callbacks
### Enterprise stack (additive over upstream)
- `error-codes.ts` — `SERVER_KEYS_*` code registry via `defineCodes("SERVER_KEYS", [...])`, `ServerKeysError extends SuaveplanError<ServerKeysErrorCode>`. Codes cover vault decrypt failures, password mismatch, missing root vault, chain verification failure, packet integrity failure, file IO failures, unknown command, plus license-bin pass-throughs.
- Zod boundary schemas for every CLI option bag.
- OpenTelemetry: counter `server_keys.command_total{command}`, histogram `server_keys.command_duration_ms{command}`, span per command.
- `LoggerLike` from `@suaveplan/types` injectable through every command (defaults to console).
- `AbortSignal` plumbing for long-running file ops.
### Bin registration in `package.json`
The full 18-bin `bin` map matches upstream verbatim so `bun link` / `npm install -g` exposes every operator command. Names:
```text
suaveplan, license, create-root-keys, create-server-keys, roll-server-keys,
create-root-keys-chain, create-server-keys-chain, roll-server-keys-chain,
verify-key-chain, view-keys, view-vault, view-public-key-file,
view-chain-public, prepare-file, serve-file, validate-file, get-file-stats,
validate-packet
```
### Subpath exports
Library subpaths so downstream packages can call the command functions directly (e.g. test harnesses, integration tests): `./commands/keys/keys`, `./commands/files/files`, `./commands/files/serve-file`, `./commands/license/license`, `./prompt/prompt`, `./error-codes`, `./run-function`.
## Out of scope
- **Daemon / always-on serve-file mode.** Upstream's `serve-file` is one-shot reconstruction; genesis preserves that. A long-running HTTP serve mode belongs in a separate `@suaveplan/server-keys-http` follow-up.
- **Re-implementing the upstream wire formats.** The keychain encoding, packet header, and license payload formats all come from their respective packages (`@suaveplan/crypto-keys`, `@suaveplan/packer`, `@suaveplan/license`). server-keys is the CLI wrapper, not the format owner.
- **Cross-platform binary distribution.** Bins run under Bun/Node; we do not ship pkg/nexe-style standalone binaries.
- **Telemetry exporter wiring.** OTel instruments are registered against `@opentelemetry/api`; concrete exporter (OTLP/Prometheus/Jaeger) is the operator's choice.
## Source mapping
| Capability | Upstream source | Notes |
|---|---|---|
| 18 bin entry points + commander routing | `../suaveplan/packages/server/server-keys/src/bin/*.ts` + `bin/index.ts` | verbatim CLI surface |
| Key + chain commands (628 LOC) | `src/commands/keys/keys.ts` | re-pointed from `@suaveplan/keys` → `@suaveplan/crypto-keys` `ChainedKeySigner` |
| File transport commands (311 LOC) | `src/commands/files/files.ts` + `serve-file.ts` | uses `@suaveplan/packer` + `@suaveplan/storage`/`storage-file` |
| Password prompts | `src/prompt/prompt.ts` | `@inquirer/prompts` |
| License bin | `src/bin/license.ts` + `src/commands/license/license.types.ts` | wraps `@suaveplan/license` `LicenseManager` |
| `runFunction` error wrapper | `src/runFunction.ts` | verbatim |
| Enterprise audit (Zod, OTel, structured errors, 100% coverage, sibling .md) | `../suaveplan/enterprise/server-keys.md` | all addressed |
| `ServerKeysError` + `SERVER_KEYS_*` code registry | genesis-original | genesis policy — upstream throws plain `Error` |
## Impact
### Affected dependents
None today — net-new on genesis. First downstream will be operator scripts (out-of-repo) and CI workflows that issue/rotate server keys. The `license` bin transitively pulls in `@suaveplan/license` (shipped today).

View File

@@ -0,0 +1,200 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: server-keys (enterprise-server-keys)
**Package**: `@suaveplan/server-keys`
**Status**: PROPOSED
## ADDED Requirements
### Requirement: Top-level commander dispatcher with 17 sub-commands
The package SHALL ship a `suaveplan` bin that uses `commander` to route to all 17 sub-commands (create/roll/verify key + chain bins, viewer bins, file transport bins, license bin). Each sub-command SHALL be lazy-imported. Calling `suaveplan` with no arguments SHALL print help and exit 0.
#### Scenario: every sub-command is reachable from the dispatcher
- **WHEN** `suaveplan <subcommand> --help` is invoked for each of the 17 sub-commands
- **THEN** each prints its own help block and exits 0
**Verified by:** `src/bin/index.test.ts` (`describe("suaveplan dispatcher")`)
---
### Requirement: Root-key creation bin produces encrypted vault + signed public-key file
`create-root-keys` SHALL accept `--output <dir>` (default `./keys`), prompt for a new password (confirm prompt), generate a root keychain via `@suaveplan/crypto`, encrypt it into a vault file via `@suaveplan/crypto-vault`, and write a signed public-key file alongside. Failures SHALL exit with code 1 and a `ServerKeysError` message.
#### Scenario: produces vault + public-key file at the configured output path
- **WHEN** `createRootKeysCommand(password, { output })` is called
- **THEN** `output/root.vault` and `output/root.pub` exist, vault is decryptable with `password`, public-key file is signature-verified by the embedded chain
**Verified by:** `src/commands/keys/keys.test.ts::"createRootKeysCommand writes encrypted vault and signed public-key file"`
---
### Requirement: Server-key derivation bin from root vault
`create-server-keys` SHALL accept `--root-vault <path>` (default `./keys/root.vault`) + `--output <dir>` (default `./keys`), prompt for the root password and a new server password, derive a server keychain from the root master key, encrypt it into `server.vault`, and write a signed `server.pub`.
#### Scenario: derives server keychain from a root vault
- **WHEN** `createServerKeysCommand(rootVaultPath, rootPassword, serverPassword, { output })` is called after a successful `create-root-keys`
- **THEN** `server.vault` is created, decryptable with `serverPassword`, and its public key is signed by the root keychain
**Verified by:** `src/commands/keys/keys.test.ts::"createServerKeysCommand derives from root vault and writes server vault + public-key file"`
---
### Requirement: Server-key rotation bin bumps keychain version
`roll-server-keys` SHALL accept `--server-vault <path>` + `--output <dir>`, prompt for both passwords (root + current server), generate a new server keychain at `version = current + 1` deterministically derived from the root master key, write `server.v<N>.vault`, and emit a rotation manifest.
#### Scenario: rotation increments keychain version
- **WHEN** `rollServerKeysCommand(...)` is called with a current `server.v1.vault`
- **THEN** `server.v2.vault` is created, decryptable, and its keychain's `version === 2`
**Verified by:** `src/commands/keys/keys.test.ts::"rollServerKeysCommand bumps keychain version"`
---
### Requirement: Hierarchical chained-key bins (create-root/server, roll-server with chain)
`create-root-keys-chain`, `create-server-keys-chain`, `roll-server-keys-chain`, and `verify-key-chain` SHALL use `@suaveplan/crypto-keys` `ChainedKeySigner` to produce / verify chained public-key bundles. Root chains are self-signed; server chains are root-signed; rolled-server chains are signed by the current root. `verify-key-chain` SHALL accept a chain file + an explicit trusted root and report `valid: boolean` + walked depth + per-level error details.
#### Scenario: end-to-end chain — create root, derive server, verify
- **WHEN** the three create commands run in sequence then `verifyKeyChainCommand(chainFile, trustedRoot)` is invoked
- **THEN** the result has `valid: true` and `depth === 2`
#### Scenario: tampered chain fails verification
- **WHEN** any byte of the chain file is flipped and `verifyKeyChainCommand` is invoked
- **THEN** the result has `valid: false` with a populated `errors` array
**Verified by:** `src/commands/keys/keys.test.ts::"end-to-end chain create + verify"` AND `src/commands/keys/keys.test.ts::"tampered chain fails verifyKeyChainCommand"`
---
### Requirement: Viewer bins decode keystore artifacts without mutating state
`view-keys`, `view-vault`, `view-public-key-file`, `view-chain-public` SHALL be read-only inspectors. `view-keys` prompts for a password and prints decrypted keychain summary; `view-vault` prints vault file metadata without decryption; `view-public-key-file` prints the public-key file's claimed identity and signature status; `view-chain-public` walks a chain file and prints each level's public key + signer + optional verification result.
#### Scenario: view-vault works without password
- **WHEN** `viewVaultCommand(vaultPath)` is called on a valid vault
- **THEN** stdout contains the vault's version, algorithm, and creation timestamp; no decryption is attempted
**Verified by:** `src/commands/keys/keys.test.ts::"viewVaultCommand prints metadata without decryption"` AND `src/commands/keys/keys.test.ts::"viewKeysCommand requires password and prints keychain summary"`
---
### Requirement: File transport bins (prepare / serve / validate / stats / validate-packet)
`prepare-file` SHALL accept `<input-file> --output <dir> --server-vault <path>`, prompt for the server password, pack the file into signed packets via `@suaveplan/packer`, and write them under `output/`. `serve-file` SHALL read packets from a directory and reconstruct the original file. `validate-file` SHALL walk every packet and verify integrity + signature against the embedded signer public key. `get-file-stats` SHALL print packet count + estimated reconstructed size + manifest summary without verification. `validate-packet` SHALL validate a single packet in isolation. Every command SHALL accept `--signal: AbortSignal` (programmatic API) and SHALL exit cleanly on `SIGINT`.
#### Scenario: prepare then serve reconstructs the input
- **WHEN** `prepareFileCommand(input, opts)` produces packet files and `serveFileCommand(packetDir, output)` then reconstructs
- **THEN** the reconstructed file is byte-identical to `input`
#### Scenario: validate-file detects a tampered packet
- **WHEN** any byte of any packet body is flipped and `validateFileCommand(packetDir)` is invoked
- **THEN** the command reports the tampered packet's path and exits non-zero
**Verified by:** `src/commands/files/files.test.ts::"prepareFile + serveFile round-trip a binary input"` AND `src/commands/files/files.test.ts::"validateFile detects tampered packet"` AND `src/commands/files/files.test.ts::"getFileStats prints manifest without verification"` AND `src/commands/files/files.test.ts::"validatePacket reports a single-packet failure"`
---
### Requirement: License bin wraps @suaveplan/license LicenseManager
`license` SHALL expose sub-commands `generate`, `validate`, `parse`, `revoke` that delegate to `@suaveplan/license` `LicenseManager`. `license generate` accepts a server-vault path + product/tier/expiry/feature flags; signs with the server vault's signing key; prints the resulting license key. `license validate` accepts a key + a public-key file (or vault); prints success/failure + decoded payload. `license parse` decodes without verification. `license revoke` writes the fingerprint to a configured `RevocationStore` (default: file-backed in `./keys/revocations.json`).
#### Scenario: generate then validate round-trip
- **WHEN** `licenseCommand("generate", ...)` produces a key and `licenseCommand("validate", key, ...)` is invoked
- **THEN** the validation prints `success: true` and exits 0
**Verified by:** `src/commands/license/license.test.ts::"license generate + validate round-trip"` AND `src/commands/license/license.test.ts::"license parse decodes without verification"` AND `src/commands/license/license.test.ts::"license revoke marks key as REVOKED on subsequent validate"`
---
### Requirement: Password prompts via @inquirer/prompts, no echo
`promptForPassword(message)` SHALL read a single password from stdin with no echo. `promptForNewPassword(message)` SHALL prompt twice and assert equality; mismatches SHALL raise `ServerKeysError(SERVER_KEYS_PASSWORD_MISMATCH)`. Both SHALL respect a `--no-tty` test mode where the password is read from an `inputStream` injected via `PromptOptions`.
#### Scenario: confirm prompt mismatch raises typed error
- **WHEN** `promptForNewPassword("test", { inputStream: stream(["one", "two"]) })` is awaited
- **THEN** the promise rejects with `ServerKeysError(SERVER_KEYS_PASSWORD_MISMATCH)`
**Verified by:** `src/prompt/prompt.test.ts::"promptForPassword reads stdin with no echo"` AND `src/prompt/prompt.test.ts::"promptForNewPassword rejects mismatched confirmation"`
---
### Requirement: ServerKeysError extends SuaveplanError with SERVER_KEYS_* code registry
The package SHALL define `ServerKeysError extends SuaveplanError<ServerKeysErrorCode>` with codes registered via `defineCodes("SERVER_KEYS", [...])`. Codes SHALL include `SERVER_KEYS_VAULT_NOT_FOUND`, `SERVER_KEYS_VAULT_DECRYPT_FAILED`, `SERVER_KEYS_PASSWORD_MISMATCH`, `SERVER_KEYS_CHAIN_VERIFY_FAILED`, `SERVER_KEYS_PACKET_INTEGRITY_FAILED`, `SERVER_KEYS_FILE_NOT_FOUND`, `SERVER_KEYS_INVALID_INPUT`, `SERVER_KEYS_LICENSE_FAILED`, `SERVER_KEYS_UNKNOWN_COMMAND`, `SERVER_KEYS_ABORTED`. Every registered code SHALL be raised by at least one production call site.
#### Scenario: every SERVER_KEYS_* code is exercised
- **WHEN** the spec-conformance test enumerates `SERVER_KEYS_CODES` and exercises each code's production path
- **THEN** every code appears in the assertions
**Verified by:** `src/server-keys.spec-conformance.test.ts::"every SERVER_KEYS_* code is thrown from a production path"`
---
### Requirement: Zod boundary validation for every CLI option bag
The package SHALL Zod-validate every `commander` action's options bag (output paths, vault paths, signal/abort flags, file paths, license-bin sub-options). Invalid input SHALL raise `ServerKeysError(SERVER_KEYS_INVALID_INPUT)` with the `ZodError` as `cause`. CLI invocation SHALL exit non-zero on validation failure with a one-line human-readable summary printed to stderr.
#### Scenario: invalid --output path rejected
- **WHEN** a command receives `--output` pointing at a non-writable path
- **THEN** the command exits with code 1 and stderr contains `SERVER_KEYS_INVALID_INPUT`
**Verified by:** `src/server-keys.spec-conformance.test.ts::"Zod validation rejects unwritable --output path"`
---
### Requirement: OpenTelemetry counter + histogram per command
The package SHALL register counter `server_keys.command_total{command}` and histogram `server_keys.command_duration_ms{command}` via `@opentelemetry/api`. Every command (`runFunction` wrapper) SHALL increment and record on completion regardless of success/failure (attribute `command.outcome: "success"|"error"`).
#### Scenario: counter and histogram emit per command
- **WHEN** any command runs against a real in-memory `MeterProvider`
- **THEN** both instruments have at least one observation with the correct `command` attribute
**Verified by:** `src/server-keys.spec-conformance.test.ts::"OTel command counter + histogram emit per run"`
---
### Requirement: Universal-tier discipline — sibling docs, subpath exports, no banned patterns
The package SHALL ship a sibling `.md` (≥200 words, Purpose/Features/Usage:Basic/Usage:Advanced/API Reference/Implementation Notes) for every implementation `.ts`. `package.json` SHALL declare subpath exports for every command + prompt module. Feature barrels SHALL use named re-exports only (no `export *`). Test files SHALL import runner primitives from `@suaveplan/testing/runner`. The `src/` SHALL contain no `@deprecated` / `TODO` / `FIXME` / `ts-ignore` / `.skip` / `console.log` / bare `any`.
#### Scenario: package shape is clean
- **WHEN** `tests/dep-graph.test.ts` inspects file tree, package.json, and barrels
- **THEN** every assertion holds
**Verified by:** `tests/dep-graph.test.ts::"every implementation .ts has a sibling .md"` AND `tests/dep-graph.test.ts::"subpath exports cover every command module"` AND `tests/dep-graph.test.ts::"no export * in any feature barrel"`
---
### Requirement: 100% coverage with integration tests against the real filesystem
The package SHALL enforce 100/100/100/100 statements/branches/functions/lines via Istanbul. Integration tests SHALL exercise the full prepare-file → serve-file round-trip against a real temp directory and a real `@suaveplan/storage-file` instance — no mocks of crypto, packer, or storage. Property tests SHALL cover keychain round-trips and packet manifest round-trips via fast-check.
#### Scenario: coverage gate
- **WHEN** `bunx turbo test:coverage --filter=@suaveplan/server-keys` runs
- **THEN** all four thresholds are 100
**Verified by:** `tests/integration/prepare-serve-roundtrip.test.ts::"prepares and reconstructs a binary file against a real temp dir"` AND `tests/property.test.ts::"chain encode/decode round-trips for arbitrary depth"` AND `tests/property.test.ts::"packet manifest round-trips arbitrary payloads"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,70 @@
# Tasks — enterprise-server-keys
## 1. Scaffold
- [ ] 1.1 Create `packages/security/server-keys/{src/{bin,commands/{keys,files,license},prompt,otel},tests/{integration,setup}}/` tree
- [ ] 1.2 `package.json` v0.1.0, `type: module`, `sideEffects: false`, 18 `bin` entries, full subpath `exports` map
- Deps: `@suaveplan/error` `@suaveplan/types` `@suaveplan/crypto` `@suaveplan/crypto-keys` `@suaveplan/crypto-hash` `@suaveplan/crypto-utils` `@suaveplan/crypto-vault` `@suaveplan/license` `@suaveplan/storage` `@suaveplan/storage-file` `@suaveplan/packer` `@suaveplan/bitpacker` `@suaveplan/logger` (all `workspace:^`), `@opentelemetry/api` (`catalog:observability`), `zod` (`catalog:forms`), `tslib` (`catalog:build`), `@inquirer/prompts` (catalog), `commander` (catalog)
- DevDeps: `@suaveplan/testing` `@vitest/coverage-istanbul` `vitest` `fast-check` (`catalog:testing`), `@types/node` `typescript` (`catalog:`), `vite` `vite-plugin-dts` (`catalog:build`)
- [ ] 1.3 `tsconfig.json` + `tsconfig.lib.json` extending `../../../tsconfig.base.json`
- [ ] 1.4 `vite.config.ts` (dts, ES, externals for `@suaveplan/*` + `tslib` + `zod` + `@opentelemetry/api` + `commander` + `@inquirer/*`)
- [ ] 1.5 `vitest.config.ts` (`pool: forks`, `provider: istanbul`, thresholds 100/100/100/100, `setupFiles: ['./tests/setup/polyfills.ts']`, `testTimeout: 30000` for the integration tests)
- [ ] 1.6 `tests/setup/polyfills.ts` (crypto polyfill for node)
- [ ] 1.7 Confirm `@inquirer/prompts` + `commander` exist in root `package.json` catalogs; add to a new `cli` catalog block if not
## 2. Foundation modules
- [ ] 2.1 `src/error-codes.ts` + `.md` + test — 10 `SERVER_KEYS_*` codes + `ServerKeysError`
- [ ] 2.2 `src/otel/instruments.ts` + `.md` + test — counter `server_keys.command_total{command,outcome}`, histogram `server_keys.command_duration_ms{command}`
- [ ] 2.3 `src/run-function.ts` + `.md` + test — top-level `.action(...)` wrapper that catches/exits, increments OTel, AbortSignal-aware
- [ ] 2.4 `src/prompt/prompt.ts` + `.md` + test — `promptForPassword`, `promptForNewPassword` with injectable `inputStream` for tests
## 3. Command modules (the library surface the bins call into)
- [ ] 3.1 `src/commands/keys/keys.ts` + `.md` + `.test.ts` — port upstream `commands/keys/keys.ts` (628 LOC). Re-point `@suaveplan/keys` calls to `@suaveplan/crypto` + `@suaveplan/crypto-keys`. Functions: `createRootKeysCommand`, `createServerKeysCommand`, `rollServerKeysCommand`, `createRootKeysChainCommand`, `createServerKeysChainCommand`, `rollServerKeysChainCommand`, `verifyKeyChainCommand`, `viewKeysCommand`, `viewVaultCommand`, `viewPublicKeyFileCommand`, `viewChainPublicCommand`
- [ ] 3.2 `src/commands/files/files.ts` + `.md` + `.test.ts` — `prepareFileCommand`, `validateFileCommand`, `getFileStatsCommand`, `validatePacketCommand`
- [ ] 3.3 `src/commands/files/serve-file.ts` + `.md` + `.test.ts` — `serveFileCommand`
- [ ] 3.4 `src/commands/license/license.ts` + `.md` + `.test.ts` — `licenseCommand` with `generate`/`validate`/`parse`/`revoke` sub-commands wrapping `@suaveplan/license` `LicenseManager`
## 4. Bin entry points (one .ts per bin; thin wrappers calling commands)
- [ ] 4.1 `src/bin/index.ts` — `suaveplan` dispatcher with all 17 sub-commands lazy-imported
- [ ] 4.2 `src/bin/create-root-keys.ts`, `create-root-keys-chain.ts`, `create-server-keys.ts`, `create-server-keys-chain.ts`, `roll-server-keys.ts`, `roll-server-keys-chain.ts`, `verify-key-chain.ts`
- [ ] 4.3 `src/bin/view-keys.ts`, `view-vault.ts`, `view-public-key-file.ts`, `view-chain-public.ts`
- [ ] 4.4 `src/bin/prepare-file.ts`, `serve-file.ts`, `validate-file.ts`, `get-file-stats.ts`, `validate-packet.ts`
- [ ] 4.5 `src/bin/license.ts`
- [ ] 4.6 `src/bin/index.test.ts` — asserts every bin parses `--help`
## 5. Tests
- [ ] 5.1 Co-located `*.test.ts` covers every scenario in the spec delta
- [ ] 5.2 `src/server-keys.spec-conformance.test.ts` — pins (a) every `SERVER_KEYS_*` code thrown, (b) Zod boundary wired, (c) OTel instruments fire, (d) `expectTypeOf` on every exported function signature
- [ ] 5.3 `tests/integration/prepare-serve-roundtrip.test.ts` — real temp dir, real `storage-file`, real packer, real crypto — full prepare → serve byte-identical reconstruction
- [ ] 5.4 `tests/integration/end-to-end-chain.test.ts` — real `create-root` → `create-server` → `roll-server` → `verify-chain` happy path + tamper-detection
- [ ] 5.5 `tests/integration/license-bin.test.ts` — real `generate` → `validate` → `revoke` against a real server vault
- [ ] 5.6 `tests/property.test.ts` — fast-check chain round-trip + packet manifest round-trip
- [ ] 5.7 `tests/dep-graph.test.ts` — subpath exports, sibling `.md`, no `export *`, dep allowlist
- [ ] 5.8 Every `it(...)` has `{ timeout: 5000 }` (or 30000 for integration tests)
- [ ] 5.9 All tests import runner from `@suaveplan/testing/runner`
## 6. Documentation
- [ ] 6.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
- [ ] 6.2 `README.md` — package description, install, all 18 bins listed under `## Commands`, programmatic-API quick-start, Modules section linking every `.md`
## 7. Gates (run in order; stop on first red)
- [ ] 7.1 `bunx turbo lint typecheck test --filter=@suaveplan/server-keys` — green
- [ ] 7.2 `bunx turbo test:coverage --filter=@suaveplan/server-keys` — 100/100/100/100
- [ ] 7.3 `bun scripts/openspec-conformance.ts @suaveplan/server-keys` — exit 0
- [ ] 7.4 `bunx turbo build --filter=@suaveplan/server-keys` — green
- [ ] 7.5 Banned-pattern scan (0 hits): `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only\|\bany\b\|console\.log\|biome-ignore" packages/security/server-keys/src/ --include="*.ts"` (false-positive `any` in JSDoc prose is OK)
- [ ] 7.6 No `export *` in feature barrels (grep gate)
- [ ] 7.7 No direct `vitest`/`bun:test` imports (grep gate)
## 8. Archive
- [ ] 8.1 Fold `specs/server-keys/spec.md` → `openspec/specs/server-keys/spec.md`
- [ ] 8.2 Move `openspec/changes/enterprise-server-keys/` → `openspec/archive/2026-05-17-enterprise-server-keys/`
- [ ] 8.3 Refresh MANIFEST (`bun scripts/folder-reorg/generate-manifest.ts`)
- [ ] 8.4 Update root `STATUS.md` — `@suaveplan/server-keys` `–` → `✅`