12 KiB
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.mdend-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.mdSections 13, 14, 14a - 1.6 Read CLAUDE.md (full) — type design, file size, doc rules
- 1.7 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.8 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.9 Draft
specs/password-generator/spec.mdfromopenspec/templates/spec-delta.mdwith all requirements - 1.10 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.11
bun openspec change validate enterprise-password-generatorclean
2. Package scaffold
- 2.1
packages/vault/password-generator/directory withsrc/{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-utilsworkspace,@opentelemetry/api/tslib/zodvia catalog - 2.3
tsconfig.lib.jsonextends@suaveplan/typescript-config/library.json - 2.4
vite.config.ts(build) +vitest.config.ts(createTestConfig,provider: 'istanbul', 100/100/100/100) - 2.5
README.mdwith 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 against2^32 - (2^32 mod n); throwsPWGEN.RNG_FAILEDifrngreturns a non-uint32 after a bounded loop - 3.2
src/random/fisher-yates.ts—shuffleInPlace<T>(array: T[], rng: Rng): T[]usingrandomInt
4. Implementation — char-class password generator
- 4.1
src/charsets/charsets.ts—LOWERCASE,UPPERCASE,DIGITS,SYMBOLS,AMBIGUOUS,LOOKALIKESReadonlySet<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 < requiredCountOR empty effective charset) withPWGEN.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?): resolveWordlistRef→ registry → reject< 256entries withPWGEN.WORDLIST_TOO_SHORT→randomInt-picklengthwords → apply capitalisation mode → join with separator → optionally insertaddDigits/addSymbolsat randomised positions
7. Implementation — PIN mode
- 7.1
src/pin/pin.ts—generatePin(length, rng?): bias-free uniform[0, 10)overlengthdigits; rejectslength < 1withPWGEN.OPTIONS_INFEASIBLE
8. Implementation — wordlists
- 8.1
src/wordlists/eff-long.ts— bundled EFF Long Wordlist (7776 entries, ASCII, deduplicated); exported asEFF_LONG_WORDLIST: readonly string[] - 8.2
src/wordlists/eff-short.ts— bundled EFF Short Wordlist 1 (1296 entries, ASCII, deduplicated); exported asEFF_SHORT_WORDLIST: readonly string[] - 8.3
src/wordlists/registry.ts—registerWordlist(name, words[])(rejects< 256withPWGEN.WORDLIST_TOO_SHORT),getWordlist(name)(throwsPWGEN.WORDLIST_NOT_REGISTEREDfor unknown names),listWordlists(): string[]. Pre-populates registry witheff-longandeff-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)returnslog2(wordlistSize) * words.length;assertStrength(password, minBits, opts?)throwsPWGEN.STRENGTH_INSUFFICIENTifbits < minBits
10. Implementation — errors, schemas, observability
- 10.1
src/errors/codes.ts—defineCodes("PWGEN", [...])withINVALID_OPTIONS,OPTIONS_INFEASIBLE,WORDLIST_TOO_SHORT,WORDLIST_NOT_REGISTERED,STRENGTH_INSUFFICIENT,RNG_FAILED - 10.2
src/schemas/schemas.ts— ZodGeneratePasswordOptionsSchema,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 (noexport *) - 11.2 Per-feature
index.tsbarrels — explicit named re-exports - 11.3
package.jsonexports map covers every documented subpath
12. Documentation
- 12.1 Sibling
.mdfor 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
.mdunder## 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— 1MrandomInt(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,< 256rejection, 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 againstn = 10 - 13.15
entropy/entropy.test.ts— class-detection table, log2 math,assertStrengththrow 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 withPWGEN.INVALID_OPTIONS - 13.18
observability/metrics.test.ts— every histogram + counter asserted via in-memoryMeterProvider - 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, noerrorfindings - 15.5
bunx turbo build --filter=@suaveplan/password-generator— green
16. Pre-archive 7-row checklist
- 16.1 Generics threaded —
Rnginjectable;shuffleInPlace<T>parametric - 16.2 Zod schemas wired at every declared boundary (
generatePassword,generatePassphrase,assertStrength) - 16.3 Every error code in
errors/codes.tsthrown 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 (defaultRngMUST 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
.tsunder 300 lines, every.test.tsunder 500 - 17.7 Sibling
.mdpresent 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.mdviabun scripts/folder-reorg/generate-manifest.ts(orchestrator after parallel migrations land) - 18.4 Refresh
MISSING.ymlviabun 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 --fixclean across touched files (orchestrator)