# Tasks — `@suaveplan/otp` (RFC 4226 / 6238 / 6287 + Key URI + recovery codes) > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. 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 - [ ] 1.1 Read `../suaveplan/enterprise/otp.md` end-to-end - [ ] 1.2 Read `openspec/AGENTS.md` Sections 13, 14, 14a - [ ] 1.3 Read RFC 4226 (HOTP), RFC 6238 (TOTP), RFC 6287 (OCRA), Google Authenticator Key URI Format wiki page - [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.5 Fill in `## Source mapping` table — every requirement cites an RFC clause OR appears in the "no source line" table with justification - [ ] 1.6 Draft `specs/otp/spec.md` from `openspec/templates/spec-delta.md` - [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles ## 2. Package scaffold - [ ] 2.1 `packages/vault/otp/` directory structure (`src/{hotp,totp,uri,base32,ocra,recovery,drift,errors,observability}/`) - [ ] 2.2 `package.json` — `@suaveplan/otp@0.0.0`, `type: "module"`, `sideEffects: false`, deps via `workspace:^` / `catalog:` - [ ] 2.3 `tsconfig.json` extends `@suaveplan/typescript-config/library.json` - [ ] 2.4 `vite.config.ts` for build, `vitest.config.ts` from `@suaveplan/testing/config` - [ ] 2.5 `README.md` with package description, install snippet, quick-start, `## Modules` table linking every co-located `.md` - [ ] 2.6 Subpath exports map covers `.`, `./uri`, `./base32`, `./ocra`, `./errors`, `./observability` ## 3. Implementation — HOTP (RFC 4226) - [ ] 3.1 `src/hotp/hotp.ts` — `hotp(secret, counter, opts?)` with dynamic truncation per §5.3 step 2 - [ ] 3.2 `src/hotp/hotp.types.ts` — `HotpOptions`, `HotpAlgorithm`, `HotpDigits` - [ ] 3.3 `src/hotp/hotp.schema.ts` — `HotpOptionsSchema` Zod boundary - [ ] 3.4 8-byte big-endian counter encoding helper in `src/hotp/encode-counter.ts` - [ ] 3.5 `src/hotp/index.ts` — explicit named re-exports ## 4. Implementation — TOTP (RFC 6238) - [ ] 4.1 `src/totp/totp.ts` — `totp(secret, opts?)` over `T = floor((now − T0) / period)` with injectable `Clock` - [ ] 4.2 `src/totp/verify.ts` — `verify(code, secret, opts?)` with `±window` drift; constant-time loop - [ ] 4.3 `src/totp/verify-many.ts` — `verifyMany(code, secrets[], opts?)` constant-time across the secret list - [ ] 4.4 `src/totp/totp.types.ts` — `TotpOptions`, `TotpVerifyOptions`, `TotpVerifyResult`, `TotpVerifyManyResult` - [ ] 4.5 `src/totp/totp.schema.ts` — `TotpOptionsSchema`, `TotpVerifyOptionsSchema` (`window` ∈ [0,10]) - [ ] 4.6 `src/totp/index.ts` — explicit named re-exports ## 5. Implementation — Key URI Format - [ ] 5.1 `src/uri/parse-uri.ts` — `parseUri(uri)` for `otpauth://totp/...` and `hotp` variant - [ ] 5.2 `src/uri/format-uri.ts` — `formatUri(opts)` round-trip with percent-encoding of label and issuer - [ ] 5.3 `src/uri/uri.types.ts` — `OtpUri` discriminated union (`type: "totp" | "hotp"`) - [ ] 5.4 `src/uri/uri.schema.ts` — `OtpUriSchema` Zod boundary - [ ] 5.5 `src/uri/index.ts` — explicit named re-exports ## 6. Implementation — Base32 (RFC 4648 §6) - [ ] 6.1 `src/base32/base32.ts` — `base32Encode(bytes, opts?)` and `base32Decode(input)`; `padding=false` default to match Google Authenticator - [ ] 6.2 `src/base32/index.ts` — explicit named re-exports ## 7. Implementation — OCRA (RFC 6287) - [ ] 7.1 `src/ocra/ocra.ts` — `ocra(suite, key, params)` per §6 computation - [ ] 7.2 `src/ocra/suite.ts` — `parseOcraSuite(suite)` per §6.1 grammar - [ ] 7.3 `src/ocra/verify-ocra.ts` — `verifyOcra(...)` constant-time - [ ] 7.4 `src/ocra/mutual-auth.ts` — `OcraMutualAuthClient`, `OcraMutualAuthServer` per §7.1 - [ ] 7.5 `src/ocra/ocra.types.ts` — `OcraSuite`, `OcraParams`, `OcraCryptoFunction`, `OcraDataInput` - [ ] 7.6 `src/ocra/ocra.schema.ts` — `OcraParamsSchema` Zod boundary - [ ] 7.7 `src/ocra/index.ts` — explicit named re-exports ## 8. Implementation — Recovery codes - [ ] 8.1 `src/recovery/generate.ts` — `generateRecoveryCodes(count, opts?)` Crockford-base32 grouping, injectable `Rng` - [ ] 8.2 `src/recovery/hash.ts` — `hashRecoveryCode(code, opts?)` Argon2id via `@suaveplan/crypto-hash` - [ ] 8.3 `src/recovery/verify.ts` — `verifyRecoveryCode(input, hashedCodes[])` constant-time over the full list - [ ] 8.4 `src/recovery/recovery.types.ts` — `RecoveryCodeOptions`, `RecoveryVerifyResult` - [ ] 8.5 `src/recovery/index.ts` — explicit named re-exports ## 9. Implementation — Clock-drift detector - [ ] 9.1 `src/drift/detect.ts` — `detectDrift(code, secret, opts?)` searches `±maxSteps` outside the verify window - [ ] 9.2 `src/drift/drift.types.ts` — `DriftDetectOptions`, `DriftResult` - [ ] 9.3 `src/drift/index.ts` — explicit named re-exports ## 10. Implementation — Errors and observability - [ ] 10.1 `src/errors/codes.ts` — `OTP_CODES` registry via `defineCodes("OTP", [...])`; `OtpError extends SuaveplanError` - [ ] 10.2 `src/errors/index.ts` — explicit named re-exports - [ ] 10.3 `src/observability/metrics.ts` — histograms and counters wired against `@opentelemetry/api` - [ ] 10.4 `src/observability/index.ts` — explicit named re-exports ## 11. Root barrel + subpath exports - [ ] 11.1 `src/index.ts` — explicit named re-exports of HOTP, TOTP, verify, verifyMany, recovery, drift, errors, observability surfaces (no `export *`) - [ ] 11.2 Verify exports map count matches feature module count (§13 step 8) ## 12. Documentation - [ ] 12.1 Sibling `.md` for every implementation `.ts` (≥200 words; basic + advanced examples; full API reference; implementation notes) - [ ] 12.2 README links every co-located `.md` under `## Modules` ## 13. Tests (runner-neutral imports — `@suaveplan/testing/runner` only) - [ ] 13.1 `src/hotp/hotp.test.ts` — RFC 4226 Appendix D 10 vectors, SHA-256/512 widening, `bigint` past 2^53, digits 6/7/8 - [ ] 13.2 `src/totp/totp.test.ts` — RFC 6238 Appendix B 6 vectors across SHA-1/256/512 at digits=8 - [ ] 13.3 `src/totp/verify.test.ts` — window 0 / 1 / 10 / >10; `delta` semantics - [ ] 13.4 `src/totp/verify-many.test.ts` — multi-secret happy path + miss path - [ ] 13.5 `src/totp/constant-time.test.ts` — every windowed step and every secret evaluated regardless of early match - [ ] 13.6 `src/uri/parse-uri.test.ts` — every documented parameter; `hotp` variant; malformed paths - [ ] 13.7 `src/uri/format-uri.test.ts` — label / issuer percent-encoding; `:` in label rejected - [ ] 13.8 `src/uri/round-trip.property.test.ts` — fast-check property test - [ ] 13.9 `src/base32/base32.test.ts` — RFC 4648 §6 vectors; padded + unpadded; case-insensitivity; invalid alphabet - [ ] 13.10 `src/ocra/ocra.test.ts` — RFC 6287 Appendix C.1–C.3 vectors - [ ] 13.11 `src/ocra/suite.test.ts` — every §6.1 grammar branch - [ ] 13.12 `src/ocra/mutual-auth.test.ts` — three-message §7.1 round-trip - [ ] 13.13 `src/recovery/generate.test.ts` — Crockford-base32 grouping; deterministic with injected `Rng` - [ ] 13.14 `src/recovery/verify.test.ts` — Argon2id round-trip; constant-time over the full set - [ ] 13.15 `src/drift/detect.test.ts` — drift outside the verify window - [ ] 13.16 `src/errors/codes.spec-conformance.test.ts` — every `OTP_CODES` entry thrown by at least one production path - [ ] 13.17 `src/observability/metrics.spec-conformance.test.ts` — every histogram and counter emitted; assertions against an in-memory `MeterProvider` - [ ] 13.18 `src/index.test.ts` — every documented subpath resolves - [ ] 13.19 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async) - [ ] 13.20 Runner-neutral imports — CLAUDE.md §11 grep clean ## 14. Pre-archive verification (the 5-gate triple) - [ ] 14.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo - [ ] 14.2 `bunx turbo lint typecheck test --filter=@suaveplan/otp` — green - [ ] 14.3 `bunx turbo test:coverage --filter=@suaveplan/otp` — 100/100/100/100 - [ ] 14.4 `bun scripts/openspec-conformance.ts @suaveplan/otp` — exit 0, no `error` findings - [ ] 14.5 `bunx turbo build --filter=@suaveplan/otp` — green ## 15. Pre-archive 7-row checklist (AGENTS.md Section 13) - [ ] 15.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim (`HotpAlgorithm`, `HotpDigits`, `OtpUri.type`) - [ ] 15.2 Zod schemas wired at every declared boundary (`HotpOptionsSchema`, `TotpOptionsSchema`, `TotpVerifyOptionsSchema`, `OtpUriSchema`, `OcraParamsSchema`) - [ ] 15.3 Every error code in `OTP_CODES` thrown by at least one production path - [ ] 15.4 Strict-mode behaviors tested both on and off (window cap, base32 padding, label `:` rejection) - [ ] 15.5 OTel histograms and counters emitted from production paths and asserted with a real in-memory `MeterProvider` - [ ] 15.6 Lifecycle dedup — N/A (pure functions; no shared mutable state) - [ ] 15.7 No retroactive tick — every box above reflects work done in this change ## 16. Benchmarks (mitata, `src/__benchmarks__/`) - [ ] 16.1 `totp_generate.bench.ts` - [ ] 16.2 `totp_verify_window_1.bench.ts` - [ ] 16.3 `totp_verify_window_10.bench.ts` - [ ] 16.4 `parse_uri.bench.ts` - [ ] 16.5 `format_uri.bench.ts` - [ ] 16.6 Baseline numbers published in README ## 17. Root-doc refresh + markdownlint (AGENTS.md §13b) - [ ] 17.1 `bun scripts/folder-reorg/generate-manifest.ts` — refresh `MANIFEST.md` - [ ] 17.2 `bun scripts/generate-missing.ts` — refresh `MISSING.yml` - [ ] 17.3 `STATUS.md` — move `@suaveplan/otp` from `Pending` to `Completed` - [ ] 17.4 `SUMMARY.md` — add per-package paragraph - [ ] 17.5 `CLAUDE.md` — add package listing under "Vaulted" / vault-orchestration section - [ ] 17.6 `bunx markdownlint-cli2 --fix` — auto-fix; re-stage - [ ] 17.7 `bunx markdownlint-cli2` — second pass exit 0 ## 18. Archive - [ ] 18.1 Fold spec delta into `openspec/specs/otp/spec.md` (canonical, status ACTIVE) - [ ] 18.2 Move `openspec/changes/enterprise-otp/` → `openspec/archive/-enterprise-otp/`