Skip to content
Back to skills

Seeded Determinism And Golden Vectors

ASecurity

Makes seeded output identical on every device: one PRNG, date keys, cutovers, golden vectors. Use when code shuffles, replaces Math.random, picks daily content or replays a seed or share code. Not for streaks (dates-streaks-and-statistics).

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
developmenttypescriptpythongosqlreactnodeawsgitapiperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill seeded-determinism-and-golden-vectors --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Seeded Determinism And Golden Vectors?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Seeded Determinism And Golden Vectors
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-seeded-determinism-and-golden-vectors-reactnative-skills/badge)](https://www.skillsdirectory.com/skills/zakariaf-seeded-determinism-and-golden-vectors-reactnative-skills)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: seeded-determinism-and-golden-vectors
description: "Makes seeded output identical on every device: one PRNG, date keys, cutovers, golden vectors. Use when code shuffles, replaces Math.random, picks daily content or replays a seed or share code. Not for streaks (dates-streaks-and-statistics)."
---

# Seeded determinism and golden vectors

Some output must be a pure function of a key: the daily content every user compares, a generated
layout, a shuffle a share code reproduces, a replay. This skill makes that output identical on every
device, in every time zone and on next year's build, with no server: one seeded PRNG the app owns,
civil-date keys, content regenerated from `(key, version)` instead of stored, shipped generators frozen
behind versioned cutovers, and golden vectors computed by an independent oracle that two checkers and
the test suite verify.

## Rules that must hold

1. **The key is an argument; seeded code never reads a clock.** A generator takes its `DateKey`, id or
   sequence number as a parameter; nothing it reaches calls `Date.now`, `new Date`, `performance.now`
   or imports the clock service. Why: a clock-reading generator cannot be pinned, replayed from a bug
   report or run for tomorrow in a test.
2. **A day is a civil-date key `'YYYY-MM-DD'`, computed with integers, from one day definition.** Keys
   come from `clock.today()` at the edge and go through `src/core/dates/date-key.ts`; never an instant,
   never the UTC day next to a local-day comparison. Why: an instant makes every read zone-dependent,
   and two day definitions disagree for hours every day.
3. **One entropy source: `src/core/rng/sfc32.ts`, threaded as state.** No `Math.random`, `crypto`,
   `expo-crypto`, `uuid` or PRNG package on a seeded path; functions take a state and return
   `{ value, state }`. Why: a library PRNG promises no frozen sequence across its versions, and only
   exact integer operations (all sfc32 uses) give the same bits in V8 and in Hermes on iOS and Android.
4. **A seed is a hash plus a frozen salt, mixed by `seedRng` before any draw.** `seedRng(dailySeed(key,
   SALT))` or `seedRng(hashSeed(text) ^ SALT)`; never `dailySeed(...) % n`. One salt per feature and
   mode, unique and recorded. Why: FNV seeds of neighbouring days differ by a small multiple of the FNV
   prime, so an unmixed seed walks the catalog in a visible step; shared salts make two features move
   together.
5. **Seeded paths, and every file they import, use only exactly specified operations.** `+ - * / %`,
   bitwise, `Math.sqrt/imul/floor/round/abs/min/max/PI`; no `**`, no other `Math.*`, no `Intl`,
   `toLocale*` or `localeCompare`; floats frozen to integers before they are stored or fingerprinted.
   Why: ECMA-262 leaves `sin`, `exp`, `pow` and `**` implementation-approximated (Hermes uses each
   platform's libm) and Hermes takes locale data from iOS and Android separately.
6. **Draw order is part of the output.** Each generator version documents its draws; inputs are sorted
   by a stable key (never `localeCompare`, never unordered SQL); the core modules never change. Why:
   one extra or reordered draw rewrites every output after it.
7. **Regenerate, never store.** Persist the key, the `generatorVersion` that ran and the user's outcome;
   regenerate the content with `generate(key, storedVersion)`. Why: a stored copy is a second source of
   truth that the next version contradicts, and history becomes un-migratable.
8. **A shipped generator is frozen; a change is a new version.** A date-keyed generator serves it from
   a future cutover date; a text-keyed one (a share code) carries the version in the key, and callers
   pass it. Append the version to the code table and the contract, keep every old version callable,
   stamp the version in the output; a committed cutover, salt or generator never moves or disappears
   (removing a whole generator takes a migration, the developer's approval and a `"retired"` entry).
   Salt, catalog, count and draw-order changes are all versions. Why: once users saw a key's output,
   that output is what the key means.
9. **Every generator is pinned by golden vectors.** A committed `<feature>.vectors.json` of `(key,
   version) → fingerprint + summary` rows, asserted exactly by a Jest test, covering every version; for
   date-keyed generators also both sides of each cutover, a year-end/new-year pair and a leap day. Why:
   a fingerprint catches any change and a summary line shows a reviewer what changed.
10. **Expected values come from an independent oracle.** The oracle is written from the spec in another
    language or style (BigInt TypeScript, Python), imports nothing from `src/`, directly or through a
    helper, and carries its own copy of the data. Why: values generated by the implementation under
    test enshrine its bugs.
11. **Rows are filled by a reviewed hand-run script; committed rows are frozen; CI verifies and never
    blesses.** The update script fills only missing rows and refuses to change a filled one; the checker
    fails any committed row that changed or vanished, however it was refilled; no CI job, hook or gate
    script runs the script. Why: a refilled row erases the record of what users saw, and a gate that
    rewrites what it checks verifies nothing.
12. **The core is a compatibility contract pinned by literals.** `sfc32.ts`, `seeded-draws.ts`,
    `fingerprint.ts`, `date-key.ts` and `daily-seed.ts` keep the golden values their tests pin as
    literals computed by the oracle. Why: a change there changes every seeded output on every installed
    copy, and a golden computed by the module under test can never fail.
13. **Generators are pure and repeatable.** A generator loads in plain Node (no React Native, Expo,
    stores or services), is synchronous, keeps no module state, and returns identical output for the
    same `(key, version)` whatever ran before. Why: purity is what lets tests, oracles and checkers run
    it headlessly, and hidden state makes the second call differ from the first.

## Workflow

1. **Decide what is seeded.** List every output a user may compare with another user or with their own
   past (daily content, shuffles, share codes, generated layouts). Read
   [references/determinism-policy.md](references/determinism-policy.md) for what seeded code may use
   and why.
2. **Copy the core** (once per app) from `templates/src/core/`: `rng/sfc32.ts`, `rng/seeded-draws.ts`,
   `rng/fingerprint.ts`, `dates/date-key.ts`, `dates/daily-seed.ts`, `dates/generator-versions.ts`, each
   with its test. Never edit them; read [references/seeded-rng-and-seeds.md](references/seeded-rng-and-seeds.md)
   and [references/civil-date-keys.md](references/civil-date-keys.md) for the API, the seeding and the
   goldens. If an app already has a `date-key.ts` from dates-streaks-and-statistics, keep one copy.
3. **Write the generator** from `templates/src/features/__FEATURE__/__FEATURE__-generator.ts` (replace
   `__FEATURE__` kebab, `__FEATURE_PASCAL__`, `__FEATURE_CONST__`): pick a new random salt, freeze the
   catalog, document the draw order, export `generate(key, version?)` and `summarize(output)`. Take
   "today" from `clock.today()` in the caller (dates-streaks-and-statistics owns today). The template
   salt `0x2f6b4c1d` (795560989 in the contract), the `item-01 … item-12` catalog in the generator and
   the oracle, the generator test's `/^item-\d{2}$/` id check and the vectors rows are template values:
   replace them all before the first release (step 6 refills the rows). A generator keyed by text (a
   share code) imitates `examples/starter-pack-share-code/`: `"keyKind": "text"`, every version
   `from: null`, and callers always pass the version the code carries.
4. **Register it** in `contracts/seeded-generators.json` (template; keys in
   [references/checkers.md](references/checkers.md)): module, exports, salt in decimal, versions,
   vectors file, outcome file, regenerate command. An app with no generator keeps `"generators": []`.
5. **Write the oracle** from `templates/scripts/oracles/__FEATURE__-oracle.ts`: reimplement the
   generator from its spec with BigInt or another language, no import from `src/`, its own copy of the
   catalog and salt. Read [references/golden-vectors.md](references/golden-vectors.md).
6. **Pin the vectors.** The template rows belong to the template salt and catalog. Before the
   generator first ships, delete each row's `fingerprint` and `summary` (keep `key`, `version` and
   `note`) and set the contract's `salt` to the new salt in decimal. Add rows `{ "key", "version",
   "note" }` (first key, a historical key, year end and new year, leap day), run
   `node scripts/oracles/update-<feature>-vectors.ts` from the repo root, and read the diff. A
   committed row is never refilled: `check-golden-vectors.mjs` fails it (rule `vectors-frozen`; the
   template's seven rows are the one exception). Keep the vectors test and the oracle test from the
   templates.
7. **Store outcomes, not content.** Persist `(key, generatorVersion, outcome)` as
   `examples/daily-focus-outcomes/` shows: the repository port and row mapping in
   `src/data/<feature>/`, the replay in `src/features/<feature>/`. persistence-and-migrations owns the
   table. When the table exists, point the contract's `outcome.file` at the file that declares the
   stored row. Read [references/versioning-and-regeneration.md](references/versioning-and-regeneration.md).
8. **To change a shipped generator**, follow "Shipping a new version, step by step" in
   [references/versioning-and-regeneration.md](references/versioning-and-regeneration.md) and imitate
   `examples/daily-focus-cutover/`: new version, future cutover, new oracle branch, new rows only.
   `check-golden-vectors.mjs` fails a new cutover that starts today or earlier, and any edit of what
   is committed (git `HEAD`): a moved or deleted cutover, a changed salt, a removed generator, a
   changed or deleted vectors row. In CI pass the base branch: `--committed-ref origin/main`.
9. **Test:** `npx jest src/core src/features/<feature> scripts/oracles --ci --selectProjects unit`
   and `npm run typecheck`.
10. **Run the checks:** `node ${CLAUDE_SKILL_DIR}/scripts/check-determinism.mjs .` and
    `node ${CLAUDE_SKILL_DIR}/scripts/check-golden-vectors.mjs .` (in a workspaces repo, once per app,
    with the app folder as the root); fix every `FAIL` line (file, rule, fix) and rerun until both
    print `RESULT: PASS`.
11. **Commit** vectors and contract changes with a `Gate-Change:` trailer (git-commits-and-reporting)
    naming the generator, the version and why; a changed row is never committed.

## Definition of done

- [ ] Every seeded output has a generator that takes its key as an argument and reads no clock,
      ambient randomness or locale data; `clock.today()` is read once at the edge.
- [ ] The core files are the templates, unedited, with their tests pinning the oracle's goldens.
- [ ] No template value is left before the first release: own salt (in code, oracle and contract),
      own catalog, own id check in the generator test, rows refilled by the oracle (the checker's
      `template values still in use` note no longer prints).
- [ ] Each generator is registered with a unique frozen salt, its versions table, vectors file, outcome
      file (or `null`) and regenerate command.
- [ ] Each vectors file was filled by the oracle (not the generator) and covers every version (date
      keys: both sides of each cutover, a year-end/new-year pair, a leap day); no committed row changed
      or vanished (`vectors-frozen`; without git the checker prints a `frozen checks:` note instead).
- [ ] Stored outcomes carry the key and `generatorVersion`, never the generated content.
- [ ] No CI job, hook or gate script runs a regenerate command.
- [ ] The core, generator, vectors and oracle tests pass; `npm run typecheck` is clean.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-golden-vectors.mjs .` prints `RESULT: PASS` (allowed
      while the outcome table does not exist yet:
      `SKIP src/data/<feature>/<feature>-outcome-repository.ts [outcome-version] not yet created (persistence-and-migrations creates it)`;
      an app without generators prints `NOT APPLICABLE: contracts/seeded-generators.json declares no seeded generators`).
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-determinism.mjs .` prints `RESULT: PASS`

## Anti-patterns

- **`new Date().toISOString().slice(0, 10)` inside a generator.** The UTC day, read from a clock: not
  reproducible and wrong for hours every day. Take the key from `clock.today()` as an argument.
- **`CATALOG[dailySeed(key, SALT) % CATALOG.length]`.** Neighbouring days step through the catalog by a
  fixed amount. `drawOne(CATALOG, seedRng(dailySeed(key, SALT)))`.
- **`Math.random()` for a harmless tie-break.** One call and no two devices agree. Draw it from the
  threaded state.
- **Sorting with `localeCompare` before drawing.** iOS, Android and Jest order differently. Compare ids
  with `<`.
- **Fixing a typo in a shipped catalog.** Every past key that drew it changes. Ship version 2 with a
  future cutover.
- **Storing the generated list next to the outcome.** Two sources of truth. Store the version and
  regenerate.
- **Pasting the generator's output into the vectors file, or `const GOLDEN = hashSeed(...)` in a test.**
  The expected value then agrees with every bug. Compute it with the oracle.
- **Deleting a shipped row's values and re-running the update script to make a red test green.** That
  rewrites history users saw. Restore the version that made the row. Refilling is only for rows no
  released build has served, such as the template rows after you set your own salt.
- **`npm run vectors:update` (or `pnpm vectors:update`, or a script that calls it) in `verify`, CI, an
  EAS Workflow or a hook (`simple-git-hooks`, `lint-staged`, husky).** The gate passes by
  construction. Run it by hand and review the diff.
- **Moving a committed cutover to "fix" a date, or editing a committed salt in code and contract
  together.** Both still agree, so only the committed contract shows the rewrite. Append a version with
  a future date instead.
- **Deleting or renaming a shipped generator's entry to get past a red check.** Stored outcomes and
  share codes still point at it. A rename is compared with the committed entry; a removal needs a
  migration, the developer's approval and a `"retired"` entry.
- **A share-code generator that defaults to its newest version.** A version 1 code then opens as a
  version 2 pack. Pass the version the code carries.
- **A helper in `src/utils/` that a generator imports and that reads `Date.now()` or calls `Math.exp`.**
  `check-determinism.mjs` follows the generator's imports into it and names the chain; a package from
  `node_modules` (a library `shuffle`) is caught only by the runtime trap. Pass the value in, and use
  integer tables instead of inexact maths.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/determinism-policy.md](references/determinism-policy.md) | Seeded paths, allowed operations and the spec/Hermes evidence, clocks, randomness, locale, ordering, floats, iOS/Android/Jest table, who enforces what, pitfalls | Workflow step 1, and when a lint or scan rule fires |
| [references/seeded-rng-and-seeds.md](references/seeded-rng-and-seeds.md) | sfc32 and its API, hash + salt + mix, the unmixed-seed trap in numbers, salts, threading, seeded draws, per-index hashing, the UI thread, the goldens and the Python oracle that computed them | Workflow steps 2 and 3 |
| [references/civil-date-keys.md](references/civil-date-keys.md) | DateKey, its integer arithmetic, one day definition, routing by key, the daily seed, pinned facts and their oracle, device behaviour | Workflow steps 2 and 3 |
| [references/golden-vectors.md](references/golden-vectors.md) | The vectors file, canonical JSON v1, coverage, the independent oracle, the fill-only update script, replacing the template rows before the first release, CI verifies, reading a diff, labels over metrics, a Python generator oracle | Workflow steps 5 and 6 |
| [references/versioning-and-regeneration.md](references/versioning-and-regeneration.md) | What to store and where (the outcome layout), replay by version, what counts as a change, the cutover procedure, choosing the date under store releases and OTA, text keys and share codes, retiring a generator | Workflow steps 7 and 8 |
| [references/checkers.md](references/checkers.md) | Both checkers' rules, the contract format, the committed baseline (`--committed-ref` in CI, renames, `"retired"`), layout options, multi-package use, expected SKIP/NOT APPLICABLE lines and notes, troubleshooting | Workflow steps 4 and 10 |
| `templates/src/core/rng/` | `sfc32.ts`, `seeded-draws.ts`, `fingerprint.ts` and their tests (synced, canonical) | Workflow step 2 |
| `templates/src/core/dates/` | `date-key.ts`, `daily-seed.ts`, `generator-versions.ts` and their tests (synced, canonical) | Workflow step 2 |
| `templates/src/features/__FEATURE__/` | The generator scaffold, its test, its vectors file and the vectors test | Workflow steps 3 and 6 |
| `templates/scripts/oracles/` | The BigInt oracle, its test (CI checks the committed rows) and the fill-only update script | Workflow steps 5 and 6 |
| `templates/contracts/seeded-generators.json` | The generator register both checkers read | Workflow step 4 |
| `examples/daily-focus-cutover/` | The Tasks app's daily-focus generator after a version 2 cutover: generator, vectors, oracle, contract | Workflow step 8 |
| `examples/daily-focus-outcomes/` | Storing `(key, generatorVersion, outcome)`: the repository port, the row schema and mapping (`src/data/`), and the replay that regenerates what the user saw (`src/features/`) | Workflow step 7 |
| `examples/starter-pack-share-code/` | A text-keyed generator (share codes `v2:7KQ2`): versions without cutover dates, explicit version on every call, a Python oracle with verify and `--fill` modes, its vectors | Workflow step 3, when the key is not a date |
| [examples/reminder-jitter.ts](examples/reminder-jitter.ts) | Stateless per-id randomness with `hashU32` (reminder offsets) | When each item needs its own stable random value |
| `scripts/check-determinism.mjs` | Runs the core against an independent implementation and the goldens; scans seeded paths and every local file they import | Workflow step 10, and at the end |
| `scripts/check-golden-vectors.mjs` | Runs every registered generator against its vectors with ambient APIs and inexact maths trapped; checks the register, what is committed (generators, salts, cutovers, vectors rows), coverage, oracle, test, outcome, no auto-bless | Workflow step 10, and at the end |
| `scripts/lib/determinism-spec.mjs` | The checkers' BigInt/Date.UTC implementation of the core, the golden table and the Math allow-list | When a golden, a core rule or the allow-list changes |
| `scripts/lib/module-graph.mjs` | The checkers' import reader: what a file loads (static, `import()`, `require()`, type-only), every local file a set of files reaches, and the enclosing workspaces repo of an app folder | When a checker follows imports |
| `scripts/lib/app-modules.mjs` | Shared helper (synced; do not edit here): imports the app's TypeScript for the runtime checks | Never by hand |
| `scripts/lib/source-scan.mjs` | Shared helper (synced; do not edit here): the source scanner (masking, imports, worklet directive) | Never by hand |
| `scripts/check-lib.mjs` | Shared script library (synced; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves both checkers pass the templates and catch every planted bug | After changing a checker, template or example |
| `assets/shared.json` | Declares the synced files (check-lib, helpers, the canonical core) | When adding a shared file |
| `assets/template-placeholders.json` | The placeholder values the template verification uses | When adding a placeholder |
| `tests/bases/app/` | The minimal app the fixtures lay the templates on | When the fixture base changes |
| `tests/fixtures/` | Overlay suites: `good/`, one `bad-*` per rule, `pass-*` and `error-*` cases for both checkers | When adding a checker rule |

## Related skills

- `dates-streaks-and-statistics` - reading today, local midnight, DST, streaks and statistics over date keys.
- `architecture-and-boundaries` - the `ClockPort` and where pure core code lives.
- `persistence-and-migrations` - the outcome table and retiring a generator version.
- `golden-and-snapshot-tests` - data snapshots and pixel goldens; `run-goldens-rebaseline` re-blesses those.
- `unit-and-component-tests` - the Jest projects, fast-check and the pinned `TZ=UTC`.
- `typescript-and-lint-rules` - the `DETERMINISTIC` and `PURE` lint blocks.
- `value-objects-money-and-units` - integers instead of floats for stored values.
- `i18n-strings-and-catalogs` - `Intl` formatting at the edge, never on a seeded path.
- `git-commits-and-reporting` - the `Gate-Change:` trailer for vectors and contract changes.
- `ota-updates` - runtime versions and rollouts that decide when a cutover is safe.
- `game-rules-engine`, `level-generation-and-solvers`, `realtime-game-loop` - seeded games built on this core.

Files in this skill

  • SKILL.md20.9 KB
  • assets/shared.json1.6 KB
  • assets/template-placeholders.json148 B
  • examples/daily-focus-cutover/daily-focus-generator.ts2.8 KB
  • examples/daily-focus-cutover/daily-focus-oracle.ts5.4 KB
  • examples/daily-focus-cutover/daily-focus.vectors.json2.2 KB
  • examples/daily-focus-cutover/seeded-generators.json917 B
  • examples/daily-focus-outcomes/daily-focus-outcome-repository.ts1.3 KB
  • examples/daily-focus-outcomes/daily-focus-outcome-schema.ts1.8 KB
  • examples/daily-focus-outcomes/daily-focus-replay.ts1.1 KB
  • examples/reminder-jitter.ts1.2 KB
  • examples/starter-pack-share-code/starter-pack-generator.ts3.6 KB
  • examples/starter-pack-share-code/starter-pack-oracle.py3.7 KB
  • examples/starter-pack-share-code/starter-pack.vectors.json1.1 KB
  • references/checkers.md22.8 KB
  • references/civil-date-keys.md5.5 KB
  • references/determinism-policy.md11.5 KB
  • references/golden-vectors.md12.6 KB
  • references/seeded-rng-and-seeds.md12.7 KB
  • references/versioning-and-regeneration.md10.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.