Admit a law
Move a candidate equality through the gauntlet into DEFAULT with MACHINE_SHIPPED provenance. The discovery pipeline proposes and ranks candidates; admission is what you do with a SHIP verdict.
1. Get a candidate
.venv/bin/python -m mlopt_discovery.pipeline
.venv/bin/python -m mlopt_discovery.pipeline --holdout select_mul # sanity: re-derive a shipped law
.venv/bin/python -m mlopt_discovery.pipeline --json /tmp/pipeline.jsonOne run does census → propose → verify → measure → rank over the real corpus and emits a ship / no-ship verdict per candidate with the reason. The --holdout arm removes a shipped law and asks the loop to re-derive it — if the pipeline cannot rank a known winner at the top, it is not trustworthy.
2. Check derivability standalone
from mlopt_discovery.verifier import verify_law
res = verify_law(lhs, rhs, rules) # -> LawResult
res.derivable # the two roots share an e-class?
res.direction # which way the witness ran
res.witness_rules # the rules that fired the derivation
res.replayable # the certificate replays standalone?
res.cert # the certificate itselfverify_law interns both sides in a fresh e-graph and saturates under the known laws — the e-graph is the decision procedure. Saturation is bounded by default (expansive and non-library rules are capped), and a bound can only ever refuse, never accept: resource exhaustion reports derivable=False, never a fabricated merge. Or run the module CLI:
.venv/bin/python -m mlopt_discovery.verifier --json /tmp/law.json3. Prove it pays — and that the guard is honest
The pipeline's measure stage already runs the pay audit per candidate: the rule fires alone over every real model (fires, cost delta, lowered sink.verify), every merged fire is audited for well-typedness, and a closure-safety check flags rules that explode saturation. What remains is the adversarial sweep — mlopt_discovery.gap_gen generates bindings inside the guard's false region and tries to falsify the side conditions. This is where candidates die: the sweep killed 212 bad accepts on mul_unsq_nat's first guard before the exact flat-map-unsq guard was synthesized.
4. Emit the admission patch
.venv/bin/python -m mlopt_discovery.pipeline \
--emit-admission <candidate> --out /tmp/admission--emit-admission writes, for a SHIP candidate, the admission artifact: the R(...) source with named check/derive hooks, a generated tests/test_admitted_<law>.py, and admission_<law>.patch — a git apply-able diff showing the exact law-bank insertion. It is a report, never a mutation: review the patch before it lands.
5. Land it
git applythe patch. TheR(...)row lands in the law bank (mlopt_core/laws/tensor.py) —cond/dspeccarry the declarative guard. A verbatimsyn_*minted composite goes throughmlopt_core/laws/minted.pyinstead (its premise-closure records replay at import).- Record the provenance. Add the law's name to
MACHINE_SHIPPEDinmlopt_core/laws/provenance.py—law_provenance(rule)then reports"machine". - Run the whole suite. A change to any default rule changes extraction behavior for every caller —
scripts/gates.sh, not a slice.
Outcome
search(..., rules=DEFAULT) picks the law up for every caller, and the shipped bank's claim holds: every machine-admitted law replayed its certificate and paid on a measured fixture before it shipped. The promotion table and per-law evidence live upstream in project/retros/promoted-laws.md.
See also
- Watch a law get minted — the tutorial version of the loop.
- The gauntlet — why each stage exists.
- The discovery scorecard — the shipped bank's public record.