The commands behind "Find the rule your auditors are really using"
Find the rule your auditors are really using
tells the refunds story with Claude Code prompts and plain-English results.
This page holds the exact gnd commands behind every number on it. Each block
runs in the project's tests, and the output shown is the output of a real run.
Files on GitHub
refunds/model.orm.yaml: the model and the written rulerefunds/batches/: one week of refunds from each storerefunds/labels.json: the auditors' verdictsrefunds/stand_in_learner.py: the example's stand-in rule learner (not a GND command)- this page's Markdown source
Before you start
Set GND up as the course planner's "Before you start"
shows. In an empty folder, copy the example and check the model. Use a new
empty folder for every run of this page: the commands refuse to overwrite
their own outputs, so a second pass in the same folder stops at step 2
(--out ilp already exists; nothing was written), and later steps refuse the
same way.
python -c "import gnd, pathlib, shutil; shutil.copytree(pathlib.Path(gnd.__file__).parent / 'examples/refunds', '.', ignore=shutil.ignore_patterns('__pycache__'), dirs_exist_ok=True)"
gnd validate model.orm.yaml
1. Score the written rule against the verdicts
gnd measure reads the auditors' verdicts in labels.json and counts, for
each rule, the weeks it fired on. In its words "negative" is a flagged week and
"positive" a clean one. The full measurement is kept in measure.json;
the line shown is the written rule's counts.
fired_on_negative=2/4 fired_on_positive=0/4 silent_on_negative=2/4 silent_on_positive=4/4 vacuous=0/8 unchecked=0/8
The written rule fired on 2 of the 4 flagged weeks and on no clean week. Measuring host labels defines every field.
2. Export the weeks and run a learner
gnd export ilp writes the labelled weeks in a form a rule learner can read.
ILP, inductive logic programming, is the family of learners that build logic
rules from labelled examples.
A learner such as Popper can use that export directly. The example ships a small stand-in learner instead, so the walk runs with nothing to install. It tries 40 simple rules of the form "a week is wrong when it has a refund above (or below) some amount" and, from those that touch no clean week, picks a small set greedily until every flagged week is explained.
gnd export ilp model.orm.yaml --labels labels.json --target neutral --out ilp > /dev/null
python stand_in_learner.py ilp learned
searched 40 rules; 4 hold in some flagged batch and in no clean one
learned: wrong(A) :- f_refund_amount(A,V0,V1), V1 > 500 covers harbor, market, airport, station
V1 is the refund amount. The learner claims the rule covers the four flagged
weeks. That is its claim; the next command checks it.
3. Re-check the learner's claim
gnd import ilp-rules runs the learned rule on every week itself and writes a
gnd evolve proposal only when its own coverage equals the learner's.
gnd import ilp-rules model.orm.yaml learned/learned.clauses.json --labels labels.json --reported-coverage learned/learned.run.json --target neutral --out learned.proposal.json --requested-at 2026-10-26T00:00:00+00:00
IMPORTED 1 learned clauses -> learned.proposal.json
clause 0 constraints[1]: covers 4 (support 4/4), vacuous 0, unchecked 0
A rule covers a week when it fires on it, so the rule covers 4 weeks.
Support is the share of covered weeks that are flagged: 4 of the 4. The
covered weeks are all flagged ones, so the rule fires on 0 of the 4 clean
weeks. The rule becomes constraints[1] once it is applied in step 6.
4. Refuse an overstated claim
With --also-claim north, the stand-in plays a faulty learner that also claims
the rule covers the clean north week.
searched 40 rules; 4 hold in some flagged batch and in no clean one
learned: wrong(A) :- f_refund_amount(A,V0,V1), V1 > 500 covers harbor, market, airport, station
deliberate mistake: also claiming that rule 0 covers north
GND compares the weeks it covers with the weeks the learner reported. They
differ by north, so the import exits 3 and writes no proposal.
gnd import ilp-rules model.orm.yaml faulty/learned.clauses.json --labels labels.json --reported-coverage faulty/learned.run.json --target neutral --out faulty.proposal.json --requested-at 2026-10-26T00:00:00+00:00
clause 0: coverage-mismatch: GND covers [] that the learner did not report, and the learner reports ['north'] that GND does not cover; vacuous [], unchecked []
The ILP bridge defines the export, the import and this refusal.
5. Suggest rules from one clean week
gnd discover reads one population and proposes the rules that hold in it.
Here it looks for two kinds: "each X has exactly one Y" (uniqueness) and
"every X has a Y" (mandatory).
gnd discover model.orm.yaml batches/south.yaml --families uniqueness,mandatory --out discovered.json --report discover-report.json --actor reader --requested-at 2026-10-26T00:00:00+00:00 | grep -E "^(EMITTED|SUPPRESSED)"
Ten rules held. Four more were left out: one is already declared in the model and three hold in any list of facts. Constraint discovery explains the families and the suppression rules.
6. Preview, apply, and check a week again
Both proposals only add rules, so neither is breaking:
gnd evolve preview model.orm.yaml discovered.json --workspace . --records records --out discovered.preview.json | grep breaking
gnd evolve preview model.orm.yaml learned.proposal.json --workspace . --records records --out learned.preview.json | grep breaking
Apply the learned proposal and read the new rule back in the model's own words.
modality=deontic marks it as advisory: breaking it gives a warning, not a
failure.
gnd evolve apply model.orm.yaml learned.proposal.json --preview learned.preview.json --workspace . --records records > /dev/null
grep -o "\[from induced.*" model.verbalization.txt | sed "s/ source=.*//"
[from induced, proposed; modality=deontic] If refund_amount(refund = $v_v0, amount = $v_v1), then $v_v1 is at most 500.
The market week, which the written rule let through, now gets a warning on refund 601 for 750:
ADVISORY (deontic)
[warning] constraints[1]: Guarded comparison consequent evaluated over bindings=3 — violated by bindings: {'$v_v0': '601', '$v_v1': '750'}.
constraints[1] is the new rule. ADVISORY (deontic) heads the warnings from
advisory rules. The written rule is advisory too, and stays quiet here because
750 is not over 1000.
The apply changed the model, so step 1 cannot be repeated on it: labels.json
records the digest of the model it was written for, and gnd measure now
refuses it (label manifest labels.json was written for model
sha256 ..., not this model). To score the new model, give the manifest the
new model's digest, as measuring host labels
describes.
With an assistant, step 6 is the gnd-model-evolve skill's work; the other steps are plain requests that Claude Code carries out with the commands above.