# `@suaveplan/packer/runtime` — Clock + Rng injection _(no submodules)_ ## Purpose Every production code path in `@suaveplan/packer` reads wall-clock time and randomness through an injected `PackerRuntime` bundle instead of calling `Date.now()`, `performance.now()`, or `crypto.getRandomValues` directly. This module hosts the platform defaults plus the `resolveRuntime(opts)` factory that every option bundle (`PackerOptions`, `PackChunkedOptions`, `UnpackChunkedOptions`) consumes when it constructs its resolved option bundle. The audit line in `enterprise/packer.md` (L43) makes this a hard requirement: "no `Math.random()` / `Date.now()` at module scope; injectable clock and RNG". The contracts (`Clock`, `MonotonicClock`, `Rng`) are imported from `@suaveplan/types` — they are the shared cross-package interface every Suaveplan library agrees on for time and randomness. Defaults wire to real platform implementations; tests pass in `FakeClock`-backed values and a seeded `Rng` from `@suaveplan/testing` to drive deterministic pack output. ## Features - `defaultClock` — `() => Date.now()`; wall-clock source for `metadata.timestamp`. - `defaultMonotonicClock` — `performance.now()` cast to nanosecond `bigint`; used for histogram observations. - `defaultRng` — wraps `crypto.getRandomValues` and exposes the full `Rng` triplet (`nextUint32`, `nextFloat`, `nextBytes`). - `resolveRuntime(opts?)` — produces a `PackerRuntime` bundle, falling back to defaults for absent fields. Upgrades a seeded `Rng` lacking `nextBytes` by deriving bytes from `nextUint32`. - `elapsedMs(monotonicClock, startNanos)` — converts a monotonic-clock delta into milliseconds for histogram observations. ## Usage: Basic Example ```ts import { Packer } from "@suaveplan/packer"; // Default — no options needed; defaults pull from the platform. const packer = new Packer(); const bundle = await packer.pack(new Uint8Array([1, 2, 3])); console.log(bundle.metadata.timestamp); // current Date.now() value ``` ## Usage: Advanced Example ```ts import { Packer } from "@suaveplan/packer"; import { FakeClock, seededRng } from "@suaveplan/testing"; // Deterministic pack: byte-identical output for byte-identical input. const fake = new FakeClock({ now: 1_700_000_000_000 }); const rng = seededRng(0xdeadbeef); const packer = new Packer({ clock: () => fake.now, monotonicClock: () => BigInt(fake.now) * 1_000_000n, rng, encryption: { algorithm: "xchacha20-poly1305", key: new Uint8Array(32), }, }); const a = await packer.pack(new Uint8Array([1, 2, 3])); const b = await packer.pack(new Uint8Array([1, 2, 3])); // a.data === b.data byte-by-byte because the RNG is seeded and the clock is fixed. ``` ## API Reference - `defaultClock: Clock` — `() => Date.now()`. - `defaultMonotonicClock: MonotonicClock` — `performance.now()` × 1e6 as `bigint`. - `defaultRng: Required> & Rng` — CSPRNG-backed `Rng` with mandatory `nextBytes`. - `resolveRuntime(opts?: RuntimeOptions): PackerRuntime` — produces the resolved bundle; `opts.clock`, `opts.monotonicClock`, `opts.rng` each default to the matching platform default. - `elapsedMs(monotonicClock, startNanos): number` — millisecond delta from the monotonic-clock sample at `startNanos`. ## Implementation Notes - `defaultMonotonicClock` rounds `performance.now()` to the nearest nanosecond integer; the float→bigint cast is lossless for any process uptime under ~584 years. - `ensureNextBytes` (internal) extracts four bytes per `nextUint32` call, big-endian, when the injected `Rng` lacks `nextBytes`. This keeps the AEAD-nonce derivation deterministic under any seeded RNG. - The runtime carries no state between calls — the bundle is a plain object holding the three injected functions. Re-resolving with the same inputs is cheap and side-effect-free. - `resolveRuntime` is the single funnel every production option bundle in the package consumes. The grep audit pinned by PACKER-25's fourth scenario verifies that no other production file references `Date.now` / `Math.random` / `crypto.getRandomValues` / `performance.now`.