53 lines
4.9 KiB
Markdown
53 lines
4.9 KiB
Markdown
# Add @suaveplan/license — port-main verbatim + enterprise stack
|
|
|
|
**Package**: `@suaveplan/license`
|
|
**Status**: PROPOSED
|
|
**Type**: migration (port-main wire-format verbatim) + enterprise enhancements (additive)
|
|
|
|
- **Spec delta**: ./specs/license/spec.md
|
|
|
|
## Why
|
|
|
|
Software license-key issuance and validation. Ports `../suaveplan/packages/security/license/` verbatim at the wire-format level (72-bit bitpacked payload, secp256k1 signature truncated to 20 bytes by default, Crockford Base32 with signature-derived obfuscation) so genesis-generated keys interoperate byte-for-byte with upstream-generated keys. Layers the genesis enterprise stack on top: `SuaveplanError`-typed errors with a 12-code registry, Zod boundary validation, OpenTelemetry counters/histogram, `AbortSignal`, injectable `Clock`/`Rng`, sibling `.md` docs ≥200 words each, mitata benchmarks, fast-check property tests, dep-graph test, spec-conformance test, 100/100/100/100 coverage. Adds a pluggable `RevocationStore` with an in-memory implementation as an opt-in offline blocklist — does not change the wire format.
|
|
|
|
Unblocks `@suaveplan/server-keys` (in flight; `enterprise-server-server-keys`).
|
|
|
|
## What changes
|
|
|
|
Net-new package at version `0.1.0`. Public surface (every symbol from upstream `index.ts`/`index.types.ts`/`bitpack.ts`/`obfuscation.ts`, plus enterprise additions):
|
|
|
|
- Codec: `base32Encode` / `base32Decode`, `SimpleBitPacker` / `SimpleBitUnpacker`, `packPayload` / `unpackPayload`, `obfuscateLicenseKey` / `deobfuscateLicenseKey` / `extractSignatureFromKey`
|
|
- Time: `getDayNumber`, `dayNumberToDate`, `checkExpiration` — `Clock` injectable
|
|
- Flags: `hasFlag`, `combineFlags`, `hasFeature`, `combineFeatures`, `getEnabledFeatures`
|
|
- Generation: `generateLicense`, `createPayload` (Rng injectable), `parseLicense`
|
|
- Validation: `validateLicense`, `isValidLicense` — full + truncated signature paths
|
|
- Manager: `LicenseManager` class with `addPublicKey` / `setSigningKey` / `setRevocationStore` / `generate` / `generateTrial` / `validate` / `isValid` / `parse` / `revoke` / `isRevoked`
|
|
- Revocation: `RevocationStore` interface, `createMemoryRevocationStore` (LRU), `fingerprintLicense`, `LicenseValidationError.REVOKED` (additive enum variant; the other 6 variants are preserved verbatim)
|
|
- Errors: `LicenseError extends SuaveplanError<LicenseErrorCode>` with 12 codes (`LICENSE.{INVALID_INPUT, INVALID_ENCODING, MALFORMED, PAYLOAD_OUT_OF_RANGE, UNSUPPORTED_VERSION, UNKNOWN_SIGNING_KEY, INVALID_SIGNATURE, EXPIRED, REVOKED, NO_SIGNING_KEY, FEATURE_INDEX_OUT_OF_RANGE, ABORTED}`)
|
|
- Schemas: `LicensePayloadSchema`, `LicenseGenerationOptionsSchema`, `LicenseValidationOptionsSchema`, `QuickLicenseOptionsSchema` (Zod)
|
|
- OTel: counter `license.generate_total`, counter `license.validate_total{license.valid}`, histogram `license.validate_duration_ms`
|
|
- Enums + types preserved verbatim from upstream: `TimeEncodingType`, `LicenseFlags`, `PackageTier`, `LicenseValidationError`, `LicensePayload`, `LicenseDetails`, `LicenseGenerationOptions`, `LicenseGenerationResult`, `LicenseValidationOptions`, `LicenseValidationSuccess`, `LicenseValidationFailure`, `LicenseValidationResult`, `LicenseParsedResult`, `LicenseManagerConfig`, `QuickLicenseOptions`, `KeyRegistry`
|
|
- Constants verbatim from upstream: `LICENSE_VERSION` (=2), `FULL_SIGNATURE_BYTES` (=64), `SIGNATURE_BYTES` (=20), `MIN_SIGNATURE_BYTES` (=16), `BASE32_ALPHABET`, `LICENSE_SCHEMA`, plus genesis-added `PAYLOAD_BYTES` (=9)
|
|
- Subpath exports per feature module
|
|
|
|
## Out of scope
|
|
|
|
- **Network-based revocation (HTTP CRL/OCSP).** Follow-up package; the shipped `RevocationStore` interface accepts async implementations so the follow-up is non-breaking.
|
|
- **Hardware-fingerprint enforcement.** `MACHINE_LOCKED` enum value is preserved (wire format); enforcement is application policy.
|
|
- **License rotation workflows.** Business logic, owned by callers.
|
|
- **Schema-registry integration.** Wire format is self-describing via the 3-bit `version` field.
|
|
|
|
## Source mapping
|
|
|
|
| Capability | Upstream source | Notes |
|
|
|---|---|---|
|
|
| Wire format (72-bit payload, sig modes, Base32, obfuscation) | `../suaveplan/packages/security/license/src/{index,index.types,bitpack,obfuscation}.ts` | verbatim |
|
|
| Enterprise audit requirements (no React/Node deps, Zod, fast-check, deterministic Clock/Rng, OTel, structured errors, AbortSignal, i18n-ready, 100% coverage, 200-word `.md`) | `../suaveplan/enterprise/license.md` L37-L63 | all addressed |
|
|
| `LicenseError` + `LICENSE_*` code registry, OTel instruments, `RevocationStore`, `Clock`/`Rng` injection, `AbortSignal` plumbing, dep-graph + spec-conformance tests, mitata benchmarks | genesis-original | genesis-wide enterprise policy (`CLAUDE.md`, `openspec/AGENTS.md`) — upstream throws plain Errors and has no OTel/Zod/revocation |
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
None today — net-new on genesis. First consumer will be `@suaveplan/server-keys` (separate change `enterprise-server-server-keys`).
|