Skip to content

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

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
OK: model.orm.yaml is a valid GND ORM model

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.

gnd measure model.orm.yaml --labels labels.json --out measure.json | grep fired_on
      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.

python stand_in_learner.py ilp faulty --also-claim north
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)"
EMITTED 10
SUPPRESSED declared=1 spanning-uniqueness=3

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
  breaking: false
  breaking: false

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:

gnd check model.orm.yaml batches/market.yaml | grep -A1 ADVISORY
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.