# Design: unify-rng-across-packages ## Goal Single canonical `Rng` interface across every `@suaveplan/*` package, with zero runtime behaviour change and a one-release-cycle deprecation window for the local aliases that are being phased out. ## Strategy 1. **Structural conformance over re-declaration.** The canonical `Rng` in `@suaveplan/types` (`src/rng.ts`) is the contract. Local types that exactly match this shape become deprecated re-exports of the canonical. Local types that diverge are evaluated case-by-case (see compatibility table below). 2. **Deprecated forwarders, not deletions.** Per CLAUDE.md §2, JSDoc `@deprecated` tags are banned. The aliases are kept as named re-exports with prose comments ("Backwards-compatibility alias for `@suaveplan/types` `Rng` — prefer the canonical import; this re-export will be removed in a follow-up change.") so existing consumers continue to compile. 3. **Type-only migration.** No runtime behaviour changes. Every package's full test suite must remain green with zero assertion edits before and after the swap. Verified with `bunx turbo typecheck test --filter=@suaveplan/` (rule 1: never `tsc` directly). ## Compatibility table | Package | Local symbol | Local surface | Canonical surface | Compatible? | Action | |---|---|---|---|---|---| | `@suaveplan/types` | `Rng` | `{ nextUint32(): number; nextFloat(): number; nextBytes?(n): Uint8Array; nextIntInRange?(min, maxExclusive): number }` | — (canonical) | — | None — source of truth. | | `@suaveplan/crypto-core` | `type Rng = ByteSource` where `ByteSource = (n: number) => Uint8Array` | `(n) => Uint8Array` (a **function**, not an object with methods) | object with `nextUint32`/`nextFloat` | **No** — structurally incompatible | Drop the `Rng` alias entirely; keep `ByteSource` as the package's own concept (distinct contract: a byte-source callable, not a uniform-distribution number source). Re-export the canonical `Rng` as a deprecated forwarder so any external code that imported `crypto-core`'s `Rng` keeps compiling but now sees the canonical shape. **Note**: any consumer that was actually using `crypto-core`'s `Rng` as a callable will fail typecheck — this is intentional and the migration note in proposal.md flags it. | | `@suaveplan/crypto` | re-exports `Rng` from `crypto-core` | same as crypto-core | object | inherits from crypto-core | Update `src/index.ts` to forward the canonical `Rng` (via crypto-core's new deprecated re-export). | | `@suaveplan/testing` | `interface Rng { nextUint32(): number; nextFloat(): number }` | exact subset of canonical | object | **Yes** — structural match (canonical adds two OPTIONAL members; required members are identical) | Import canonical `Rng`; re-export as deprecated forwarder. `cryptoRng`/`seededRng` implementations satisfy the canonical type without code changes (they implement the required members and omit the optional ones). Add `@suaveplan/types` as a `workspace:^` dependency (rule 8 exception: testing-infrastructure may depend on `@suaveplan/types` for types-only, byte-size-test guarded). | ## Why `crypto-core`'s `ByteSource` is not the canonical `Rng` The canonical `Rng` is an **object** with `nextUint32` / `nextFloat` (uniform number sources). `crypto-core`'s `ByteSource` is a **function** that returns random bytes — a fundamentally different shape and use case (key material, nonces, AEAD entropy). These should never have shared the `Rng` name. The migration cleans up the naming collision: `ByteSource` stays under its real name, and `Rng` becomes the canonical numeric RNG everywhere. ## Back-compat strategy For one release cycle (until the follow-up removal change archives): - `@suaveplan/crypto-core/random-bytes` exports a deprecated `Rng` re-export pointing at `@suaveplan/types`' `Rng`. The original `type Rng = ByteSource` alias is **removed** because keeping both shapes under the same name would break the typecheck. - `@suaveplan/crypto/index` forwards the same deprecated alias. - `@suaveplan/testing/context` exports a deprecated `Rng` re-export of the canonical type. The local `interface Rng` declaration is removed. The deprecation prose comment names the canonical import path and the removal change ID (TBD when the follow-up is opened). ## Removal follow-up (not in scope here) A separate OpenSpec change titled `remove-deprecated-rng-aliases` (opened **at least one release cycle after this change archives**) will: 1. Delete the `Rng` re-export from `@suaveplan/crypto-core/primitives/random-bytes/random-bytes.ts`. 2. Delete the `Rng` re-export from `@suaveplan/crypto/src/index.ts`. 3. Delete the `Rng` re-export from `@suaveplan/testing/src/context/context.ts`. 4. Run dependent sweep (`bunx turbo test --filter=...@suaveplan/`) for each of the three packages. 5. Update consumers caught by the sweep with the canonical import. This is tracked as a follow-up item — not a phase-2 split of this change (per CLAUDE.md §2b, this change is complete on its own surface). ## Error codes No new error codes are introduced. The migration is structural-types-only; no new throw sites, no new error paths. ## Verification commands (per CLAUDE.md rule 1) ```bash # Per affected package — full pre-publish gate bunx turbo lint typecheck test --filter=@suaveplan/crypto-core bunx turbo lint typecheck test --filter=@suaveplan/crypto bunx turbo lint typecheck test --filter=@suaveplan/testing # Dependent sweep for testing (factory consumers) bunx turbo test --filter=...@suaveplan/testing # Repo-wide grep — only the canonical declaration plus the three deprecated # re-exports should remain grep -rln "interface Rng\b\|type Rng = " packages/ --include="*.ts" \ | grep -v "/dist/\|/node_modules/" ``` ## File layout (per affected package) No new files. The migration edits exactly three implementation files and their three sibling `.md` files (doc-drift gate, CLAUDE.md §4a): ```text packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.ts # swap alias packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.md # update API reference packages/crypto/crypto/src/index.ts # update forwarder packages/crypto/crypto/src/index.md # update API reference (if mentions Rng) packages/testing/testing/src/context/context.ts # swap interface for re-export packages/testing/testing/src/context/context.md # update API reference packages/testing/testing/package.json # add @suaveplan/types: workspace:^ ```