5.8 KiB
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:
- Import the canonical
Rngfrom@suaveplan/types. - Re-export the canonical
Rngfrom the original module path under the original name, with a "Backwards-compatibility alias for@suaveplan/typesRng" prose comment (per CLAUDE.md §2 — no@deprecatedJSDoc). - Add
@suaveplan/typesas aworkspace:^dependency where missing. - Re-run
bunx turbo typecheckandbunx turbo testto 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 (declarestype Rng = ByteSourceas a back-compat alias insrc/primitives/random-bytes/random-bytes.ts)@suaveplan/crypto— [x] tests run / [x] docs reviewed / [ ] no action needed (re-exports the alias fromcrypto-coreviasrc/index.ts)@suaveplan/testing— [x] tests run / [x] docs reviewed / [ ] no action needed (declares a fullinterface Rnginsrc/context/context.ts; consumed bysrc/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 oftestingconsumeRngtransitively viafactory.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 inpackages/core/types/src/rng.tsplus 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 separatetypes-contract-tightening-style change against@suaveplan/types. - Removing the deprecated aliases. Follow-up change (one release cycle
after this one archives) removes the
Rngre-exports fromcrypto-core,crypto, andtesting. Tracked as a TODO in this proposal's design.md. - Upstream-only packages (
cache,idempotencyupstream RNG redefinitions). The 2026-04-27 deferral text namedcacheandidempotencyas RNG-shape redefiners. On genesis,@suaveplan/idempotencyand the three cache packages (@suaveplan/cache,cache-storage,cache-redis) do not currently referenceRngat all (verified by grep). When those packages port the upstream randomness features, the migration must consume the canonicalRngdirectly — 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
Rngfrom@suaveplan/crypto-core,@suaveplan/crypto, or@suaveplan/testing. - New: import
Rngfrom@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.