Files
2026-05-27 23:29:53 +01:00

112 lines
6.5 KiB
Markdown

# 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/<pkg>` (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/<pkg>`) 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:^
```