What
exitdrill mutate export.json --attachment-root files/ --out lossy/ --seed 7 --drop-relationships 2 --corrupt-attachment 1 --strip-permissions 1 --drop-audit-events 2 --alter-field 1 builds a deterministic adversarial derivative of any synthetic normalized export — the generalisation of scripts/build_directus_lossy_canary.py, which hard-codes one profile's mutations. It keeps entity row counts unchanged (the "same count, different evidence" attack the README highlights), writes a mutation-statement.json listing exactly what it changed as aggregate counts (never record ids or values), and refuses any input that lacks the synthetic-only marker.
Why it matters
Negative controls are how this project proves its gates can fail: the lossy canary exists because a clean fixture alone proves nothing. Today every new fixture needs a hand-written mutation script, so the negative control is the part most likely to be skipped. A seeded mutator makes "this evaluator would have caught it" a one-line check for any synthetic contract, and it is squarely inside the freeze — a test-tooling verb over the existing normalized contract, adding no connector or data category. It also gives the outside-person walkthrough (#51) a second thing to try.
Scope
mutate.py with pure, seeded mutation operators per dimension; output through the same bounded atomic writer; statement document with a schema.
validate accepts the derivative; drill on it must report not_structurally_restorable with the dimensions named in the statement failing.
scripts/build_directus_lossy_canary.py reimplemented on top of the operators, with its committed statement byte-identical (acceptance test).
Out of scope
- Mutating baselines or capture bundles; random or unseeded modes; any mutation that changes entity counts.
Done when
- Same seed and flags → byte-identical derivative and statement; different seed → different derivative, same statement counts.
- Drilling the derivative fails exactly the dimensions the statement names and no others.
- An export without the synthetic marker is refused before any file is written.
- The statement contains no record value from the input (disclosure gate).
Pointers
scripts/build_directus_lossy_canary.py (_mutate), src/exitdrill/loader.py, src/exitdrill/models.py, src/exitdrill/paths.py
- README "Three-minute demo";
docs/decisions/0021-gate-record-value-disclosure-on-the-synthetic-demo.md
Proposed with AI assistance.
What
exitdrill mutate export.json --attachment-root files/ --out lossy/ --seed 7 --drop-relationships 2 --corrupt-attachment 1 --strip-permissions 1 --drop-audit-events 2 --alter-field 1builds a deterministic adversarial derivative of any synthetic normalized export — the generalisation ofscripts/build_directus_lossy_canary.py, which hard-codes one profile's mutations. It keeps entity row counts unchanged (the "same count, different evidence" attack the README highlights), writes amutation-statement.jsonlisting exactly what it changed as aggregate counts (never record ids or values), and refuses any input that lacks the synthetic-only marker.Why it matters
Negative controls are how this project proves its gates can fail: the lossy canary exists because a clean fixture alone proves nothing. Today every new fixture needs a hand-written mutation script, so the negative control is the part most likely to be skipped. A seeded mutator makes "this evaluator would have caught it" a one-line check for any synthetic contract, and it is squarely inside the freeze — a test-tooling verb over the existing normalized contract, adding no connector or data category. It also gives the outside-person walkthrough (#51) a second thing to try.
Scope
mutate.pywith pure, seeded mutation operators per dimension; output through the same bounded atomic writer; statement document with a schema.validateaccepts the derivative;drillon it must reportnot_structurally_restorablewith the dimensions named in the statement failing.scripts/build_directus_lossy_canary.pyreimplemented on top of the operators, with its committed statement byte-identical (acceptance test).Out of scope
Done when
Pointers
scripts/build_directus_lossy_canary.py(_mutate),src/exitdrill/loader.py,src/exitdrill/models.py,src/exitdrill/paths.pydocs/decisions/0021-gate-record-value-disclosure-on-the-synthetic-demo.mdProposed with AI assistance.