# Data / Determinism - one seed, byte-identical regeneration **Purpose + scope.** The reproducibility contract for all generated data. Binding for task 03 (CSV generation) and for every piece of code that produces values which end up inside the corpus or its conversions. The entire evaluation rests on this: benchmark results are only comparable because every format holds EXACTLY the same data, and the study is only reviewable because anyone can regenerate that data bit-for-bit. --- ## 1. The seed - **The global seed is `20260711`.** It lives in `lab_config.yaml` (written by task 01) and is READ from there - never hardcoded in a generation script (see [code-config-yaml.md](code-config-yaml.md)). - Changing the seed is a project-level decision, never a side effect. It invalidates the whole `./out/` tree, every checksum, and every measured result (see [meta-no-assumptions.md](meta-no-assumptions.md)). ## 2. Sanctioned randomness - seeded generators only - The ONLY random sources are `random.Random(seed)` and `numpy.random.default_rng(seed)`, constructed explicitly from the config seed. - Derive per-entity generators deterministically when parallel or out-of-order generation needs them (e.g. `default_rng([seed, batch_index, coupon_index])`) - never from time, never from a global unseeded state. - Forbidden anywhere in generation code: module-level `random.*` calls, `numpy.random.*` legacy global functions, `os.urandom`, `uuid.uuid4`, `hash()`-dependent ordering (Python string hashing is salted per process). ## 3. No wall-clock in the data - Every timestamp inside the corpus is SIMULATED - computed from the lab schedule model - never `datetime.now()` / `time.time()`. - Real wall-clock time may appear only in logs and in measurement records (benchmark wall times in `./out/bench/` are measurements, not corpus data). ## 4. Deterministic everything else Randomness is not the only nondeterminism; all of these are binding: - **Iteration order.** Iterate in a defined order (sorted paths, explicit index loops). Never rely on `os.listdir` / `glob` / `set` / dict-of-set ordering for anything that affects output. Sort directory listings before use. - **Float formatting.** Format floats with an explicit spec (e.g. `f"{x:.6g}"`), chosen once per column family; repr drift between runs or Python versions must not change file bytes. - **Line endings and encoding.** Generated files are UTF-8 with `\n` newlines, on every platform (`open(..., newline="")` for csv writers). - **Parallelism.** If generation is parallelized, each worker's output is deterministic (derived generators, fixed partitioning) and the merge order is fixed. ## 5. The proof - MANIFEST sha256 - `./out/csv/MANIFEST.csv` records sha256 per generated file (spec 03). **Regenerating with the same `lab_config.yaml` MUST reproduce every sha256 exactly.** This is the acceptance test for this whole rule - run generation twice when touching generator code and diff the manifests. - Downstream conversions must be equally deterministic in CONTENT (same rows, same values); byte-identity of database files is not required (SQLite/PostgreSQL internals may differ), which is why their gate is row counts + query-result checksums instead (see [test-pipeline-validation.md](test-pipeline-validation.md)). ## 6. Anti-patterns - **Hardcoding `20260711`** in a script instead of reading the config. - **An unseeded or globally-seeded random call** anywhere in generation. - **`datetime.now()` in corpus values** - simulated schedule only. - **Ordering that depends on the filesystem or hash salting** - sort explicitly. - **"Harmless" float repr changes** - a formatting change is a corpus change; it must be deliberate and re-baselines every checksum. - **Regenerating the corpus to "refresh" it** - identical input config must yield identical output; if bytes changed without a config change, that is a determinism bug to fix, not a new baseline to accept.