Initial Commit
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# 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<T>(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<string>` 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<T>` 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/<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)
|
||||
Reference in New Issue
Block a user