# 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 - [x] 1.1 Read `../suaveplan/enterprise/password-generator.md` end-to-end - [x] 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 - [x] 1.3 Read Lemire 2018 _Fast Random Integer Generation in an Interval_ (https://arxiv.org/abs/1805.10941) §3 ("Nearly Divisionless") - [x] 1.4 Read NIST SP 800-63B §5.1.1.2 (memorized-secret-verifiers composition guidance) - [x] 1.5 Read `openspec/AGENTS.md` Sections 13, 14, 14a - [x] 1.6 Read CLAUDE.md (full) — type design, file size, doc rules - [x] 1.7 Draft `proposal.md` from `openspec/templates/proposal.md` - [x] 1.8 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification - [x] 1.9 Draft `specs/password-generator/spec.md` from `openspec/templates/spec-delta.md` with all requirements - [x] 1.10 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles - [x] 1.11 `bun openspec change validate enterprise-password-generator` clean ## 2. Package scaffold - [x] 2.1 `packages/vault/password-generator/` directory with `src/{random,charsets,password,passphrase,pin,wordlists,entropy,errors,schemas,observability,__benchmarks__}/` - [x] 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 - [x] 2.3 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json` - [x] 2.4 `vite.config.ts` (build) + `vitest.config.ts` (`createTestConfig`, `provider: 'istanbul'`, 100/100/100/100) - [x] 2.5 `README.md` with description, install, quick-start, Modules table linking every co-located `.md` - [x] 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 - [x] 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 - [x] 3.2 `src/random/fisher-yates.ts` — `shuffleInPlace(array: T[], rng: Rng): T[]` using `randomInt` ## 4. Implementation — char-class password generator - [x] 4.1 `src/charsets/charsets.ts` — `LOWERCASE`, `UPPERCASE`, `DIGITS`, `SYMBOLS`, `AMBIGUOUS`, `LOOKALIKES` `ReadonlySet` constants; `buildEffectiveCharset(opts): string[]` applying inclusion + exclusion - [x] 4.2 `src/password/password.types.ts` — `GeneratePasswordOptions`, `PasswordMode`, `RequiredClasses` - [x] 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 - [x] 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 - [x] 6.1 `src/passphrase/passphrase.types.ts` — `GeneratePassphraseOptions`, `CapitalizationMode = "none" | "firstChar" | "randomWord"`, `WordlistRef = "long" | "short" | string[]` - [x] 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 - [x] 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 - [x] 8.1 `src/wordlists/eff-long.ts` — bundled EFF Long Wordlist (7776 entries, ASCII, deduplicated); exported as `EFF_LONG_WORDLIST: readonly string[]` - [x] 8.2 `src/wordlists/eff-short.ts` — bundled EFF Short Wordlist 1 (1296 entries, ASCII, deduplicated); exported as `EFF_SHORT_WORDLIST: readonly string[]` - [x] 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 - [x] 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 - [x] 10.1 `src/errors/codes.ts` — `defineCodes("PWGEN", [...])` with `INVALID_OPTIONS`, `OPTIONS_INFEASIBLE`, `WORDLIST_TOO_SHORT`, `WORDLIST_NOT_REGISTERED`, `STRENGTH_INSUFFICIENT`, `RNG_FAILED` - [x] 10.2 `src/schemas/schemas.ts` — Zod `GeneratePasswordOptionsSchema`, `GeneratePassphraseOptionsSchema`, `EntropyOptionsSchema`; mismatch → `PWGEN.INVALID_OPTIONS` - [x] 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 - [x] 11.1 `src/index.ts` — explicit named re-exports (no `export *`) - [x] 11.2 Per-feature `index.ts` barrels — explicit named re-exports - [x] 11.3 `package.json` exports map covers every documented subpath ## 12. Documentation - [x] 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) - [x] 12.2 README links every co-located `.md` under `## Modules` - [x] 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`) - [x] 13.1 `random/random-int.test.ts` — uniform output, rejection-loop hit, monotone bounds - [x] 13.2 `random/random-int.spec-conformance.test.ts` — 1M `randomInt(rng, 7776)` calls, χ² p > 0.01 vs uniform - [x] 13.3 `random/fisher-yates.test.ts` — permutation correctness, deterministic-seed reproducibility - [x] 13.4 `charsets/charsets.test.ts` — set membership, exclusion arithmetic - [x] 13.5 `password/password.test.ts` — happy path, exclusion sets, each required-class combination, infeasible rejection - [x] 13.6 `password/password.spec-conformance.test.ts` — fast-check property: required-class quotas always satisfied; deterministic-seed reproducibility - [x] 13.7 `password/pronounceable.test.ts` — CVC pattern; RNG injection - [x] 13.8 `passphrase/passphrase.test.ts` — word count, separator, every capitalisation mode, addDigits/addSymbols insertion - [x] 13.9 `passphrase/passphrase.spec-conformance.test.ts` — fast-check property: every word in output is a wordlist member; word count equals requested length - [x] 13.10 `wordlists/registry.test.ts` — register/get/list happy path, `< 256` rejection, unknown-name rejection - [x] 13.11 `wordlists/eff-long.test.ts` — length 7776, ASCII-only, no duplicates - [x] 13.12 `wordlists/eff-short.test.ts` — length 1296, ASCII-only, no duplicates - [x] 13.13 `pin/pin.test.ts` — every char in `[0-9]`, length matches - [x] 13.14 `pin/pin.spec-conformance.test.ts` — bias-free distribution check against `n = 10` - [x] 13.15 `entropy/entropy.test.ts` — class-detection table, log2 math, `assertStrength` throw and pass paths - [x] 13.16 `errors/codes.test.ts` — every registered code thrown by ≥1 production path - [x] 13.17 `schemas/schemas.test.ts` — every schema rejects malformed input with `PWGEN.INVALID_OPTIONS` - [x] 13.18 `observability/metrics.test.ts` — every histogram + counter asserted via in-memory `MeterProvider` - [x] 13.19 `index.test.ts` — every documented subpath resolves - [x] 13.20 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for the χ² stats test) ## 14. Benchmarks - [x] 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) - [x] 15.1 `bun run check` — Biome 0/0/0 - [x] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/password-generator` — green - [x] 15.3 `bunx turbo test:coverage --filter=@suaveplan/password-generator` — 100/100/100/100 - [x] 15.4 `bun scripts/openspec-conformance.ts @suaveplan/password-generator` — exit 0, no `error` findings - [x] 15.5 `bunx turbo build --filter=@suaveplan/password-generator` — green ## 16. Pre-archive 7-row checklist - [x] 16.1 Generics threaded — `Rng` injectable; `shuffleInPlace` parametric - [x] 16.2 Zod schemas wired at every declared boundary (`generatePassword`, `generatePassphrase`, `assertStrength`) - [x] 16.3 Every error code in `errors/codes.ts` thrown by ≥1 production path (no orphan registry entries — §2c of AGENTS) - [x] 16.4 Strict-mode behaviors tested both on and off (Zod strict-object boundary) - [x] 16.5 OTel instruments emitted from production paths, asserted via real in-memory `MeterProvider` - [x] 16.6 Lifecycle dedup — N/A (pure functions, no in-flight Promise to dedup) - [x] 16.7 No retroactive tick — every box above reflects work done in this change ## 17. Banned-pattern + structural scans (zero output expected) - [x] 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"` - [x] 17.2 `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/vault/password-generator/src/ --include="*.ts"` - [x] 17.3 `grep -rn "Math.random" packages/vault/password-generator/src/ --include="*.ts"` — zero (default `Rng` MUST come from `@suaveplan/crypto-utils.randomBytes`) - [x] 17.4 `grep -rn "biome-ignore" packages/vault/password-generator/src/ --include="*.ts"` — zero - [x] 17.5 Feature barrel `export *` scan — zero - [x] 17.6 File-size budget — every `.ts` under 300 lines, every `.test.ts` under 500 - [x] 17.7 Sibling `.md` present for every implementation `.ts` ## 18. Archive - [x] 18.1 Fold spec delta into `openspec/specs/password-generator/spec.md` (canonical, status ACTIVE) - [x] 18.2 Move `openspec/changes/enterprise-password-generator/` → `openspec/archive/-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)