128 lines
8.7 KiB
Markdown
128 lines
8.7 KiB
Markdown
# Tasks — `complete-webauthn-mds3`
|
|
|
|
## 0. Pre-flight (non-negotiable)
|
|
|
|
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
|
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
|
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
|
|
|
## 1. Package scaffolding
|
|
|
|
- [ ] 1.1 Create `packages/auth/webauthn-mds3/` per genesis package layout.
|
|
- [ ] 1.2 `package.json` at `version: 0.0.0`, `sideEffects: false`, `workspace:^` for internal deps, `catalog:*` for external.
|
|
- [ ] 1.3 `tsconfig.json` extending `@suaveplan/typescript-config` universal preset.
|
|
- [ ] 1.4 `vitest.config.ts` via `createTestConfig()` from `@suaveplan/testing/config`; coverage 100/100/100/100; `provider: 'istanbul'`.
|
|
- [ ] 1.5 Subpath exports: `.`, `./fetch`, `./verify`, `./cache`, `./bootstrap`, `./refresher`, `./resolver`, `./status-filter`, `./events`, `./errors`.
|
|
- [ ] 1.6 Root `README.md` with one-paragraph description, install, quick-start, Modules section.
|
|
|
|
## 2. Constants + FIDO root pinning
|
|
|
|
- [ ] 2.1 `src/constants/fido-root.ts` + `.md` — pin FIDO Alliance MDS3 root CA DER bytes + SHA-256 fingerprint; export `FIDO_MDS3_DEFAULT_URL`, `DEFAULT_REFRESH_INTERVAL_MS`, `DEFAULT_DENY_STATUSES`, `JITTER_WINDOW_MS`.
|
|
|
|
## 3. Error codes (≥12; current: 14)
|
|
|
|
- [ ] 3.1 `src/errors/error-codes.ts` + `.md` — `defineCodes("WEBAUTHN_MDS3", [BLOB_FETCH_FAILED, BLOB_NOT_MODIFIED, BLOB_MALFORMED, BLOB_SIGNATURE_INVALID, BLOB_CHAIN_UNTRUSTED, BLOB_EXPIRED, BLOB_ALG_DISALLOWED, BLOB_SCHEMA_MISMATCH, CACHE_READ_FAILED, CACHE_WRITE_FAILED, CACHE_ROLLBACK_REFUSED, RESOLVER_LOOKUP_FAILED, REFRESHER_ALREADY_RUNNING, REFRESHER_ABORTED])`.
|
|
- [ ] 3.2 `src/errors/errors.test.ts` — assert every code is reachable from a production path.
|
|
|
|
## 4. Schemas
|
|
|
|
- [ ] 4.1 `src/schemas/metadata-blob.schemas.ts` + `.md` — `MetadataBlobPayloadSchema`, `MetadataStatementSchema`, `AuthenticatorStatusSchema`, `StatusReportSchema`, `BiometricStatusReportSchema`, `EcdaaTrustAnchorSchema`, `MetadataBlobEntrySchema` tracking FIDO MDS3 §3.1.
|
|
|
|
## 5. Fetch (conditional)
|
|
|
|
- [ ] 5.1 `src/fetch/fetch-blob.ts` + `.md` — `fetchMetadataBlob({ fetch, url?, signal?, cache? })` returning `FetchOutcome`; sends `If-None-Match` / `If-Modified-Since` when validators present; maps `304` → `{ kind: "not-modified" }`; non-2xx (excluding 304) → `BLOB_FETCH_FAILED`.
|
|
- [ ] 5.2 `src/fetch/fetch-blob.test.ts` — happy, 304, non-2xx, abort, conditional headers asserted.
|
|
|
|
## 6. Verify
|
|
|
|
- [ ] 6.1 `src/verify/verify-blob.ts` + `.md` — JWS via `@suaveplan/jws`; alg allow-list `{RS256, ES256, ES384}`; chain to pinned FIDO root via `@suaveplan/x509`; decode + Zod-validate payload; reject expired.
|
|
- [ ] 6.2 `src/verify/verify-blob.test.ts` — happy, signature-flipped, chain-broken, expired, alg-disallowed, schema-mismatch.
|
|
|
|
## 7. Cache (rollback-protected)
|
|
|
|
- [ ] 7.1 `src/cache/metadata-cache.ts` + `.md` — `MetadataCache` over `StorageLike<CachedMetadataEnvelope>`; namespace `webauthn-mds3:blob:v1`; rejects writes whose `payload.no < cached.payload.no` with `CACHE_ROLLBACK_REFUSED`.
|
|
- [ ] 7.2 `src/cache/metadata-cache.test.ts` — round-trip, rollback refusal, read/write failure mapping.
|
|
|
|
## 8. Bootstrap blob
|
|
|
|
- [ ] 8.1 `src/bootstrap/blob.ts` + `.md` — export `BOOTSTRAP_BLOB: string` (base64 JWS) plus a verified `BOOTSTRAP_PAYLOAD: MetadataBlobPayload` derived at module load.
|
|
- [ ] 8.2 `src/bootstrap/blob.test.ts` — `verifyMetadataBlob(BOOTSTRAP_BLOB)` resolves; bootstrap is overridden by first successful refresh.
|
|
|
|
## 9. Refresher
|
|
|
|
- [ ] 9.1 `src/refresher/metadata-refresher.ts` + `.md` — `createMetadataRefresher({ fetch, storage, clock, logger, eventBus?, intervalMs?, signal? })`; reentrancy guard; jittered schedule at `nextUpdate - jitter`; conditional fetch; EventBus + OTel.
|
|
- [ ] 9.2 `src/refresher/metadata-refresher.test.ts` — schedule, fallback intervalMs, reentrancy, abort, 304-skips-verify, verify-fail keeps prior envelope, EventBus + OTel emitted.
|
|
|
|
## 10. Resolver
|
|
|
|
- [ ] 10.1 `src/resolver/types.ts` + `.md` — `MetadataResolver`, `MetadataLookup`.
|
|
- [ ] 10.2 `src/resolver/memory.ts` + `.md` + `.test.ts` — `createMemoryResolver({ payload })`; O(1) primary map by AAGUID; secondary O(1) map by SKI hex; constant-time AAGUID equality.
|
|
- [ ] 10.3 `src/resolver/cached.ts` + `.md` + `.test.ts` — `createCachedResolver({ cache, refresher?, bootstrap? })`; lazy refresh on first lookup; falls back to bootstrap when cache empty AND fetch fails.
|
|
- [ ] 10.4 `src/resolver/manual.ts` + `.md` + `.test.ts` — `createManualResolver({ jws })`; runs `verifyMetadataBlob` then delegates to memory resolver.
|
|
|
|
## 11. Status filter
|
|
|
|
- [ ] 11.1 `src/status-filter/status-filter.ts` + `.md` — `createStatusFilter({ allow?, deny? })`; deny wins; defaults `DEFAULT_DENY_STATUSES`.
|
|
- [ ] 11.2 `src/status-filter/status-filter.test.ts` — allow-only, deny-only, both, default deny set, deny-wins precedence.
|
|
|
|
## 12. Events
|
|
|
|
- [ ] 12.1 `src/events/topics.ts` + `.md` — typed topics `webauthn-mds3.refresh.success`, `webauthn-mds3.refresh.failure`, `webauthn-mds3.lookup.hit`, `webauthn-mds3.lookup.miss` via `@suaveplan/events`.
|
|
- [ ] 12.2 `src/events/topics.test.ts` — every topic fires from its production call site.
|
|
|
|
## 13. Observability
|
|
|
|
- [ ] 13.1 `src/observability/metrics.ts` + `.md` — counters `refresh_total{outcome}`, `lookup_total{outcome}`; histogram `blob_age_ms`. No-op when no exporter.
|
|
- [ ] 13.2 `src/observability/spans.ts` + `.md` — spans `fetch`, `verify`, `refresh`, `lookup` with attribute set + status mapping.
|
|
- [ ] 13.3 `src/observability/{metrics,spans}.test.ts` — every metric and span records on its production path.
|
|
|
|
## 14. Fixtures
|
|
|
|
- [ ] 14.1 `src/__fixtures__/mds3-sample-blob.jwt` — captured FIDO Alliance conformance sample.
|
|
- [ ] 14.2 `src/__fixtures__/mds3-corrupted-blob.jwt` — signature-flipped.
|
|
- [ ] 14.3 `src/__fixtures__/mds3-rolled-back-blob.jwt` — same payload with `no` decremented.
|
|
- [ ] 14.4 `src/__fixtures__/index.ts` — typed exports.
|
|
|
|
## 15. Benchmarks
|
|
|
|
- [ ] 15.1 `src/__benchmarks__/verify-blob.bench.ts` — JWS verify throughput.
|
|
- [ ] 15.2 `src/__benchmarks__/resolve-aaguid.bench.ts` — AAGUID lookup throughput.
|
|
|
|
## 16. Tier-purity + subpath harnesses
|
|
|
|
- [ ] 16.1 `src/dep-graph.test.ts` — zero DOM / Node-only imports across the public surface.
|
|
- [ ] 16.2 `src/index.test.ts` — every documented subpath resolves.
|
|
|
|
## 17. Integration into `@suaveplan/webauthn`
|
|
|
|
- [ ] 17.1 Extend `VerifyAttestationInput` with optional `metadata?: MetadataResolver` and `statusFilter?: StatusFilter` (typed via `import type` — no runtime dep).
|
|
- [ ] 17.2 Extend `AttestationVerifyResult` with optional `metadata?: MetadataLookup`.
|
|
- [ ] 17.3 Wire post-verifier resolution: AAGUID for non-U2F; SKI for `fido-u2f` / all-zero AAGUID; downgrade `trusted=false` when filter rejects status.
|
|
- [ ] 17.4 Update `packages/auth/webauthn/src/verifier/verify-attestation.test.ts` — add six scenarios per the modified delta.
|
|
- [ ] 17.5 Refresh `verify-attestation.md` and the `packed.md` / `fido-u2f.md` / `none.md` anchors to point at `MetadataResolver` as the canonical seam.
|
|
- [ ] 17.6 Add `@suaveplan/webauthn-mds3` to `packages/auth/webauthn/README.md` Modules section.
|
|
|
|
## 18. Pre-publish gate
|
|
|
|
- [ ] 18.1 `bunx turbo lint typecheck test --filter=@suaveplan/webauthn-mds3` green.
|
|
- [ ] 18.2 `bunx turbo test:coverage --filter=@suaveplan/webauthn-mds3` 100/100/100/100.
|
|
- [ ] 18.3 `bunx turbo lint typecheck test --filter=@suaveplan/webauthn` green (dependent sweep).
|
|
- [ ] 18.4 Banned-pattern scan: zero matches (§13 step 4).
|
|
- [ ] 18.5 Barrel-shape scan: zero matches (§13 step 4a).
|
|
- [ ] 18.6 Test-runner import scan: zero matches (§13 step 4b).
|
|
- [ ] 18.7 Missing-`.md` scan: zero misses.
|
|
- [ ] 18.8 `bunx markdownlint-cli2 --fix` then no-fix pass exits 0.
|
|
|
|
## 19. Repo-root manifest refresh (§13b)
|
|
|
|
- [ ] 19.1 `bun scripts/folder-reorg/generate-manifest.ts` → `MANIFEST.md`.
|
|
- [ ] 19.2 `bun scripts/generate-missing.ts` → `MISSING.yml`.
|
|
- [ ] 19.3 `STATUS.md` add `@suaveplan/webauthn-mds3` row.
|
|
- [ ] 19.4 `SUMMARY.md` paragraph added.
|
|
|
|
## 20. Fold + archive
|
|
|
|
- [ ] 20.1 Fold `specs/webauthn-mds3/spec.md` into `openspec/specs/webauthn-mds3/spec.md`.
|
|
- [ ] 20.2 Fold `specs/webauthn/spec.md` `## MODIFIED Requirements` into `openspec/specs/webauthn/spec.md`.
|
|
- [ ] 20.3 Move `openspec/changes/complete-webauthn-mds3/` → `openspec/archive/<yyyy-mm-dd>-complete-webauthn-mds3/`.
|