Enforces reproducible derived content — output every device must compute identically from a key, with no server. The key is injected, never read from a clock; a calendar-day key is a civil date, not an instant, and one day definition serves every comparison; the seed is a specified hash plus a mix step feeding a PRNG you own, never `dart:math`'s `Random`; entropy has exactly one source, so no `DateTime.now()`, no ambient `Random()`, no ordering by `identityHashCode`; content is regenerated from (key, generatorVersion) rather than stored; a shipped generator is frozen and an improvement is a versioned cutover, never an in-place edit that rewrites history; and it is pinned by a committed golden-vector table of fingerprints from an independent oracle, regenerated only by a reviewed command CI verifies but never blesses. Use when generating content from a seed or date, writing or regenerating golden vectors, versioning a generator, or debugging output that differs between devices or runs.
Some output must be a pure function of a key: the daily pick every user compares with their
friends, a procedurally generated layout, a shareable result someone else can verify. It has to come
out identical on every device, in every timezone, on the platform build you ship next year — with no
server to arbitrate. That is a stronger property than "the tests pass", and it needs its own
discipline plus a frozen oracle to keep it.
testing-strategy owns how you test pure logic (seeded fuzz against an independent oracle, injected
Clock). This skill owns what makes the output reproducible in the first place, and the committed
vector table that pins it.
A golden vector is not a golden image. Vectors are data fingerprints for a pure generator,
asserted by dart test with no widget binding; reference PNGs of rendered UI belong to
widget-golden-and-a11y-testing, and re-blessing them is the separate ritual in
run-goldens-rebaseline. The two share only the word "golden" and the rule that a gate must never
regenerate what it checks.
Non-negotiable rules
The key is injected, never read. A generator takes its key as an argument — a date string, an
id, a sequence number. Nothing reachable from it calls DateTime.now(). Wall-clock time enters
the app exactly once, through the injected Clock at the composition root
(value-objects-money-and-units owns that rule). WHY: a generator that reads a clock is
untestable and un-golden-able, and its output cannot be reproduced from a bug report.
A calendar-day key is a civil date, not an instant. "The 4th of July" is a date-only value —
store and pass it as one (an ISO YYYY-MM-DD string or a day serial), never as a DateTime
instant. An event timestamp is still a UTC instant; a day identity is not a moment in time at
all. WHY: an instant makes every read timezone-dependent, so two devices disagree about which
content is "today's".
One day definition, used everywhere it is compared. If content is derived from a UTC day while
a streak, deadline, or "already seen" check uses local midnight, the two disagree for hours every
day near the boundary. Pick local or UTC deliberately, name it once, and route every comparison
through that one definition.
Entropy has exactly one source: a seed you derived. No ambient Random()/Random.secure()
anywhere on the generation path — not even for a "harmless" tiebreak or shuffle. Every draw is an
explicit call on the seeded generator you threaded in, and the order of draws is part of the
contract: reordering two draws changes every output after it.
Derive the seed with a specified hash and a mix step. Hash the key string (FNV-1a-64 is
small, specified and easy to reimplement), XOR in a per-feature and per-mode salt, then pass the
result through an avalanche/mix step (SplitMix64) before drawing from it. WHY: consecutive keys
differ by one byte, and without a mix step that correlation shows up as visibly similar output on
consecutive days — the bug users notice first. See references/seed-derivation.md.
Own the PRNG; do not depend on an unspecified one.dart:math's Random(seed) is reproducible
within a given runtime, but its algorithm is an implementation detail with no documented stability
guarantee across SDK versions or platforms. Output a user will compare with another user's must not
rest on that. SplitMix64 is eight lines; write it, test it against published vectors, and freeze it.
Salt per feature and per mode. Two features seeded from the same date must not draw the same
sequence, or their outputs correlate visibly. One salt constant per feature, frozen forever once
shipped.
Regenerate derived content; never store it. Persist the key, the generatorVersion, and
the outcome (what the user did). The content itself is key → generator → content, rebuildable
forever. WHY: storing it doubles the source of truth and makes history un-migratable. Derived
aggregates are likewise recomputed on read — persistence-drift owns that rule.
A shipped generator is frozen. The moment users have seen output for a key, that output is
what that key means. An improvement ships as a new version with a cutover key, with the old
version kept in the binary to reproduce everything before it. Never edit a shipped generator in
place: it silently rewrites what the past was, and every stored outcome now refers to content that
no longer exists.
Pin the contract with golden vectors. A committed table of (key, params) → fingerprint rows,
plus whatever derived metrics the UI shows, asserted exactly. Store a fingerprint (a stable
hash of the canonical serialization), not the whole artifact, so the table stays small and a real
change is a one-line diff a reviewer can actually see. == for integers and fingerprints;
tolerance only for a deliberately calibrated float.
Compute every expected value from an independent oracle. A fixture generated by the
implementation under test enshrines that implementation's bugs — including the one you are about
to introduce. Derive vector rows from a separate, simpler, slower implementation, or by hand.
(testing-strategy owns the independent-oracle principle for fuzz loops; this is its frozen-table
half.)
Regeneration is explicit, reviewed, and never automatic. Vectors are refreshed only by a
deliberate local command whose diff a human reads in the pull request. CI verifies and never
blesses — a pipeline that regenerates its own expectations is a gate that checks nothing, the
same failure as running --update-goldens in CI.
Measured, then labeled — never marketed as a proof. Where a generated artifact carries a
derived label (a difficulty, a quality tier, a size class), the metric is computed and the label
is a frozen threshold table over it. Assert that the mapping is monotonic and that the
thresholds have not moved; do not describe a calibration as proven, and never re-label content
that already shipped.
Deriving the seed
/// FNV-1a-64 over the key's UTF-8 bytes. Small, specified, trivially portable.
int fnv1a64(String key) {
var hash = 0xcbf29ce484222325;
for (final byte in utf8.encode(key)) {
hash = (hash ^ byte) * 0x100000001b3; // 64-bit wrap-around is intended
}
return hash;
}
/// The one entropy source. SplitMix64 — eight lines, published test vectors,
/// and yours forever, unlike an SDK-internal generator.
class SeededRng {
SeededRng(this._state);
int _state;
int nextInt64() {
_state += 0x9e3779b97f4a7c15;
var z = _state;
z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >>> 27)) * 0x94d049bb133111eb;
return z ^ (z >>> 31);
}
/// Uniform in [0, max). Draw order is part of the contract — never reorder.
int nextInt(int max) => nextInt64().toUnsigned(63) % max;
}
/// The only place the calendar meets a generator. `isoDay` is a civil date
/// ("2026-07-04") supplied by the caller — the generator never reads a clock.
SeededRng seedFor(String isoDay, {required int featureSalt, int modeSalt = 0}) =>
SeededRng(fnv1a64(isoDay) ^ featureSalt ^ modeSalt);
⚠️ If the app also builds for web, 64-bit integer arithmetic does not behave this way — Dart
integers compile to JavaScript doubles there. Either restrict this code to native targets or
implement the hash and PRNG in 32-bit halves. See references/seed-derivation.md.
The golden vector table
// One row per pinned key. Computed ONCE from an independent oracle (rule 11) and
// refreshed only by `dart run tool/update_vectors.dart` (rule 12).
const dailyPickVectors = <DailyPickVector>[
DailyPickVector('2026-01-01', 0x9f3ac1e0, 12, 'year boundary'),
DailyPickVector('2026-07-04', 0x2b7708d4, 3, 'mid-year'),
DailyPickVector('2026-12-31', 0x84ce11a9, 7, 'year end'),
];
test('the generator reproduces every frozen vector', () {
for (final v in dailyPickVectors) {
final pick = generateDailyPick(v.isoDay);
expect(pick.fingerprint(), equals(v.fingerprint), reason: v.note);
expect(pick.items.length, equals(v.itemCount), reason: v.note);
}
});
Cover the boundaries a bug would hide behind: the first and last key you support, a leap day, a
month/year rollover, and at least one historical key so a regression that rewrites the past fails
loudly.
Anti-patterns
DateTime.now() inside a generator or a seed helper — the output stops being reproducible and
cannot be regenerated from a bug report.
A DateTime instant used as a day identity — every read becomes timezone-dependent.
Deriving content from a UTC day while a boundary check uses local midnight — a daily off-by-one
for hours a day, and the hardest kind of bug to see from your own timezone.
An ambient Random() for a "harmless" tiebreak — one call and the whole contract is gone.
Reordering draws during a refactor — every output after the swap changes; draw order is a contract.
Seeding straight from the raw hash with no mix step — consecutive days come out visibly similar.
Reusing one salt for two features — their outputs correlate.
Depending on Random(seed), identityHashCode, or hash-set iteration order for anything a user
sees — no documented stability guarantee; sort by an explicit key before drawing.
Storing the generated artifact — persist (key, generatorVersion, outcome) and regenerate.
Editing a shipped generator in place — it rewrites what past keys meant; ship a versioned cutover.
Vectors generated by the implementation under test — they enshrine its bugs.
Storing whole artifacts in the vector table — unreviewable diffs; fingerprint them.
Auto-regenerating vectors in CI — the gate then verifies nothing.
Calling a calibrated label "proven" — the metric is measured; the label is a frozen threshold.
Definition of done
The generator takes its key as an argument; nothing on its path reads a clock or ambient random.
A day key is a civil date, and one day definition is used by every comparison that touches it.
The seed is hash(key) ^ featureSalt ^ modeSalt through a mix step, into a PRNG the repo owns.
Salts are frozen constants, one per feature/mode.
Content is regenerated from (key, generatorVersion); only the key, version and outcome persist.
generatorVersion is stored with every outcome; a generator change ships as a new version with
a cutover, and the previous version stays able to reproduce history.
A committed vector table pins fingerprints plus any user-visible derived metric, covering the
range boundaries, a leap day, and a historical key.
Every expected value came from an independent oracle, not from the implementation under test.
Regeneration is a reviewed local command; CI only verifies.
A determinism test asserts two runs from the same key are byte-identical, so any reintroduced
ambient randomness fails the build.
scripts/check-determinism-bans.sh is green over the generator's package/directory.
Any derived label is a frozen threshold table with a monotonicity test; shipped content is never
re-labeled.
Related skills
See testing-strategy for seeded fuzz loops, the independent-oracle principle, and injecting
Clock — this skill's tests are that shape.
See value-objects-money-and-units for canonical storage and the injected Clock seam.
See persistence-drift for storing the key/version/outcome and recomputing derived aggregates.
See project-structure-and-packages for keeping the generator in a Flutter-free package where these
bans are mechanically enforceable.
See widget-golden-and-a11y-testing for reference images of rendered UI, and
run-goldens-rebaseline for the ritual that re-blesses them — a different tier from these vectors.
See ci-pipeline-and-gates for running the vector check and the ban grep as gates.
See dart3-idioms-and-coding-standards for the immutable value types the generator returns.
1---2name: seeded-determinism-and-golden-vectors3description: Enforces reproducible derived content — output every device must compute identically from a key, with no server. The key is injected, never read from a clock; a calendar-day key is a civil date, not an instant, and one day definition serves every comparison; the seed is a specified hash plus a mix step feeding a PRNG you own, never `dart:math`'s `Random`; entropy has exactly one source, so no `DateTime.now()`, no ambient `Random()`, no ordering by `identityHashCode`; content is regenerated from (key, generatorVersion) rather than stored; a shipped generator is frozen and an improvement is a versioned cutover, never an in-place edit that rewrites history; and it is pinned by a committed golden-vector table of fingerprints from an independent oracle, regenerated only by a reviewed command CI verifies but never blesses. Use when generating content from a seed or date, writing or regenerating golden vectors, versioning a generator, or debugging output that differs between devices or runs.4---56# Seeded determinism and golden vectors78Some output must be a **pure function of a key**: the daily pick every user compares with their9friends, a procedurally generated layout, a shareable result someone else can verify. It has to come10out identical on every device, in every timezone, on the platform build you ship next year — with no11server to arbitrate. That is a stronger property than "the tests pass", and it needs its own12discipline plus a frozen oracle to keep it.1314`testing-strategy` owns *how* you test pure logic (seeded fuzz against an independent oracle, injected15`Clock`). This skill owns what makes the output reproducible in the first place, and the committed16vector table that pins it.1718**A golden *vector* is not a golden *image*.** Vectors are data fingerprints for a pure generator,19asserted by `dart test` with no widget binding; reference PNGs of rendered UI belong to20`widget-golden-and-a11y-testing`, and re-blessing them is the separate ritual in21`run-goldens-rebaseline`. The two share only the word "golden" and the rule that a gate must never22regenerate what it checks.2324## Non-negotiable rules25261. **The key is injected, never read.** A generator takes its key as an argument — a date string, an27 id, a sequence number. Nothing reachable from it calls `DateTime.now()`. Wall-clock time enters28 the app exactly once, through the injected `Clock` at the composition root29 (`value-objects-money-and-units` owns that rule). WHY: a generator that reads a clock is30 untestable and un-golden-able, and its output cannot be reproduced from a bug report.31322. **A calendar-day key is a civil date, not an instant.** "The 4th of July" is a date-only value —33 store and pass it as one (an ISO `YYYY-MM-DD` string or a day serial), never as a `DateTime`34 instant. An *event timestamp* is still a UTC instant; a *day identity* is not a moment in time at35 all. WHY: an instant makes every read timezone-dependent, so two devices disagree about which36 content is "today's".37383. **One day definition, used everywhere it is compared.** If content is derived from a UTC day while39 a streak, deadline, or "already seen" check uses local midnight, the two disagree for hours every40 day near the boundary. Pick local or UTC deliberately, name it once, and route every comparison41 through that one definition.42434. **Entropy has exactly one source: a seed you derived.** No ambient `Random()`/`Random.secure()`44 anywhere on the generation path — not even for a "harmless" tiebreak or shuffle. Every draw is an45 explicit call on the seeded generator you threaded in, and the **order of draws is part of the46 contract**: reordering two draws changes every output after it.47485. **Derive the seed with a specified hash *and* a mix step.** Hash the key string (FNV-1a-64 is49 small, specified and easy to reimplement), XOR in a per-feature and per-mode salt, then pass the50 result through an avalanche/mix step (SplitMix64) before drawing from it. WHY: consecutive keys51 differ by one byte, and without a mix step that correlation shows up as visibly similar output on52 consecutive days — the bug users notice first. See `references/seed-derivation.md`.53546. **Own the PRNG; do not depend on an unspecified one.** `dart:math`'s `Random(seed)` is reproducible55 within a given runtime, but its algorithm is an implementation detail with no documented stability56 guarantee across SDK versions or platforms. Output a user will compare with another user's must not57 rest on that. SplitMix64 is eight lines; write it, test it against published vectors, and freeze it.58597. **Salt per feature and per mode.** Two features seeded from the same date must not draw the same60 sequence, or their outputs correlate visibly. One salt constant per feature, frozen forever once61 shipped.62638. **Regenerate derived content; never store it.** Persist the **key**, the **generatorVersion**, and64 the **outcome** (what the user did). The content itself is `key → generator → content`, rebuildable65 forever. WHY: storing it doubles the source of truth and makes history un-migratable. Derived66 *aggregates* are likewise recomputed on read — `persistence-drift` owns that rule.67689. **A shipped generator is frozen.** The moment users have seen output for a key, that output *is*69 what that key means. An improvement ships as a **new version with a cutover key**, with the old70 version kept in the binary to reproduce everything before it. Never edit a shipped generator in71 place: it silently rewrites what the past was, and every stored outcome now refers to content that72 no longer exists.737410. **Pin the contract with golden vectors.** A committed table of `(key, params) → fingerprint` rows,75 plus whatever derived metrics the UI shows, asserted exactly. Store a **fingerprint** (a stable76 hash of the canonical serialization), not the whole artifact, so the table stays small and a real77 change is a one-line diff a reviewer can actually see. `==` for integers and fingerprints;78 tolerance only for a deliberately calibrated float.798011. **Compute every expected value from an independent oracle.** A fixture generated by the81 implementation under test enshrines that implementation's bugs — including the one you are about82 to introduce. Derive vector rows from a separate, simpler, slower implementation, or by hand.83 (`testing-strategy` owns the independent-oracle principle for fuzz loops; this is its frozen-table84 half.)858612. **Regeneration is explicit, reviewed, and never automatic.** Vectors are refreshed only by a87 deliberate local command whose diff a human reads in the pull request. **CI verifies and never88 blesses** — a pipeline that regenerates its own expectations is a gate that checks nothing, the89 same failure as running `--update-goldens` in CI.909113. **Measured, then labeled — never marketed as a proof.** Where a generated artifact carries a92 derived label (a difficulty, a quality tier, a size class), the metric is computed and the label93 is a **frozen threshold table** over it. Assert that the mapping is monotonic and that the94 thresholds have not moved; do not describe a calibration as proven, and never re-label content95 that already shipped.9697## Deriving the seed9899```dart100/// FNV-1a-64 over the key's UTF-8 bytes. Small, specified, trivially portable.101int fnv1a64(String key) {102 var hash = 0xcbf29ce484222325;103 for (final byte in utf8.encode(key)) {104 hash = (hash ^ byte) * 0x100000001b3; // 64-bit wrap-around is intended105 }106 return hash;107}108109/// The one entropy source. SplitMix64 — eight lines, published test vectors,110/// and yours forever, unlike an SDK-internal generator.111class SeededRng {112 SeededRng(this._state);113 int _state;114115 int nextInt64() {116 _state += 0x9e3779b97f4a7c15;117 var z = _state;118 z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9;119 z = (z ^ (z >>> 27)) * 0x94d049bb133111eb;120 return z ^ (z >>> 31);121 }122123 /// Uniform in [0, max). Draw order is part of the contract — never reorder.124 int nextInt(int max) => nextInt64().toUnsigned(63) % max;125}126127/// The only place the calendar meets a generator. `isoDay` is a civil date128/// ("2026-07-04") supplied by the caller — the generator never reads a clock.129SeededRng seedFor(String isoDay, {required int featureSalt, int modeSalt = 0}) =>130 SeededRng(fnv1a64(isoDay) ^ featureSalt ^ modeSalt);131```132133⚠️ **If the app also builds for web, 64-bit integer arithmetic does not behave this way** — Dart134integers compile to JavaScript doubles there. Either restrict this code to native targets or135implement the hash and PRNG in 32-bit halves. See `references/seed-derivation.md`.136137## The golden vector table138139```dart140// One row per pinned key. Computed ONCE from an independent oracle (rule 11) and141// refreshed only by `dart run tool/update_vectors.dart` (rule 12).142const dailyPickVectors = <DailyPickVector>[143 DailyPickVector('2026-01-01', 0x9f3ac1e0, 12, 'year boundary'),144 DailyPickVector('2026-07-04', 0x2b7708d4, 3, 'mid-year'),145 DailyPickVector('2026-12-31', 0x84ce11a9, 7, 'year end'),146];147148test('the generator reproduces every frozen vector', () {149 for (final v in dailyPickVectors) {150 final pick = generateDailyPick(v.isoDay);151 expect(pick.fingerprint(), equals(v.fingerprint), reason: v.note);152 expect(pick.items.length, equals(v.itemCount), reason: v.note);153 }154});155```156157Cover the boundaries a bug would hide behind: the first and last key you support, a leap day, a158month/year rollover, and at least one *historical* key so a regression that rewrites the past fails159loudly.160161## Anti-patterns162163- **`DateTime.now()` inside a generator or a seed helper** — the output stops being reproducible and164 cannot be regenerated from a bug report.165- **A `DateTime` instant used as a day identity** — every read becomes timezone-dependent.166- **Deriving content from a UTC day while a boundary check uses local midnight** — a daily off-by-one167 for hours a day, and the hardest kind of bug to see from your own timezone.168- **An ambient `Random()` for a "harmless" tiebreak** — one call and the whole contract is gone.169- **Reordering draws** during a refactor — every output after the swap changes; draw order is a contract.170- **Seeding straight from the raw hash with no mix step** — consecutive days come out visibly similar.171- **Reusing one salt for two features** — their outputs correlate.172- **Depending on `Random(seed)`, `identityHashCode`, or hash-set iteration order** for anything a user173 sees — no documented stability guarantee; sort by an explicit key before drawing.174- **Storing the generated artifact** — persist `(key, generatorVersion, outcome)` and regenerate.175- **Editing a shipped generator in place** — it rewrites what past keys meant; ship a versioned cutover.176- **Vectors generated by the implementation under test** — they enshrine its bugs.177- **Storing whole artifacts in the vector table** — unreviewable diffs; fingerprint them.178- **Auto-regenerating vectors in CI** — the gate then verifies nothing.179- **Calling a calibrated label "proven"** — the metric is measured; the label is a frozen threshold.180181## Definition of done182183- [ ] The generator takes its key as an argument; nothing on its path reads a clock or ambient random.184- [ ] A day key is a civil date, and one day definition is used by every comparison that touches it.185- [ ] The seed is `hash(key) ^ featureSalt ^ modeSalt` through a mix step, into a PRNG the repo owns.186- [ ] Salts are frozen constants, one per feature/mode.187- [ ] Content is regenerated from `(key, generatorVersion)`; only the key, version and outcome persist.188- [ ] `generatorVersion` is stored with every outcome; a generator change ships as a new version with189 a cutover, and the previous version stays able to reproduce history.190- [ ] A committed vector table pins fingerprints plus any user-visible derived metric, covering the191 range boundaries, a leap day, and a historical key.192- [ ] Every expected value came from an independent oracle, not from the implementation under test.193- [ ] Regeneration is a reviewed local command; CI only verifies.194- [ ] A determinism test asserts two runs from the same key are byte-identical, so any reintroduced195 ambient randomness fails the build.196- [ ] `scripts/check-determinism-bans.sh` is green over the generator's package/directory.197- [ ] Any derived label is a frozen threshold table with a monotonicity test; shipped content is never198 re-labeled.199200## Related skills201202- See `testing-strategy` for seeded fuzz loops, the independent-oracle principle, and injecting203 `Clock` — this skill's tests are that shape.204- See `value-objects-money-and-units` for canonical storage and the injected `Clock` seam.205- See `persistence-drift` for storing the key/version/outcome and recomputing derived aggregates.206- See `project-structure-and-packages` for keeping the generator in a Flutter-free package where these207 bans are mechanically enforceable.208- See `widget-golden-and-a11y-testing` for reference *images* of rendered UI, and209 `run-goldens-rebaseline` for the ritual that re-blesses them — a different tier from these vectors.210- See `ci-pipeline-and-gates` for running the vector check and the ban grep as gates.211- See `dart3-idioms-and-coding-standards` for the immutable value types the generator returns.212213## References214215- Dart API — `dart:math` `Random` (seeded construction): https://api.dart.dev/stable/dart-math/Random-class.html216- Dart — numbers on the web (`int` as JS double, `>>>`, overflow): https://dart.dev/guides/language/numbers217- Dart API — `identityHashCode`: https://api.dart.dev/stable/dart-core/identityHashCode.html218- FNV hash specification: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv219- SplitMix64 (Steele, Lea, Flood — "Fast splittable pseudorandom number generators"): https://doi.org/10.1145/2660193.2660195220- `package:test`: https://pub.dev/packages/test
Run npx skillmds@latest add zakariaf/seeded-determinism-and-golden-vectors in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Enforces reproducible derived content — output every device must compute identically from a key, with no server. The key is injected, never read from a clock; a calendar-day key is a civil date, not an instant, and one day definition serves every comparison; the seed is a specified hash plus a mix step feeding a PRNG you own, never `dart:math`'s `Random`; entropy has exactly one source, so no `DateTime.now()`, no ambient `Random()`, no ordering by `identityHashCode`; content is regenerated from (key, generatorVersion) rather than stored; a shipped generator is frozen and an improvement is a versioned cutover, never an in-place edit that rewrites history; and it is pinned by a committed golden-vector table of fingerprints from an independent oracle, regenerated only by a reviewed command CI verifies but never blesses. Use when generating content from a seed or date, writing or regenerating golden vectors, versioning a generator, or debugging output that differs between devices or runs. It is listed under Productivity on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
zakariaf (@zakariaf) published this skill. Their other Agent Skills are listed on their SkillMD profile.