# OpenSpec Change: unify-rng-across-packages - **Change ID**: unify-rng-across-packages - **Package**: `@suaveplan/types` (canonical) + downstream consumers - **Design**: ./design.md - **Spec delta**: ./specs/types/spec.md - **Status**: PROPOSED - **Date**: 2026-05-17 ## Why The 2026-04-27 `types-contract-tightening` change widened the canonical `Rng` interface in `@suaveplan/types` (adding optional `nextBytes` and `nextIntInRange` members) and explicitly **deferred** migration of the packages that were redefining `Rng`-shaped interfaces locally (`crypto-core`, `cache`, `idempotency`). That deferral has lapsed: every fresh package author who needs randomness still has to choose between importing the canonical `Rng` or hand-rolling a local one, and the existing local redefinitions silently fork the contract. This change closes the loop. It migrates every genesis package with a local `Rng` type to consume the canonical `Rng` from `@suaveplan/types`, leaving deprecated aliases in place for one release cycle so consumers can update at their own pace. ## What changes For each affected genesis package, the source files that currently declare a local `Rng` interface are migrated to: 1. Import the canonical `Rng` from `@suaveplan/types`. 2. Re-export the canonical `Rng` from the original module path under the original name, with a "Backwards-compatibility alias for `@suaveplan/types` `Rng`" prose comment (per CLAUDE.md §2 — no `@deprecated` JSDoc). 3. Add `@suaveplan/types` as a `workspace:^` dependency where missing. 4. Re-run `bunx turbo typecheck` and `bunx turbo test` to confirm zero runtime / behavioural change. The spec delta is a `## MODIFIED Requirements` block on the existing `Rng has optional bounded operations` requirement in `openspec/specs/types/` that adds an explicit single-source-of-truth clause: no other `@suaveplan/*` package may declare its own `Rng` interface; downstream packages that need a narrower or wider random-source shape MUST either consume `Rng` directly or define a sub-interface that `extends` it. ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/types` to enumerate. Packages that currently declare a local `Rng` type (surveyed via `grep -rln "interface Rng\b\|type Rng = " packages/`) are: - `@suaveplan/crypto-core` — [x] tests run / [x] docs reviewed / [ ] no action needed (declares `type Rng = ByteSource` as a back-compat alias in `src/primitives/random-bytes/random-bytes.ts`) - `@suaveplan/crypto` — [x] tests run / [x] docs reviewed / [ ] no action needed (re-exports the alias from `crypto-core` via `src/index.ts`) - `@suaveplan/testing` — [x] tests run / [x] docs reviewed / [ ] no action needed (declares a full `interface Rng` in `src/context/context.ts`; consumed by `src/factories/factory/factory.ts`) For every other dependent of `@suaveplan/types`: [x] no action needed — they already consume `Rng` from `@suaveplan/types` and are unaffected by this change. ### Source mapping (local Rng → canonical) | Package | Local file | Local symbol | Action | |---|---|---|---| | `@suaveplan/crypto-core` | `src/primitives/random-bytes/random-bytes.ts` | `type Rng = ByteSource` | Replace with deprecated re-export of canonical `Rng`; keep `ByteSource` (distinct contract). | | `@suaveplan/crypto` | `src/index.ts` | re-export `Rng` from `crypto-core` | Update re-export to point at canonical alias. | | `@suaveplan/testing` | `src/context/context.ts` | `interface Rng { nextUint32; nextFloat }` | Import canonical `Rng`; re-export as deprecated alias. Add `@suaveplan/types` workspace dep (rule 8 exception). | ### Verification - **Verified by:** `bunx turbo typecheck test --filter=@suaveplan/crypto-core` - **Verified by:** `bunx turbo typecheck test --filter=@suaveplan/crypto` - **Verified by:** `bunx turbo typecheck test --filter=@suaveplan/testing` - **Verified by:** `bunx turbo typecheck test --filter=...@suaveplan/testing` (dependents of `testing` consume `Rng` transitively via `factory.ts`) - **Verified by:** repo-wide grep `grep -rln "interface Rng\b\|type Rng = " packages/ --include="*.ts" | grep -v "/dist/\|/node_modules/"` returns only the canonical declaration in `packages/core/types/src/rng.ts` plus the deprecated alias forwarders the change introduces. ## Out of scope - **Widening the canonical `Rng`.** If a consumer needs additional members (e.g. `nextBigInt`, streaming bytes), open a separate `types-contract-tightening`-style change against `@suaveplan/types`. - **Removing the deprecated aliases.** Follow-up change (one release cycle after this one archives) removes the `Rng` re-exports from `crypto-core`, `crypto`, and `testing`. Tracked as a TODO in this proposal's design.md. - **Upstream-only packages (`cache`, `idempotency` upstream RNG redefinitions).** The 2026-04-27 deferral text named `cache` and `idempotency` as RNG-shape redefiners. On genesis, `@suaveplan/idempotency` and the three cache packages (`@suaveplan/cache`, `cache-storage`, `cache-redis`) do **not** currently reference `Rng` at all (verified by grep). When those packages port the upstream randomness features, the migration must consume the canonical `Rng` directly — there is no local type to migrate here yet. - **Runtime behaviour changes.** This change is type-only; the test suite must remain green pre- and post-migration with zero assertion edits. ## Migration - **Old**: consumers importing `Rng` from `@suaveplan/crypto-core`, `@suaveplan/crypto`, or `@suaveplan/testing`. - **New**: import `Rng` from `@suaveplan/types`. - **Search command**: `grep -rln "from ['\"]@suaveplan/\(crypto-core\|crypto\|testing\)['\"]" packages/ | xargs grep -l "Rng\b"` - **Recommended replacement**: `import type { Rng } from "@suaveplan/types"`. The deprecated alias re-exports remain functional for one release cycle.