rusty-pwgen 0.2.0

Generate pronounceable or random passwords from the OS CSPRNG — a Rust port of Theodore Ts'o's `pwgen` with strict-compat mode, deterministic `-H` reproducible mode (SHA256 + ChaCha20), and a typed library API.
Documentation
# rusty-pwgen — v0.2.0 Feature Layout

**Status**: implementation draft for the v0.2.0 Cargo features convention
backfill (spec 00011, Phase 7 — rusty-pwgen).

**Authority**:
- `specs/adrs/0006-cargo-features-convention-for-portfolio-ports.md` (why)
- `project-instructions.md` §Cargo Feature Surface (what)
- This document — the per-port carving + WHY for each leaf, per HINT-003
  + HINT-009 of spec 00011.

**Reference port**: rusty-figlet v0.2.0 — see `../../rusty-figlet/docs/feature-layout.md`
(FROZEN reference port) for the format anchor. rusty-pwgen conforms to the
same shape with the minimum-convention surface dictated by its
single-capability scope. The companion sibling ports rusty-sponge v0.2.0
(see `../../rusty-sponge/docs/feature-layout.md`, E011 Phase 4),
rusty-vipe v0.2.0 (see `../../rusty-vipe/docs/feature-layout.md`,
E011 Phase 5), and rusty-pee v0.2.0 (see `../../rusty-pee/docs/feature-layout.md`,
E011 Phase 6) established the zero-leaf precedent for single-capability
ports with an aliased `<port>` binary.

**Iteration model**: v0.2.0 is a **purely additive** SemVer-minor release.
Every v0.1.x feature name and composition is preserved verbatim; new
umbrellas (`full`, `pwgen-classic`, `pwgen-minimal`) are layered on top
without renaming or narrowing the existing `cli` / `default` / `pwgen-alias`
/ `bench` features. Library and binary API surfaces are unchanged.

## Single-capability port — spec 00011 §Scope Edge Cases

rusty-pwgen is a **single-capability port**: it has exactly one documented
capability — generate pronounceable or random passwords from the OS CSPRNG
(a Rust port of Theodore Ts'o's `pwgen`). Spec 00011 §Scope Edge Cases
dictates that single-capability ports apply the **minimum convention**:

> ports with only one capability adopt the minimum convention:
> `full = ["cli"]` and `<port>-classic = ["cli"]` are the required
> umbrellas; ZERO leaves carved beyond those required umbrellas.

This document records the carving exercise and the explicit decision
to NOT split orthogonal sub-capabilities into leaves — every additional
behavior of `rusty-pwgen` (pronounceable phoneme generator, secure
uniform-random generator, ambiguous-character filter, no-vowels filter,
arbitrary character removal, reproducible `-H` mode SHA256+ChaCha20
chain, TTY-aware columnar output, Strict-mode last-wins argv pre-scanner,
`completions` subcommand) is part of the single core capability surface
and removing any of them would either break the documented public
CLI / library contract or change byte-output for stored seed files
(the `-H` chain is locked at v0.1.0 per the lockstep-SemVer policy).

## Source-tree walk

`src/` modules (v0.1.0, post-Phase-1 baseline):

| Module                | Always-on?   | CLI-only deps                                       | Notes                                                                       |
|-----------------------|-------------:|-----------------------------------------------------|-----------------------------------------------------------------------------|
| `error.rs`            | yes          | (thiserror — always-on)                             | `Error` enum; library + binary need it.                                     |
| `charset.rs`          | yes          | none                                                | Character-set assembly with capitalize/numerals/symbols/ambiguous filters.  |
| `phoneme.rs`          | yes          | none                                                | Pronounceable phoneme-driven password generator (default mode).             |
| `secure.rs`           | yes          | none                                                | Uniform-random password generator (`-s` mode).                              |
| `rng.rs`              | yes          | (rand + rand_chacha — always-on)                    | `RngSource` trait, OS CSPRNG source, seeded ChaCha20Rng source for `-H`.    |
| `lib.rs`              | yes          | none                                                | Public API (`PwgenBuilder`, `Pwgen`, `CompatibilityMode`).                  |
| `cli.rs`              | no — `cli`   | clap                                                | clap-derive `Cli` struct + `Subcommand::Completions`.                       |
| `mode.rs`             | no — `cli`   | none (gated by `cli`)                               | CompatibilityMode resolver (`--strict` > env > argv[0]).                    |
| `output.rs`           | no — `cli`   | terminal_size                                       | TTY detection + columnar output formatting.                                 |
| `seed.rs`             | no — `cli`   | (gated by `cli`)                                    | `-H <file>[#suffix]` seed-input resolver.                                   |
| `strict.rs`           | no — `cli`   | (clap_complete + clap pulled by `cli`)              | Hand-rolled Strict-mode argv pre-scanner + byte-equal upstream dispatcher.  |
| `main.rs`             | no — `cli`   | clap, clap_complete, anyhow, terminal_size          | Binary entry; gated by `required-features = ["cli"]`.                       |
| `bin/pwgen.rs`        | no — `pwgen-alias` | (inherits `cli`)                              | `pwgen` alias binary; gated by `required-features = ["pwgen-alias"]`.       |

## Leaf-carving criteria (HINT-009)

A capability becomes a leaf when ALL of the following hold:

1. It is **self-containable** — gated cleanly via `#[cfg(feature = "<leaf>")]`
   at the module or top-level item boundary (HINT-004).
2. Either (a) it has a **sole optional dependency** that no other leaf needs
   (HINT-005), OR (b) it is a pure-cfg-gate of an internal module worth
   exposing as a knob.
3. Disabling it does NOT break any always-on library/CLI surface.

A capability does NOT become a leaf when:

- It is foundational (password generation cores: pronounceable phoneme
  loop, secure uniform random, RNG sourcing) — disabling it would break
  the headline `cargo install rusty-pwgen` capability.
- It is part of the single documented capability surface (pronounceable
  mode, secure mode, ambiguous filter, no-vowels filter, remove-chars
  filter, reproducible `-H` mode, columnar output, completions
  subcommand, Strict-mode dispatcher).
- It would create more than ~6 leaves (FR-007 + HINT-003 envelope).

## v0.2.0 Carved Leaves

**ZERO new leaves carved at v0.2.0**. Every capability inside rusty-pwgen
is either:

1. Foundational always-on library code (pronounceable phoneme generator,
   secure uniform random generator, RNG sourcing, charset assembly,
   error types) — cannot be stripped without breaking the public surface.
2. Already gated by the v0.1.x `cli` umbrella (clap-derived argument
   parsing, completions subcommand, mode resolver, output formatting,
   Strict-mode argv pre-scanner, seed file resolver).
3. Already gated by the v0.1.x `pwgen-alias` feature (the second
   `pwgen` binary entry).
4. A dev-tooling feature (`bench` → criterion benches) outside the
   convention's runtime-capability purview.

### Leaves intentionally NOT carved

The following candidate leaves were considered + rejected:

- **`reproducible-h` / `seed-file`**: The `-H <file>[#suffix]`
  reproducible-mode chain (SHA256 + ChaCha20Rng) is locked at v0.1.0
  per the lockstep-SemVer stability policy; any change is a MAJOR bump
  because consumers may have stored seed files expecting specific
  passwords. Carving this as an opt-out leaf would let users
  accidentally drop reproducibility — silently breaking the FR-028
  contract. The `sha2` + `rand_chacha` deps are pulled regardless
  (also used by the always-on library API). Rejected per HINT-009
  criterion 3.
- **`pronounceable`** / **`secure`**: Both generation modes share the
  same `charset.rs` + `rng.rs` foundation; splitting them would orphan
  the foundation to one leaf or the other and require artificial
  cross-gating in `lib.rs::PwgenBuilder::build()` (which dispatches
  on the `secure` flag at runtime). No external dep saved either way.
  Both are part of the documented public API. Rejected per HINT-009
  criteria 1 + 3.
- **`completions`**: Could be carved as `["dep:clap_complete"]`, but
  per spec 00011 §Scope Edge Cases minimum-convention single-capability
  ports declare ZERO new leaves. `clap_complete` is bundled into the
  v0.1.x `cli` umbrella verbatim. Carving it would either rename `cli`
  (breaking SemVer additivity) or duplicate the surface.
- **`strict-compat`**: rusty-pwgen's Strict mode dispatches inline in
  `lib.rs::run()` via `mode::resolve` and the hand-rolled getopt mirror
  in `src/strict.rs`. Both are gated by the `cli` umbrella in v0.1.x
  (since they consume `clap` for the `--strict` flag itself). Carving
  out a separate `strict-compat` leaf would require splitting
  `strict.rs` away from `cli.rs`, which is more refactoring than the
  additive v0.2.0 release allows. The capability survives untouched
  inside the existing `cli` composition. (Note: rusty-figlet carves
  `strict-compat` because its Strict-mode parser is dep-free hand-rolled
  getopt; rusty-pwgen's Strict dispatcher consumes `clap` for the
  `--strict` flag itself, so it cannot stand alone without `cli`.)
- **`pwgen-alias`**: This v0.1.x feature ships a second binary named
  `pwgen`. It IS retained verbatim per the v0.2.0 SemVer additive
  contract — but it is NOT one of the 2 required preset bundles per
  FR-007 (those are `pwgen-classic` and `pwgen-minimal` below).
  Documented separately as an installation-time convenience knob.
  PATH-collision behavior with upstream `pwgen` is documented in the
  README.
- **`bench`**: The v0.1.x `bench` feature is a tooling / benchmark
  scaffold (criterion benches under `benches/`), not a runtime
  capability leaf. It remains a dev-tooling feature outside the
  convention's purview (the vendored `tools/feature-lint/lint.sh`
  allowlist skips `bench` from leaf-CI-matrix and phantom-leaf checks)
  and is retained verbatim from v0.1.0.

## Preset bundles (FR-007 — 2 required for single-capability ports)

Per spec 00011 §Scope Edge Cases + FR-007, even single-capability ports
declare 2 preset bundles to give the keep-list workaround documentation
something concrete to point at.

### `pwgen-classic` (REQUIRED — bare port, 1:1 with upstream pwgen 2.08)

```toml
pwgen-classic = ["cli"]
```

- Includes `cli` so the binary exists.
- Single-capability port; the `cli` umbrella IS the bare-port surface.
- Use case: `cargo install rusty-pwgen --no-default-features --features pwgen-classic`
  for a pwgen 2.08 drop-in replacement (Strict mode is invoked via
  the `--strict` flag, `RUSTY_PWGEN_STRICT` env var, or `pwgen-alias`
  binary name — none of these require additional features).

### `pwgen-minimal`

```toml
pwgen-minimal = ["cli"]
```

- Same composition as `pwgen-classic` (single-capability port has no
  smaller subset to carve).
- Use case: explicit "minimal CLI install" alias for users who prefer
  the `<port>-minimal` naming convention seen across other Rusty ports
  (figlet-minimal, ts-minimal, sponge-minimal, vipe-minimal,
  pee-minimal).
  Documented as an intentional semantic alias rather than a distinct
  composition.

### `pwgen-alias` (v0.1.x feature retained, NOT a convention preset)

`pwgen-alias = ["cli"]` from v0.1.0 ships an additional `pwgen` binary
alongside `rusty-pwgen`. It is retained verbatim per the v0.2.0 SemVer
additive contract — but it is NOT one of the 2 required preset bundles
per FR-007 (those are `pwgen-classic` and `pwgen-minimal` above).
`pwgen-alias` is documented separately as an installation-time
convenience knob, not a capability subset.

### `bench` (v0.1.x dev-tooling feature retained, NOT a convention preset)

`bench = ["dep:criterion"]` from v0.1.0 enables criterion benches under
`benches/`. It is dev-tooling, not a runtime capability — outside the
convention's purview per the vendored feature-lint allowlist.

## Cross-port glossary candidates

No leaves carved → no cross-port glossary contributions from rusty-pwgen
in this iteration. If a future minor release adds an orthogonal
capability (e.g., a `naughty-word-filter` leaf adapting upstream's
compile-time substring blocklist, or an `alt-phonics` leaf), the leaf
would be a candidate for promotion to `docs/feature-vocabulary.md`
per FR-053.

## CI matrix shape (FR-010..FR-014)

Per plan §Per-Port v0.2.0 CI Matrix, scaled to a zero-leaf port:

- **Tier 1 — `test-default`**: full DDR-003 cross-compile matrix
  (5 targets). Post-v0.2.0 `default = ["full"]` and `full = ["cli"]`,
  so the kitchen-sink test resolves to the same set as v0.1.0
  `default = ["cli"]` — no regression in coverage.
- **Tier 2 — `test-no-default`**: Linux x86_64 only. `cargo test
  --no-default-features --lib` + dep-tree audit (SC-001 evidence).
- **Tier 3 — `test-<bundle>`**: one job per preset bundle. Linux only.
  - `test-pwgen-classic`
  - `test-pwgen-minimal`
- **Tier 4 — `check-leaf-<leaf>`**: SKIPPED. Zero leaves → no
  per-leaf compile-check jobs. A placeholder comment in `ci.yml`
  documents why this tier is empty. The `bench` feature is in the
  vendored feature-lint allowlist (dev-tooling) and does not require
  a Tier-4 entry.
- **Tier 5 — `lint-convention`**: single Linux job invoking the
  vendored `tools/feature-lint/run.sh` script.

Per FR-014, bundle/lint jobs are constrained to Linux x86_64.

## Vendored feature-lint

Per spec 00011 §Phase 2 iteration 6 precedent (rusty-figlet vendored
the lint script because the umbrella `jsh562/rustylib` is private and
cross-repo `actions/checkout` cannot reach it), rusty-pwgen vendors
`tools/feature-lint/{lint.sh,run.sh,README.md}` from the umbrella into
the port repo. The vendored copy is byte-equal to the umbrella source
of truth as of the freeze commit (post the dev-tooling-allowlist +
benches/tests-search + additive-CHANGELOG-support fixes from rusty-ts
v0.2.0 / E011 Phase 3 iteration 2 and the path-sanitization fixes from
rusty-sponge v0.2.0 / E011 Phase 4).

## Why no new leaves — explicit rationale

Spec 00011 §Scope Edge Cases anticipates this case verbatim:

> Some ports have only one orthogonal capability. Those ports adopt the
> minimum convention: `full = ["cli"]` and `<port>-classic = ["cli"]`
> as aliases; the convention SHAPE is consistent across the portfolio
> even when the per-port leaf carving yields zero leaves.

rusty-pwgen deliberately chooses the zero-leaf path because:

1. The `-H` reproducible-mode contract (SHA256 + ChaCha20Rng chain) is
   locked at v0.1.0 per the lockstep-SemVer stability policy. Carving
   any of its supporting machinery (RNG sourcing, charset assembly,
   phoneme/secure generators) into an opt-out leaf would let users
   silently break stored-seed-file reproducibility — a MAJOR-bump
   behavior change disguised as a feature flag.
2. The cost of carving a speculative leaf (cfg-gate scaffolding,
   per-leaf CI matrix entry, README/CHANGELOG row, glossary candidacy)
   outweighs the value when no orthogonal capability exists to gate.
3. The portfolio-wide convention shape (umbrella set, README "Cargo
   Features" section, lint compliance) is preserved verbatim — a
   downstream library consumer reading the README for rusty-pwgen
   gets the same one-glance feature matrix UX as one reading
   rusty-figlet, rusty-ts, rusty-sponge, rusty-vipe, or rusty-pee.
4. v0.2.0 is **purely additive**. Every v0.1.x feature is preserved
   verbatim; no SemVer break. Future minor releases can add leaves
   without breaking the v0.2.0 contract: a hypothetical
   `naughty-word-filter` v0.3.0 feature would slot in as
   `naughty-word-filter = []` alongside the existing umbrellas with
   zero migration cost.