Files
suaveplan/openspec/archive/2026-05-10-enterprise-password-generator/tasks.md
2026-05-27 23:29:53 +01:00

12 KiB

Tasks — @suaveplan/password-generator (cryptographically-strong password and passphrase generator)

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/password-generator.md end-to-end
  • 1.2 Read EFF wordlists deep-dive (https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases) — note exact 7776 / 1296 entry counts and bits-per-word
  • 1.3 Read Lemire 2018 Fast Random Integer Generation in an Interval (https://arxiv.org/abs/1805.10941) §3 ("Nearly Divisionless")
  • 1.4 Read NIST SP 800-63B §5.1.1.2 (memorized-secret-verifiers composition guidance)
  • 1.5 Read openspec/AGENTS.md Sections 13, 14, 14a
  • 1.6 Read CLAUDE.md (full) — type design, file size, doc rules
  • 1.7 Draft proposal.md from openspec/templates/proposal.md
  • 1.8 Fill in ## Source mapping table — every requirement cites a line range OR appears in the "no source line" table with justification
  • 1.9 Draft specs/password-generator/spec.md from openspec/templates/spec-delta.md with all requirements
  • 1.10 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles
  • 1.11 bun openspec change validate enterprise-password-generator clean

2. Package scaffold

  • 2.1 packages/vault/password-generator/ directory with src/{random,charsets,password,passphrase,pin,wordlists,entropy,errors,schemas,observability,__benchmarks__}/
  • 2.2 package.json — @suaveplan/password-generator@0.0.0, type: module, sideEffects: false, deps: @suaveplan/types/@suaveplan/error/@suaveplan/crypto-utils workspace, @opentelemetry/api/tslib/zod via catalog
  • 2.3 tsconfig.lib.json extends @suaveplan/typescript-config/library.json
  • 2.4 vite.config.ts (build) + vitest.config.ts (createTestConfig, provider: 'istanbul', 100/100/100/100)
  • 2.5 README.md with description, install, quick-start, Modules table linking every co-located .md
  • 2.6 Subpath exports in package.json: ., ./password, ./passphrase, ./pin, ./entropy, ./wordlists/eff-long, ./wordlists/eff-short, ./wordlists/registry, ./random, ./errors, ./schemas

3. Implementation — bias-free random

  • 3.1 src/random/random-int.ts — randomInt(rng: Rng, max: number): number, Lemire-style rejection sampling against 2^32 - (2^32 mod n); throws PWGEN.RNG_FAILED if rng returns a non-uint32 after a bounded loop
  • 3.2 src/random/fisher-yates.ts — shuffleInPlace<T>(array: T[], rng: Rng): T[] using randomInt

4. Implementation — char-class password generator

  • 4.1 src/charsets/charsets.ts — LOWERCASE, UPPERCASE, DIGITS, SYMBOLS, AMBIGUOUS, LOOKALIKES ReadonlySet<string> constants; buildEffectiveCharset(opts): string[] applying inclusion + exclusion
  • 4.2 src/password/password.types.ts — GeneratePasswordOptions, PasswordMode, RequiredClasses
  • 4.3 src/password/password.ts — generatePassword(opts, rng?): validate via Zod → reject infeasible (length < requiredCount OR empty effective charset) with PWGEN.OPTIONS_INFEASIBLE → place one char per required class → fill remainder from union → Fisher-Yates shuffle → return string

5. Implementation — pronounceable mode

  • 5.1 src/password/pronounceable.ts — English digram Markov table baked in, CVC syllable generator, generatePronounceablePassword(opts, rng?). Documented as lower-entropy than random mode

6. Implementation — passphrase generator

  • 6.1 src/passphrase/passphrase.types.ts — GeneratePassphraseOptions, CapitalizationMode = "none" | "firstChar" | "randomWord", WordlistRef = "long" | "short" | string[]
  • 6.2 src/passphrase/passphrase.ts — generatePassphrase(opts, rng?): resolve WordlistRef → registry → reject < 256 entries with PWGEN.WORDLIST_TOO_SHORT → randomInt-pick length words → apply capitalisation mode → join with separator → optionally insert addDigits/addSymbols at randomised positions

7. Implementation — PIN mode

  • 7.1 src/pin/pin.ts — generatePin(length, rng?): bias-free uniform [0, 10) over length digits; rejects length < 1 with PWGEN.OPTIONS_INFEASIBLE

8. Implementation — wordlists

  • 8.1 src/wordlists/eff-long.ts — bundled EFF Long Wordlist (7776 entries, ASCII, deduplicated); exported as EFF_LONG_WORDLIST: readonly string[]
  • 8.2 src/wordlists/eff-short.ts — bundled EFF Short Wordlist 1 (1296 entries, ASCII, deduplicated); exported as EFF_SHORT_WORDLIST: readonly string[]
  • 8.3 src/wordlists/registry.ts — registerWordlist(name, words[]) (rejects < 256 with PWGEN.WORDLIST_TOO_SHORT), getWordlist(name) (throws PWGEN.WORDLIST_NOT_REGISTERED for unknown names), listWordlists(): string[]. Pre-populates registry with eff-long and eff-short

9. Implementation — entropy + strength

  • 9.1 src/entropy/entropy.ts — estimateEntropy(password, opts?) detects class membership and returns { bits: log2(charsetSize) * length, charsetSize }; estimatePassphraseEntropy(words, wordlistSize) returns log2(wordlistSize) * words.length; assertStrength(password, minBits, opts?) throws PWGEN.STRENGTH_INSUFFICIENT if bits < minBits

10. Implementation — errors, schemas, observability

  • 10.1 src/errors/codes.ts — defineCodes("PWGEN", [...]) with INVALID_OPTIONS, OPTIONS_INFEASIBLE, WORDLIST_TOO_SHORT, WORDLIST_NOT_REGISTERED, STRENGTH_INSUFFICIENT, RNG_FAILED
  • 10.2 src/schemas/schemas.ts — Zod GeneratePasswordOptionsSchema, GeneratePassphraseOptionsSchema, EntropyOptionsSchema; mismatch → PWGEN.INVALID_OPTIONS
  • 10.3 src/observability/metrics.ts — pwgen.generate_duration_ms{mode} histogram, pwgen.entropy_bits{mode} histogram, pwgen.errors_total{code} counter; wired from every production call site

11. Root barrel + subpath exports

  • 11.1 src/index.ts — explicit named re-exports (no export *)
  • 11.2 Per-feature index.ts barrels — explicit named re-exports
  • 11.3 package.json exports map covers every documented subpath

12. Documentation

  • 12.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference, Implementation Notes covering rejection-sampling rationale + entropy formula derivation)
  • 12.2 README links every co-located .md under ## Modules
  • 12.3 Update CLAUDE.md package listing under #### Vault orchestration (or new sibling section) ONLY at archive time

13. Tests (runner-neutral imports — @suaveplan/testing/runner)

  • 13.1 random/random-int.test.ts — uniform output, rejection-loop hit, monotone bounds
  • 13.2 random/random-int.spec-conformance.test.ts — 1M randomInt(rng, 7776) calls, χ² p > 0.01 vs uniform
  • 13.3 random/fisher-yates.test.ts — permutation correctness, deterministic-seed reproducibility
  • 13.4 charsets/charsets.test.ts — set membership, exclusion arithmetic
  • 13.5 password/password.test.ts — happy path, exclusion sets, each required-class combination, infeasible rejection
  • 13.6 password/password.spec-conformance.test.ts — fast-check property: required-class quotas always satisfied; deterministic-seed reproducibility
  • 13.7 password/pronounceable.test.ts — CVC pattern; RNG injection
  • 13.8 passphrase/passphrase.test.ts — word count, separator, every capitalisation mode, addDigits/addSymbols insertion
  • 13.9 passphrase/passphrase.spec-conformance.test.ts — fast-check property: every word in output is a wordlist member; word count equals requested length
  • 13.10 wordlists/registry.test.ts — register/get/list happy path, < 256 rejection, unknown-name rejection
  • 13.11 wordlists/eff-long.test.ts — length 7776, ASCII-only, no duplicates
  • 13.12 wordlists/eff-short.test.ts — length 1296, ASCII-only, no duplicates
  • 13.13 pin/pin.test.ts — every char in [0-9], length matches
  • 13.14 pin/pin.spec-conformance.test.ts — bias-free distribution check against n = 10
  • 13.15 entropy/entropy.test.ts — class-detection table, log2 math, assertStrength throw and pass paths
  • 13.16 errors/codes.test.ts — every registered code thrown by ≥1 production path
  • 13.17 schemas/schemas.test.ts — every schema rejects malformed input with PWGEN.INVALID_OPTIONS
  • 13.18 observability/metrics.test.ts — every histogram + counter asserted via in-memory MeterProvider
  • 13.19 index.test.ts — every documented subpath resolves
  • 13.20 Every it(...) carries { timeout: 5000 } (or 30000 for the χ² stats test)

14. Benchmarks

  • 14.1 src/__benchmarks__/password.bench.ts — gen_password_16, gen_passphrase_5, gen_passphrase_7, entropy_estimate_long_password; baseline numbers in README

15. Pre-archive verification (5-gate triple)

  • 15.1 bun run check — Biome 0/0/0
  • 15.2 bunx turbo lint typecheck test --filter=@suaveplan/password-generator — green
  • 15.3 bunx turbo test:coverage --filter=@suaveplan/password-generator — 100/100/100/100
  • 15.4 bun scripts/openspec-conformance.ts @suaveplan/password-generator — exit 0, no error findings
  • 15.5 bunx turbo build --filter=@suaveplan/password-generator — green

16. Pre-archive 7-row checklist

  • 16.1 Generics threaded — Rng injectable; shuffleInPlace<T> parametric
  • 16.2 Zod schemas wired at every declared boundary (generatePassword, generatePassphrase, assertStrength)
  • 16.3 Every error code in errors/codes.ts thrown by ≥1 production path (no orphan registry entries — §2c of AGENTS)
  • 16.4 Strict-mode behaviors tested both on and off (Zod strict-object boundary)
  • 16.5 OTel instruments emitted from production paths, asserted via real in-memory MeterProvider
  • 16.6 Lifecycle dedup — N/A (pure functions, no in-flight Promise to dedup)
  • 16.7 No retroactive tick — every box above reflects work done in this change

17. Banned-pattern + structural scans (zero output expected)

  • 17.1 grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only\|\bany\b\|console\.log" packages/vault/password-generator/src/ --include="*.ts"
  • 17.2 grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/vault/password-generator/src/ --include="*.ts"
  • 17.3 grep -rn "Math.random" packages/vault/password-generator/src/ --include="*.ts" — zero (default Rng MUST come from @suaveplan/crypto-utils.randomBytes)
  • 17.4 grep -rn "biome-ignore" packages/vault/password-generator/src/ --include="*.ts" — zero
  • 17.5 Feature barrel export * scan — zero
  • 17.6 File-size budget — every .ts under 300 lines, every .test.ts under 500
  • 17.7 Sibling .md present for every implementation .ts

18. Archive

  • 18.1 Fold spec delta into openspec/specs/password-generator/spec.md (canonical, status ACTIVE)
  • 18.2 Move openspec/changes/enterprise-password-generator/ → openspec/archive/<yyyy-mm-dd>-enterprise-password-generator/
  • 18.3 Refresh MANIFEST.md via bun scripts/folder-reorg/generate-manifest.ts (orchestrator after parallel migrations land)
  • 18.4 Refresh MISSING.yml via bun scripts/generate-missing.ts (orchestrator)
  • 18.5 Update STATUS.md — move row from Pending → Completed (orchestrator)
  • 18.6 Update SUMMARY.md — add per-package paragraph (orchestrator)
  • 18.7 bunx markdownlint-cli2 --fix clean across touched files (orchestrator)