textmachine/backend/internal/pipeline/oracle_test.go

133 lines
5.1 KiB
Go

package pipeline
// oracle_test.go: the cold-run-A oracle, IN THE TREE.
//
// ⛔ WHY IT IS HERE AND NOT IN A REPORT. The pack's own rule is that what must survive a restart lives in
// the repository; the comparison against the polygon's hand-built table (docs/experiments/24-door-to-file.md
// §7) existed only in a written report, so the single number the whole instrument was accepted on — «it
// reproduces the oracle» — could not be re-checked by anyone, ever. The TABLE is committed
// (testdata/oracle-run-a.json); the paid artifacts it is measured against are not, so the test skips when
// they are absent, exactly as the labelled-corpus harness next door does.
//
// ⚠ IT SKIPS UNDER A COPY-BASED WORKFLOW (tmmutate), because standdata.Root walks to the repo root and the
// books tree is not copied with the module. Named rather than implied: this pin is held by the battery and
// not by the mutation gate.
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"textmachine/backend/internal/lang"
"textmachine/backend/internal/membank"
"textmachine/backend/internal/standdata"
"textmachine/backend/internal/store"
)
var runADir = standdata.EnvOr("TM_RUN_A_DIR", standdata.StandFile("gu-zhenren", "door-to-file"))
type oracleTerm struct {
Src string `json:"src"`
BankDst string `json:"bank_dst"`
Population string `json:"population"`
Oracle string `json:"oracle"`
Instrument string `json:"instrument"`
Agrees bool `json:"agrees"`
}
type oracleDoc struct {
Agree int `json:"agree"`
Terms []oracleTerm `json:"terms"`
}
// frozenBank builds the bank as it stood at the mining stop of 04:39, from the projection that IS the
// frozen evidence. The stand database is a DIFFERENT revision — 12 rows were re-consolidated at 04:42 —
// so judging the oracle against it would be judging a different bank.
func frozenBank(t *testing.T) *membank.Bank {
t.Helper()
raw, err := os.ReadFile(filepath.Join(runADir, "bankstop-paid", "project.db.bank.json"))
if err != nil {
t.Skipf("run-A evidence absent (%v) — set TM_RUN_A_DIR to force", err)
}
var doc struct {
Proposed []struct{ Src, Dst string } `json:"proposed"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse the frozen bank projection: %v", err)
}
rows := make([]store.GlossaryEntry, 0, len(doc.Proposed))
for _, p := range doc.Proposed {
rows = append(rows, store.GlossaryEntry{BookID: "runA", Src: p.Src, Dst: p.Dst, Status: "draft", Source: "seed"})
}
if len(rows) == 0 {
t.Fatalf("the frozen bank projection carries no proposals")
}
return membank.MaterializeBank(membank.BankInput{
Rows: rows, TargetStemmer: lang.NewTargetStemmer(lang.TargetChecksFor("ru")),
}, false)
}
// TestTheInstrumentAgainstTheColdRunOracle re-runs the comparison the pack was accepted on, over the same
// shipped units, and pins BOTH halves: which terms agree and which do not. A disagreement is not a failure
// here — three of them are properties this engine documents — but a disagreement that CHANGES is, because
// it means the instrument's verdict moved without anyone deciding that it should.
func TestTheInstrumentAgainstTheColdRunOracle(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("testdata", "oracle-run-a.json"))
if err != nil {
t.Fatalf("the oracle table is part of the repository: %v", err)
}
var want oracleDoc
if err := json.Unmarshal(raw, &want); err != nil {
t.Fatalf("parse the oracle table: %v", err)
}
bank := frozenBank(t) // skips when the paid artifacts are absent
pairs, err := os.ReadFile(filepath.Join(runADir, "pairs.json"))
if err != nil {
t.Skipf("run-A shipped units absent (%v) — set TM_RUN_A_DIR to force", err)
}
var exp struct {
Chunks []struct {
Chapter int `json:"chapter"`
Source string `json:"source"`
FinalText string `json:"final_text"`
} `json:"chunks"`
}
if err := json.Unmarshal(pairs, &exp); err != nil {
t.Fatalf("parse the shipped units: %v", err)
}
c := newConsistencyScan(bank)
for _, u := range exp.Chunks {
c.add(u.Chapter, u.Source, u.FinalText)
}
// The denominator is the bank the scan actually ran over, so the partition below is about the same rows.
rows := make([]store.GlossaryEntry, 0)
for _, term := range want.Terms {
rows = append(rows, store.GlossaryEntry{BookID: "runA", Src: term.Src, Dst: term.BankDst, Status: "draft"})
}
got := c.finish(rows)
verdict := map[string]string{}
for _, term := range got.Terms {
verdict[term.Src] = term.Verdict
}
agree := 0
for _, term := range want.Terms {
v := verdict[term.Src]
if v == "" {
v = "covered"
}
if v != term.Instrument {
t.Errorf("%s: the instrument now says %q where the table records %q (the oracle says %q) — a verdict moved without anyone deciding it should",
term.Src, v, term.Instrument, term.Oracle)
}
if v == term.Oracle {
agree++
}
}
if agree != want.Agree {
t.Errorf("agreement with the hand-built oracle is %d of %d; the table records %d", agree, len(want.Terms), want.Agree)
}
t.Logf("oracle agreement: %d of %d terms (the table's recorded disagreements are documented properties, not defects)", agree, len(want.Terms))
}