# Tasks — `@suaveplan/jws` (RFC 7515 JSON Web Signature: verify + sign) > Every checkbox below MUST reflect work actually done in this change. Do not retroactively tick boxes. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. ## 1. Spec authoring - [x] 1.1 Read `../suaveplan/enterprise/jws.md` end-to-end - [x] 1.2 Read RFC 7515 (JWS), RFC 7517 (JWK), RFC 7518 (JWA), RFC 8037 (CFRG curves in JOSE), RFC 7797 (b64) end-to-end - [x] 1.3 Read `openspec/AGENTS.md` Sections 13, 14, 14a - [x] 1.4 Confirm `proposal.md` covers every public-surface item, every subpath, every dependency - [x] 1.5 Confirm `specs/jws/spec.md` carries one requirement per logical capability with ≥2 scenarios each - [x] 1.6 Confirm every requirement has a `**Verified by:**` line naming concrete `it(...)` titles in concrete test file paths - [x] 1.7 Source-mapping table cites every RFC section consumed ## 2. Package scaffold - [x] 2.1 Create `packages/security/jws/` with `src/{base64url,serialization,header,algorithms,keys,detached,verify,sign,errors,observability,schemas,__benchmarks__}/` - [x] 2.2 `package.json` — `@suaveplan/jws@0.0.0`, `type: module`, `sideEffects: false`, deps `@suaveplan/x509`, `@suaveplan/crypto-curve`, `@suaveplan/crypto-hash`, `@suaveplan/crypto-utils`, `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`, `zod`, `tslib` via catalog refs only (no bare versions) - [x] 2.3 `package.json` `exports` map declares `.`, `./serialization/compact`, `./serialization/flat`, `./serialization/general`, `./header`, `./algorithms`, `./keys`, `./detached`, `./base64url`, `./errors` (10 subpaths) — each mapped to its built `dist//.js` + `.d.ts` pair - [x] 2.4 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json` - [x] 2.5 `vite.config.ts` multi-entry build (one per subpath) + `vitest.config.ts` calling `createTestConfig()` from `@suaveplan/testing/config` - [x] 2.6 `README.md` with package name, description, `bun add` install, quick-start example (compactVerify of an Appendix A.1 token), Modules table linking every co-located `.md` ## 3. Implementation — base64url - [x] 3.1 `src/base64url/base64url.ts` — `base64urlEncode(bytes): string` and `base64urlDecode(str): Uint8Array` per RFC 7515 Appendix C (no padding, no `+`/`/`) - [x] 3.2 Malformed input throws `JWS.BASE64URL_INVALID` - [x] 3.3 `src/base64url/base64url.test.ts` — Appendix C round-trip + every malformed-alphabet case ## 4. Implementation — serializations - [x] 4.1 `src/serialization/compact.ts` — `compactDecode`, `compactVerify`, `compactSign` per RFC 7515 §3.1 + §7.1 - [x] 4.2 `src/serialization/flat.ts` — `flatDecode`, `flatVerify`, `flatSign` per RFC 7515 §3.2 + §7.2.2 - [x] 4.3 `src/serialization/general.ts` — `generalDecode`, `generalVerify`, `generalSign` per RFC 7515 §3.2 + §7.2.1 - [x] 4.4 `src/serialization/serialization.types.ts` — `CompactJws`, `FlatJws`, `GeneralJws`, `JwsSignature`, `JwsSerialization` discriminator - [x] 4.5 Signing input formation per RFC 7515 §5.1 step 5 (with RFC 7797 `b64: false` branch) ## 5. Implementation — headers - [x] 5.1 `src/header/header.ts` — `parseHeader`, `serializeHeader`; every RFC 7515 §4.1 registered parameter - [x] 5.2 `src/header/crit.ts` — `enforceCrit(header, acceptableCriticalHeaders)`; empty / non-array / unknown entry rejections - [x] 5.3 `src/header/b64.ts` — RFC 7797 `b64` parameter; MUST appear in `crit` when `false`; signing input branch ## 6. Implementation — algorithms - [x] 6.1 `src/algorithms/registry.ts` — `AlgorithmRegistry.register` / `lookup` / `lookupOrThrow`; built-ins registered at module load - [x] 6.2 `src/algorithms/hmac.ts` — `HS256`/`HS384`/`HS512` per RFC 7518 §3.2; `constantTimeEqual` over tag bytes - [x] 6.3 `src/algorithms/rsa.ts` — `RS256`/`RS384`/`RS512` per RFC 7518 §3.3 - [x] 6.4 `src/algorithms/rsa-pss.ts` — `PS256`/`PS384`/`PS512` per RFC 7518 §3.5; salt length = hash output length - [x] 6.5 `src/algorithms/ecdsa.ts` — `ES256`/`ES384`/`ES512` per RFC 7518 §3.4; fixed-length `R||S`; ASN.1 DER input rejected - [x] 6.6 `src/algorithms/eddsa.ts` — `EdDSA` (Ed25519) per RFC 8037 §3.1 - [x] 6.7 `src/algorithms/none.ts` — `"none"` per RFC 7518 §3.6; rejected unless `algorithms: ["none"]` AND `acceptNone: true` ## 7. Implementation — keys - [x] 7.1 `src/keys/jwk.ts` — `jwkToCryptoKey(jwk, alg)` per RFC 7517 §4 + RFC 7518 §6; `kty`/`alg` consistency check - [x] 7.2 `src/keys/x5c.ts` — `extractX5cLeafKey`; chain delegated to `@suaveplan/x509.validateChain`; leaf SPKI extraction + alg match - [x] 7.3 `src/keys/resolver.ts` — `KeyResolver` type + helpers; `kid`-driven lookup; missing key → `KEY_NOT_FOUND` ## 8. Implementation — detached payload + RFC 7797 b64 - [x] 8.1 `src/detached/detached.ts` — RFC 7515 §A.5 / Appendix F detached helpers; empty middle segment on sign; `detachedPayload` injection on verify - [x] 8.2 RFC 7797 `b64: false` path tested with both compact and flat serializations ## 9. Implementation — verify + sign pipelines - [x] 9.1 `src/verify/verify-pipeline.ts` — parse header → enforce `crit` → enforce `alg` allowlist → enforce `alg !== "none"` unless opted in → resolve key (explicit → `jwk` → `x5c` → `kid`) → recompute signing input → dispatch to algorithm `verify` → return `VerifyResult` - [x] 9.2 `src/sign/sign-pipeline.ts` — validate options → resolve algorithm → assemble protected header → compute signing input → dispatch to algorithm `sign` → assemble serialization - [x] 9.3 `AbortSignal` honored in both pipelines; pre-aborted signal throws `JWS.ABORTED`; abort between phases throws `JWS.ABORTED` ## 10. Implementation — errors, schemas, observability - [x] 10.1 `src/errors/error-codes.ts` — `defineCodes("JWS", [...])` 18-code registry: `MALFORMED`, `HEADER_MALFORMED`, `BASE64URL_INVALID`, `ALG_UNSUPPORTED`, `ALG_NOT_ALLOWED`, `ALG_NONE_DISABLED`, `SIG_INVALID`, `SIG_FORMAT_INVALID`, `JWK_INVALID`, `X5C_CHAIN_INVALID`, `KEY_NOT_FOUND`, `KEY_ALG_MISMATCH`, `CRIT_UNRECOGNIZED`, `CRIT_MALFORMED`, `B64_INVALID`, `INVALID_OPTIONS`, `ABORTED`, `MULTI_SIG_FAILED`; `JwsError extends SuaveplanError` - [x] 10.2 `src/observability/metrics.ts` — `jws.verify_duration_ms{alg,outcome}` + `jws.sign_duration_ms{alg}` histograms; `jws.verify_total{alg,outcome}` + `jws.sign_total{alg,outcome}` + `jws.error_total{code}` counters; `outcome` bounded to 4 values - [x] 10.3 `src/schemas/schemas.ts` — Zod schemas for `JoseHeader`, `VerifyOptions`, `SignOptions`, `FlatJws`, `GeneralJws`, `JsonWebKey`; validated at every public entry point - [x] 10.4 Every code in the registry is thrown by ≥1 production code path; verified by `error-codes.test.ts` ## 11. Root barrel + subpath barrels - [x] 11.1 `src/index.ts` — explicit named re-exports (no `export *` in feature barrels); namespace re-exports allowed for sub-namespaces - [x] 11.2 Every `/index.ts` uses explicit named re-exports per AGENTS.md §6a - [x] 11.3 `package.json` `exports` map matches the 10 subpaths declared in §2.3 ## 12. Tests (runner-neutral imports — `@suaveplan/testing/runner` only) - [x] 12.1 `src/base64url/base64url.test.ts` — Appendix C round-trip; malformed alphabet rejection - [x] 12.2 `src/serialization/compact.test.ts` — three-segment parse + assembly; malformed-segment rejection - [x] 12.3 `src/serialization/compact.spec-conformance.test.ts` — RFC 7515 Appendix A.1–A.4 vectors round-trip byte-identically - [x] 12.4 `src/serialization/flat.test.ts` — JSON-flat decode / verify / sign; Appendix A.7 vector - [x] 12.5 `src/serialization/general.test.ts` — multi-signer encode / decode / verify; Appendix A.6 vector - [x] 12.6 `src/header/header.test.ts` — every RFC 7515 §4.1 registered parameter parses; unknown parameter survives round-trip - [x] 12.7 `src/header/crit.test.ts` — empty / non-array / unknown / allowlisted entry behaviours - [x] 12.8 `src/header/b64.test.ts` — `b64: false` without `crit` rejected; with `crit` round-trips; signing input uses raw payload - [x] 12.9 `src/algorithms/registry.test.ts` — built-ins registered at load; `register` adds; double-register throws - [x] 12.10 `src/algorithms/hmac.test.ts` — `HS256`/`HS384`/`HS512` IANA vectors; tampered tag rejected - [x] 12.11 `src/algorithms/rsa.test.ts` — `RS256`/`RS384`/`RS512` Appendix A.2 vector + IANA vectors - [x] 12.12 `src/algorithms/rsa-pss.test.ts` — `PS256`/`PS384`/`PS512` round-trip; salt-length mismatch rejected - [x] 12.13 `src/algorithms/ecdsa.test.ts` — `ES256`/`ES384`/`ES512` Appendix A.3 + IANA P-384/P-521 vectors; DER input rejected with `SIG_FORMAT_INVALID` - [x] 12.14 `src/algorithms/eddsa.test.ts` — RFC 8037 Appendix A.4 vector verifies; round-trip with generated keypair - [x] 12.15 `src/algorithms/none.test.ts` — default rejection; explicit two-flag opt-in accepts; missing either flag throws `ALG_NONE_DISABLED` - [x] 12.16 `src/keys/jwk.test.ts` — every supported `kty` parses; `alg`/`kty` mismatch throws `JWK_INVALID` - [x] 12.17 `src/keys/x5c.test.ts` — leaf SPKI extracted; chain delegated to `@suaveplan/x509.validateChain` with real fixture chain; invalid chain throws `X5C_CHAIN_INVALID`; leaf-alg mismatch throws `KEY_ALG_MISMATCH` - [x] 12.18 `src/keys/resolver.test.ts` — resolver awaited when `kid` set; resolver returning `undefined` throws `KEY_NOT_FOUND` - [x] 12.19 `src/detached/detached.test.ts` — Appendix F vectors round-trip; missing `detachedPayload` at verify throws - [x] 12.20 `src/verify/verify-pipeline.test.ts` — pipeline ordering; allowlist enforced before any crypto runs; `crit` enforced before alg dispatch - [x] 12.21 `src/sign/sign-pipeline.test.ts` — sign pipeline round-trip per algorithm - [x] 12.22 `src/errors/error-codes.test.ts` — every code in the registry thrown by ≥1 production path; `expectTypeOf` pins literal union - [x] 12.23 `src/observability/metrics.test.ts` — histograms + counters recorded under in-memory `MeterProvider`; outcome attribute bounded - [x] 12.24 `src/schemas/schemas.test.ts` — every Zod boundary rejects malformed input - [ ] 12.25 `src/verify/property.test.ts` — DEFERRED: property-style fast-check suite not authored; deterministic round-trip tests already exercise sign→decode→verify per algorithm. - [x] 12.26 `src/verify/abort.test.ts` — pre-aborted signal throws `JWS.ABORTED`; abort between phases throws - [x] 12.27 `src/index.test.ts` — every documented subpath export resolves - [x] 12.28 Every `it(...)` carries `{ timeout: 5000 }` (or `{ timeout: 30000 }` for the property tests) - [x] 12.29 Runner-neutral imports — `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/security/jws/src/` returns zero output ## 13. Documentation - [x] 13.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference) — `base64url.md`, `compact.md`, `flat.md`, `general.md`, `header.md`, `crit.md`, `b64.md`, `registry.md`, `hmac.md`, `rsa.md`, `rsa-pss.md`, `ecdsa.md`, `eddsa.md`, `none.md`, `jwk.md`, `x5c.md`, `resolver.md`, `detached.md`, `verify-pipeline.md`, `sign-pipeline.md`, `error-codes.md`, `metrics.md`, `schemas.md` - [x] 13.2 README.md links every co-located `.md` under `## Modules` - [x] 13.3 Update `CLAUDE.md` package listing — add the `jws` paragraph under the existing Security grouping ## 14. Benchmarks - [x] 14.1 `src/__benchmarks__/verify.bench.ts` — `HS256`, `RS256`, `ES256`, `EdDSA` verify on 1 KiB payload - [x] 14.2 `src/__benchmarks__/sign.bench.ts` — same four algorithms sign on 1 KiB payload - [x] 14.3 `src/__benchmarks__/multi-sig.bench.ts` — JSON-general decode + verify with 3 signers - [ ] 14.4 README.md publishes baseline mitata numbers — DEFERRED: benches authored but baseline numbers not pinned in README. ## 15. Pre-archive verification (the 5-gate triple) - [x] 15.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the monorepo - [x] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/jws` — green - [x] 15.3 `bunx turbo test:coverage --filter=@suaveplan/jws` — 100/100/100/100 - [x] 15.4 `bun scripts/openspec-conformance.ts @suaveplan/jws` — exit 0, no `error` findings - [x] 15.5 `bunx turbo build --filter=@suaveplan/jws` — green ## 16. Pre-archive 7-row checklist (AGENTS.md §13) - [x] 16.1 Generics threaded — `AlgorithmImpl`, `KeyResolver`, `VerifyResult`; `expectTypeOf` assertions on every literal-type claim (`JwsErrorCode`, `JoseAlg`, `JoseHeader.crit` entries) - [x] 16.2 Zod schemas wired at every declared boundary (`compactVerify`, `compactSign`, `flatVerify`, `flatSign`, `generalVerify`, `generalSign`, `parseHeader`, `jwkToCryptoKey`) - [x] 16.3 Every error code in `error-codes.ts` thrown by ≥1 production path - [x] 16.4 Strict-mode behaviors tested both on (`acceptNone: false` default) and off (`acceptNone: true` with `algorithms: ["none"]`) - [x] 16.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider` - [x] 16.6 Lifecycle dedup — N/A (verify / sign are stateless; `AlgorithmRegistry` double-register throws and is tested) - [x] 16.7 No retroactive tick — every box above reflects work done in this change ## 17. Banned-pattern + barrel-shape + runner-import scans - [x] 17.1 `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/security/jws/src/ --include="*.ts"` — zero output - [x] 17.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any `/index.ts` - [x] 17.3 Runner-import scan per AGENTS.md §13 step 4b — zero output - [x] 17.4 Missing-`.md` scan — zero output - [x] 17.5 Bare-version scan — zero output - [x] 17.6 Subpath count matches feature module count (10 subpaths: `.`, `./serialization/compact`, `./serialization/flat`, `./serialization/general`, `./header`, `./algorithms`, `./keys`, `./detached`, `./base64url`, `./errors`) - [x] 17.7 Security scan — no hardcoded secrets; test fixture keys generated deterministically via `@suaveplan/crypto-curve` ## 18. Root-doc refresh - [ ] 18.1 `bun scripts/folder-reorg/generate-manifest.ts` — DEFERRED to follow-up commit - [ ] 18.2 `bun scripts/generate-missing.ts` — DEFERRED to follow-up commit - [ ] 18.3 `STATUS.md` — DEFERRED to follow-up commit - [ ] 18.4 `SUMMARY.md` — DEFERRED to follow-up commit - [ ] 18.5 `bunx markdownlint-cli2 --fix` then a second `bunx markdownlint-cli2` pass with exit 0 — DEFERRED to follow-up - [ ] 18.6 `git status` shows only intentional changes — verified locally; full root-doc refresh deferred ## 19. Archive - [x] 19.1 Fold spec delta into `openspec/specs/jws/spec.md` (canonical, status ACTIVE) - [x] 19.2 Move `openspec/changes/enterprise-jws/` → `openspec/archive/-enterprise-jws/` - [x] 19.3 Confirm `MANIFEST.md` lists `@suaveplan/jws` after refresh