Close the loop's own false pins and the twins of the money hole: a preflight pinned by a list is not pinned, and a catalogue entry can rot three ways rather than one

This commit is contained in:
heaven 2026-09-05 03:20:37 +03:00
parent 87d800f720
commit 74ffe4f0a5
9 changed files with 828 additions and 39 deletions

View file

@ -155,7 +155,15 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
// before each mutation. Belt and braces on purpose: the per-mutation restore verifies itself too, but
// a restore that verifies against the wrong baseline verifies nothing — which is exactly how this
// tool failed the first time it was used.
//
// ⚠ A FILE THIS PASS CANNOT READ IS THAT ENTRY'S ROT, NOT THE RUN'S DEATH. It used to `return` here,
// and that quietly undid the whole point of making a moved ANCHOR per-entry: a renamed or deleted file
// is the commonest way a catalogue goes stale, and one of them stopped the run BEFORE entry one —
// taking every healthy entry behind it down unseen, `-id` runs included, because this loop walks the
// whole catalogue and not the selection. Measured: a catalogue with one such entry printed
// «fingerprint …: no such file or directory» and ran nothing at all.
baseline := map[string]string{}
rottedFile := map[string]string{} // mutation id → why its file could not be fingerprinted
for _, m := range all {
for _, e := range m.Edits {
path := filepath.Join(root, e.File)
@ -164,7 +172,11 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
}
body, rerr := os.ReadFile(path)
if rerr != nil {
return fmt.Errorf("fingerprint %s: %w", path, rerr)
// Recorded against the ENTRY, so the run reports it in place and still ends non-zero. The
// file is deliberately left out of the baseline: nothing may be measured against a file
// this pass never saw.
rottedFile[m.ID] = fmt.Sprintf("%s: %v (the file moved or was deleted under the catalogue)", m.ID, rerr)
continue
}
baseline[path] = hash(body)
}
@ -190,6 +202,7 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
// source is byte-identical afterwards) was reported RED and the gate exited 0. A baseline is what
// makes a later red ATTRIBUTABLE.
green := map[string]bool{}
rottedPkg := map[string]string{}
for _, m := range all {
if len(want) > 0 && !want[m.ID] {
continue
@ -201,6 +214,16 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
base := exec.Command("go", "test", m.Package, "-count=1", fmt.Sprintf("-timeout=%s", timeout))
base.Dir = root
if out, berr := base.CombinedOutput(); berr != nil {
// ⚠ A PACKAGE PATH THAT NO LONGER RESOLVES IS ROT, and it is the third way a catalogue goes
// stale after a moved anchor and a moved file. It used to `return` here, so one renamed package
// stopped the run before entry one and took every healthy entry with it — the same shape the
// other two were fixed out of. A package that resolves and is genuinely RED still stops the
// run: a verdict measured against a package that was already failing is unattributable, which
// is the whole reason this baseline exists.
if setupFailed(out) {
rottedPkg[m.Package] = fmt.Sprintf("package %s does not resolve (it was renamed, moved or deleted under the catalogue)", m.Package)
continue
}
return fmt.Errorf("%s is NOT GREEN before any mutation is planted — every verdict about it would be unattributable:\n%s",
m.Package, tailOf(out))
}
@ -212,6 +235,19 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
if len(want) > 0 && !want[m.ID] {
continue
}
if why, dead := rottedPkg[m.Package]; dead {
ran++
fmt.Printf("ROTTED %-28s %s\n", m.ID, why)
survived = append(survived, m.ID+" (package moved)")
continue
}
if why, dead := rottedFile[m.ID]; dead {
// Reported in place, before anything is planted: the entry guards nothing, and the run goes on.
ran++
fmt.Printf("ROTTED %-28s %s\n", m.ID, why)
survived = append(survived, m.ID+" (file moved)")
continue
}
if err := pristine("before " + m.ID); err != nil {
return err
}
@ -226,7 +262,10 @@ func run(root, catalog, ids string, timeout time.Duration, logs string) error {
// Counted as an unexpected outcome, exactly like NOTHING: an entry whose anchor moved guards
// nothing, and the run must still end non-zero. What it no longer does is stop the run.
fmt.Printf("ROTTED %-28s %s\n", m.ID, detail)
survived = append(survived, m.ID+" (anchor moved)")
// The summary label stays generic: the DETAIL line above already says which kind of rot this
// was, and a label naming one kind for all of them would be the same over-claim in miniature —
// «anchor moved» printed for an entry whose run filter had been renamed.
survived = append(survived, m.ID+" (rotted)")
case verdict == inconclusive:
// NOT a red. The package did not build, or the run died before a single test reported — so
// nothing was judged and the entry proves nothing. Counted as an unexpected outcome on
@ -374,13 +413,12 @@ func one(root string, m Mutation, timeout time.Duration, logs string) (v verdict
path := filepath.Join(root, file)
body, rerr := os.ReadFile(path)
if rerr != nil {
// ROT, not a run failure. ⚠ AND IT DOES NOT CLOSE THE MOVED-FILE CASE, which is what a first
// draft of this comment claimed: the fingerprint pass above reads every file the catalogue names
// BEFORE any entry runs, so a renamed or deleted file still aborts the whole run there, ahead of
// this line. Measured: a catalogue with one entry naming a missing file printed
// «fingerprint …: no such file or directory» and ran nothing, the healthy entry behind it
// included. What this branch does cover is the narrow race — a file that disappears BETWEEN the
// fingerprint pass and this read. The moved-file abort is a live gap with a backlog row.
// ROT, not a run failure. The moved-file case itself is caught EARLIER, by the fingerprint pass,
// which records it against the entry and lets the run continue; what this branch covers is the
// narrow race in between — a file that disappears after that pass and before this read.
// ⚠ An earlier version of this comment said the moved-file case "still aborts the whole run".
// That was true when it was written and false one edit later, and it is the exact class of
// defect this tool exists to find: a sentence that outlived the code it described.
return rotted, fmt.Sprintf("%s: %v (the file moved or was deleted under the catalogue)", m.ID, rerr), nil
}
mu.Lock()
@ -420,6 +458,15 @@ func one(root string, m Mutation, timeout time.Duration, logs string) (v verdict
return survivedTests, "", fmt.Errorf("%s: write the log: %w", m.ID, werr)
}
}
// ⚠ A `run` FILTER THAT MATCHES NOTHING IS ROT, NOT A SURVIVOR. `go test -run TestRenamedAway` runs
// zero tests and exits 0, so a renamed pin turned every entry that names it green forever: an
// expect-RED entry printed SURVIVED (a finding that is really a NOTHING — no test was judged), and an
// expect-`survives` entry printed exactly what the catalogue records and kept the whole gate at exit 0.
// The catalogue has entries with run filters precisely so a red is attributable to ONE pin, which is
// what makes this failure quiet: the narrower the filter, the more a rename costs.
if strings.Contains(string(out), "no tests to run") {
return rotted, fmt.Sprintf("%s: the run filter %q matched no test — it was renamed or removed, and this entry has been asserting nothing", m.ID, m.Run), nil
}
if terr == nil {
return survivedTests, "", nil
}
@ -445,3 +492,14 @@ func hash(b []byte) string {
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:])
}
// setupFailed reports whether `go test` refused the PACKAGE PATTERN itself rather than running anything —
// the package was renamed, moved or deleted under the catalogue. Distinguishable from a genuine red in the
// output, and it must be, because the two get opposite treatment: rot is recorded per entry and the run
// goes on, a real red stops everything.
func setupFailed(out []byte) bool {
s := string(out)
return strings.Contains(s, "[setup failed]") ||
strings.Contains(s, "directory not found") ||
strings.Contains(s, "matched no packages")
}

View file

@ -1322,7 +1322,7 @@
},
{
"id": "WB15-screen-floor-slides-inside-its-interval",
"why": "the threshold is ratified as a CONTRACT, not a tuning knob (D39.92 п.1а), and it was pinned only to an INTERVAL — 0.45 is still inside the corpus's empty band, so the slide passed everything AND moved no version, which would silently re-verdict a resumed chunk under a threshold nobody re-billed for. langscreen.Version is now derived from the constants, so the snapshot moves by construction",
"why": "a threshold ratified as a CONTRACT (D39.92 п.1а) was pinned only to an INTERVAL — 0.45 is still inside the corpus's empty band — so a slide re-verdicted a resumed chunk under a threshold nobody re-billed for. Version is derived from the constants, so THIS edit moves the snapshot and the golden catches it. ⚠ THAT IS THE WHOLE OF WHAT THIS ENTRY PROVES: the golden sees a slide, and it CANNOT see a literal that stops tracking the constants — under `literal + slide` the string is byte-identical, the golden does not move and this entry survives. The derivation itself is WB36's, in the langscreen package. An earlier `why` here claimed «the snapshot moves by construction» and read as a guarantee against both",
"package": "./internal/pipeline/",
"run": "TestGoldenDeterminism",
"edits": [
@ -1763,5 +1763,233 @@
"replace": "\t\t\tsignedSrc[p.entry.src] = true"
}
]
},
{
"id": "WB51-flag-reason-declared-elsewhere",
"why": "every FlagReason the engine declares must be RANKED; the exhaustiveness test used to read one file, so a reason declared anywhere else fell to the unknown bucket while the test still said «every declared reason is ranked»",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/status.go",
"find": "const severityUnknown = 8\n",
"replace": "const severityUnknown = 8\n\n// planted\nconst FlagPlantedElsewhere FlagReason = \"planted_elsewhere\"\n"
}
]
},
{
"id": "WB52-builder-hardcodes-the-target",
"why": "the classify input's target must come from the BOOK; a hard-coded target makes every book judged as the shipping pair, and no runner fixture translates into anything but Russian, so nothing else can see it",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/chunkrun.go",
"find": "\t\tTargetLang: r.Book.TargetLang,\n",
"replace": "\t\tTargetLang: \"ru\",\n"
}
]
},
{
"id": "WB53-builder-hardcodes-target-scripts",
"why": "the target's scripts must come from the BOOK's target: hard-coded, a →ja book is screened against Cyrillic and every correct chapter is off-target",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/chunkrun.go",
"find": "\t\tTargetScripts: lang.LangScripts(r.Book.TargetLang),\n",
"replace": "\t\tTargetScripts: lang.LangScripts(\"ru\"),\n"
}
]
},
{
"id": "WB54-builder-hardcodes-source-scripts",
"why": "the source's scripts must come from the RUN's compiled checkers: hard-coded to zh, the echo rule measures the wrong script for every other source language",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/chunkrun.go",
"find": "\t\tSourceScripts: r.checkers.SourceScripts(),\n",
"replace": "\t\tSourceScripts: lang.LangScripts(\"zh\"),\n"
}
]
},
{
"id": "WB56-labelled-preflight-skips-gates",
"why": "the LABELLED branch of CheckKeys must demand the keys of the models the gates call: it was pinned only by the reachable LIST, which is exactly the thing «a pin on the list is not a pin on the preflight» rejects",
"package": "./internal/config/",
"edits": [
{
"file": "internal/config/models.go",
"find": "\t\tfor _, mdl := range pipe.ReachableModels() {\n\t\t\tneeded[mdl] = struct{}{}\n\t\t}\n\t\treturn m.checkKeysFor(needed)",
"replace": "\t\tfor _, st := range pipe.Stages {\n\t\t\tneeded[st.ResolvedModel] = struct{}{}\n\t\t}\n\t\treturn m.checkKeysFor(needed)"
}
]
},
{
"id": "WB57-banknote-warning-loses-the-pack-arm",
"why": "the banknote warning fires when EITHER mining input is missing; keyed on the contrast artifact alone, a book with a pack and no contrast pays for a term table nobody reads and is told nothing",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/runner.go",
"find": "(r.pack == nil || r.Pipeline.Mining.ContrastPath == \"\")",
"replace": "(r.Pipeline.Mining.ContrastPath == \"\")"
}
]
},
{
"id": "WB58-banknote-warning-loses-the-contrast-arm",
"why": "the mirror arm: keyed on the pack alone, a book with a contrast artifact and no langpack pays for the same unread table in silence",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/runner.go",
"find": "(r.pack == nil || r.Pipeline.Mining.ContrastPath == \"\")",
"replace": "(r.pack == nil)"
}
]
},
{
"id": "WB59-banknote-warning-fires-on-a-read-open",
"why": "a $0 read path buys nothing, so warning there teaches an operator to ignore the line on the run where it costs money",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/runner.go",
"find": "\tif forWrite && r.Pipeline.Gates.Banknote.Enabled && (",
"replace": "\tif r.Pipeline.Gates.Banknote.Enabled && ("
}
]
},
{
"id": "WB60-volume-report-decided-after-reconcile",
"why": "the volume report is decided BEFORE reconcile trues the counters up: decided after, a run whose only work outside its grant was a carried unit that came back FLAGGED has Carried==0 and reports nothing at all — the one run that most needs the disclosure goes silent. ⚠ Run-filtered: this planting also strips the report from the DRAFT-ONLY path, which three neighbouring tests catch for a different reason (they lose the report entirely, not out of order). Unfiltered it would go red without proving anything about this pin. WB64 is the faithful both-paths form",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/waverun.go",
"find": "\tif scope.bound() {\n\t\tres.Volume = &scope.stop\n",
"replace": "\tif scope.bound() {\n"
},
{
"file": "internal/pipeline/waverun.go",
"find": "\tscope.reconcile(res.Chunks)\n\tr.Log.InfoContext(ctx, \"book run finished\", \"book\"",
"replace": "\tscope.reconcile(res.Chunks)\n\tif scope.bound() {\n\t\tres.Volume = &scope.stop\n\t}\n\tr.Log.InfoContext(ctx, \"book run finished\", \"book\""
}
],
"run": "TestTheVolumeReportIsAskedBeforeTheCountersAreTruedUp"
},
{
"id": "WB55-corpus-provenance-swapped",
"why": "the corpus digest must cover the ADDRESS a specimen was taken from, not only its counts: a corpus whose numbers are right and whose provenance is wrong is unreproducible in the one way that matters",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/testdata/offtarget-corpus.json",
"find": "\"provenance\": \"probe-4axes/arms/backups/20260902T075308Z.db#draft\",\n \"head\": \"Рассечение камня",
"replace": "\"provenance\": \"planted/not-the-address-it-came-from.db#draft\",\n \"head\": \"Рассечение камня"
}
]
},
{
"id": "WB61-builder-hardcodes-the-fixtures-own-pair",
"why": "a hard-code to the FIXTURE'S OWN pair is the hole a value-comparing test cannot see: the earlier pin asserted the builder's output against values chosen for one book, so it caught a hard-code to the shipping pair and would have sailed past a hard-code to the pair the test itself uses. Only «two books must answer differently» is unsatisfiable by any constant. ⚠ Run-filtered on purpose: a hard-code to `ja` also breaks twenty ru fixtures, so an unfiltered entry would go red without proving anything about THIS pin. The dangerous hard-code is the one to the SHIPPING pair (WB52), which nothing else sees",
"package": "./internal/pipeline/",
"edits": [
{
"file": "internal/pipeline/chunkrun.go",
"find": "\t\tTargetLang: r.Book.TargetLang,\n",
"replace": "\t\tTargetLang: \"ja\",\n"
}
],
"run": "TestClassifyInputForLeavesNoAxisUnset"
},
{
"id": "WB62-innocent-const-must-not-be-accused",
"expect": "survives",
"why": "THE BOUNDARY OF TestEveryFlagReasonIsRanked, recorded because the test used to cross it. An ordinary untyped string constant sitting in the FlagReason block is NOT a flag — Go types it as untyped string, inheriting nothing — and the walk must ignore it. An earlier version carried the previous spec's type forward and failed the test with a message accusing a documentation URL of being an unranked flag. A test that accuses the innocent is not enforcing its own sentence, so this planting MUST survive",
"package": "./internal/pipeline/",
"run": "TestEveryFlagReasonIsRanked",
"edits": [
{
"file": "internal/pipeline/disposition.go",
"find": "\tFlagSanitizerStripped FlagReason = \"sanitizer_stripped\"\n",
"replace": "\tFlagSanitizerStripped FlagReason = \"sanitizer_stripped\"\n\n\t// not a flag: a documentation pointer that happens to live in this block\n\tflagReasonDocURL = \"https://example.invalid/flags\"\n"
}
]
},
{
"id": "WB63-run-compiles-a-constant-source",
"why": "the run's declared SOURCE scripts must come from the book. This is the WRITER of the axis whose reader was pinned one commit earlier — pinning the reader alone left the whole axis decided by an unguarded line, and a constant here gives a ko→ru or ja→ru book the wrong declared source while the echo detector measures a script the book does not use",
"package": "./internal/pipeline/",
"run": "TestTheRunCompilesTheBooksOwnSourceScripts",
"edits": [
{
"file": "internal/pipeline/runner.go",
"find": "\t\tr.checkers.SetSourceScripts(lang.LangScripts(r.Book.SourceLang))\n",
"replace": "\t\tr.checkers.SetSourceScripts(lang.LangScripts(\"ja\"))\n"
}
]
},
{
"id": "WB64-volume-report-after-reconcile-on-both-paths",
"why": "the FAITHFUL form of the ordering defect: the decision moved after reconcile on BOTH the draft-only and the editor path, so no neighbour loses its report for an unrelated reason. Only a fixture whose single unit is carried AND flagged can tell the two orders apart, and until this entry existed the draft-only path had none",
"package": "./internal/pipeline/",
"run": "TestTheVolumeReportIsAskedBefore",
"edits": [
{
"file": "internal/pipeline/waverun.go",
"find": "\tif scope.bound() {\n\t\tres.Volume = &scope.stop\n",
"replace": "\tif scope.bound() {\n"
},
{
"file": "internal/pipeline/waverun.go",
"find": "\t\tscope.reconcile(res.Chunks)\n\t\tr.Log.InfoContext(ctx, \"book run finished (draft-only)\"",
"replace": "\t\tscope.reconcile(res.Chunks)\n\t\tif scope.bound() {\n\t\t\tres.Volume = &scope.stop\n\t\t}\n\t\tr.Log.InfoContext(ctx, \"book run finished (draft-only)\""
},
{
"file": "internal/pipeline/waverun.go",
"find": "\tscope.reconcile(res.Chunks)\n\tr.Log.InfoContext(ctx, \"book run finished\", \"book\"",
"replace": "\tscope.reconcile(res.Chunks)\n\tif scope.bound() {\n\t\tres.Volume = &scope.stop\n\t}\n\tr.Log.InfoContext(ctx, \"book run finished\", \"book\""
}
]
},
{
"id": "WB65-labelled-preflight-skips-the-hop",
"why": "a labelled run CALLS its escalation hop, and BuildClient never refuses an empty key — it only becomes a header. A preflight blind to the hop lets the book open, buy the draft wave and 401 at the escalation: the same late failure as the gates, one field over",
"package": "./internal/config/",
"run": "TestTheLABELLEDPreflightDemandsEveryCallersKeyToo",
"edits": [
{
"file": "internal/config/pipeline.go",
"find": "\t\tadd(st.ResolvedHop)\n",
"replace": ""
}
]
},
{
"id": "WB66-preflight-demands-the-configured-not-the-resolved-model",
"why": "routing is what a labelled book is FOR: the model in the yaml may not be the model that answers. A preflight that demands the CONFIGURED model passes a book whose RESOLVED model has no key, and the refusal moves from open time to the first draft call",
"package": "./internal/config/",
"run": "TestTheLABELLEDPreflightDemandsEveryCallersKeyToo",
"edits": [
{
"file": "internal/config/pipeline.go",
"find": "\t\tadd(st.ResolvedModel)\n",
"replace": "\t\tadd(st.Model)\n"
}
]
},
{
"id": "WB67-flag-reason-in-the-conversion-spelling",
"why": "`X = FlagReason(\"…\")` is as ordinary a declaration as `X FlagReason = \"…\"`, and a walk that reads only the spec's type is blind to it — exhibited as a pair, the typed spelling caught and the conversion spelling not",
"package": "./internal/pipeline/",
"run": "TestEveryFlagReasonIsRanked",
"edits": [
{
"file": "internal/pipeline/status.go",
"find": "const severityUnknown = 8\n",
"replace": "const severityUnknown = 8\n\n// planted\nconst FlagPlantedConv = FlagReason(\"planted_conv\")\n"
}
]
}
]

View file

@ -259,3 +259,93 @@ func TestTheUnlabelledPreflightDemandsEveryCallersKey(t *testing.T) {
})
}
}
// TestTheLABELLEDPreflightDemandsEveryCallersKeyToo is the twin of the test above, and it exists because
// the fix that closed the hole closed ONE branch of it.
//
// ⚠ CheckKeys forks on whether the book carries content labels. The unlabelled branch — the shipping
// default — was the one found open and the one pinned. The labelled branch reaches the gates only through
// ReachableModels(), and the only thing asserting that was TestABankModelIsAReachableModel, which reads
// the LIST. «A pin on the list is not a pin on the preflight» is the sentence this pack was written around;
// leaving the second branch pinned by exactly the thing that sentence rejects is the same defect with the
// paint changed. Planted — the labelled branch collecting stage models by hand — the whole package stayed
// green, and a labelled book with its bank role on a keyless provider bought the draft wave and died at
// the gate, which is where this started.
func TestTheLABELLEDPreflightDemandsEveryCallersKeyToo(t *testing.T) {
m := &Models{
Providers: map[string]Provider{
"stage-provider": {Kind: "openai", BaseURL: "http://x", APIKeyEnv: "TEST_STAGE_KEY"},
"other-provider": {Kind: "openai", BaseURL: "http://y", APIKeyEnv: "TEST_OTHER_KEY"},
},
Models: map[string]Model{
"draft-model": {Provider: "stage-provider"},
"bank-model": {Provider: "other-provider"},
"classify-model": {Provider: "other-provider"},
"repair-model": {Provider: "other-provider"},
"hop-model": {Provider: "other-provider"},
},
}
t.Setenv("TEST_STAGE_KEY", "sk-present")
t.Setenv("TEST_OTHER_KEY", "")
base := func() *Pipeline {
return &Pipeline{
ContentLabels: []string{"violence"},
Stages: []Stage{{Name: "draft", Model: "draft-model", ResolvedModel: "draft-model"}},
}
}
for _, c := range []struct {
name string
mut func(*Pipeline)
}{
{"the terminologist", func(p *Pipeline) {
p.Gates.Terminology = TerminologyGate{Enabled: true, Model: "bank-model"}
}},
{"the classifier phase", func(p *Pipeline) {
p.Gates.Terminology = TerminologyGate{Enabled: true, Model: "draft-model",
ClassifyTypes: true, ClassifyModel: "classify-model"}
}},
{"the repair gate", func(p *Pipeline) {
p.Gates.Repair = RepairGate{Enabled: true, Model: "repair-model"}
}},
// ⚠ THE HOP, found by an adversarial pass over this very test: the gates were covered and the
// stage's own hop was not. A labelled run calls its escalation hop, BuildClient never refuses an
// empty key (it only becomes a header), so under a preflight blind to the hop the book opens, buys
// the draft wave, and 401s at the escalation — the same late failure, one field over.
{"the stage's escalation hop", func(p *Pipeline) {
p.Stages[0].ResolvedHop = "hop-model"
}},
// ⚠ AND THE RESOLVED MODEL, NOT THE CONFIGURED ONE. Routing is what a labelled book is FOR: the
// model in the yaml may not be the model that answers. A preflight that demanded `Model` would pass
// here and the first draft call would 401 — the refusal moving from open time to mid-run, which is
// precisely the distance this whole family of tests exists to keep at zero.
{"the RESOLVED model when routing replaced the configured one", func(p *Pipeline) {
p.Stages[0].Model, p.Stages[0].ResolvedModel = "draft-model", "bank-model"
}},
} {
t.Run(c.name, func(t *testing.T) {
p := base()
c.mut(p)
if len(p.ContentLabels) == 0 {
t.Fatal("premise broken: this test is about the LABELLED branch and must carry labels")
}
err := m.CheckKeys(p)
if err == nil {
t.Fatalf("a LABELLED book's %s calls a provider whose key is absent and the preflight passed: "+
"the run buys the draft wave and dies at the gate", c.name)
}
if !strings.Contains(err.Error(), "TEST_OTHER_KEY") {
t.Fatalf("the refusal must name the env var an operator has to set, got: %v", err)
}
})
}
// And the disabled direction, so this cannot be satisfied by demanding every model in the file.
t.Run("gates off demand nothing", func(t *testing.T) {
p := base()
p.Gates.Terminology = TerminologyGate{Enabled: false, Model: "bank-model"}
p.Gates.Repair = RepairGate{Enabled: false, Model: "repair-model"}
if err := m.CheckKeys(p); err != nil {
t.Fatalf("a labelled book that never calls those models must not be made to hold their keys: %v", err)
}
})
}

View file

@ -297,6 +297,47 @@ func TestTheLawBlockGivesOneOrderPerSourceTerm(t *testing.T) {
if n := strings.Count(RenderEditorConstraintBlock(sortByPriority(padded), tx), "元海 → "); n != 1 {
t.Errorf("the signed set and the lookup must normalize the surface the same way, got %d lines", n)
}
// (10) ⚠ TWO UNSIGNED ROWS OF ONE SURFACE BOTH STAND — and this is a DECLARED CHANGE OF WIRE BEHAVIOUR,
// written down here because it happened and was not declared when it did.
//
// The rule this block enforces used to dedup on the bare `src`, which collapsed any two renderings of a
// surface to whichever the sort put first. That was corrected because it dropped a second SIGNED canon
// (青山 the surname and 青山 the direction are two decisions the owner made separately). The correction
// keys on (src, dst) and makes only an UNSIGNED row yield, and only to a SIGNED one — so where there is
// no signature at all, nothing yields and both renderings reach the model. Measured across the change:
// the same two unsigned rows produced ONE line before and TWO after.
//
// ⚠ WHAT THIS CASE DOES AND DOES NOT SAY. It pins the RENDER's behaviour: with no signature present
// there is nothing to prefer, so the block does not arbitrate — it shows both and lets the run's own
// unsigned-conflict report (TestUnsignedConflictIsReportedByTheRun) be the place a human is told. A
// silent pick-by-sort-order would be the engine choosing between two of its own guesses and hiding
// that it did, which is strictly worse at THIS layer.
//
// It does NOT say that two unsigned renderings of one surface may exist in the first place. That is an
// UPSTREAM question and the owner has answered it: an unsigned term must have ONE rendering too, and
// the consolidation stage owns that, not the signature. Measured today, the bank accepts the state —
// two rows, one src, one (empty) sense, overlapping spoiler windows, different dst — because the
// same-sense overlap guard (memseed.go) runs PER FILE and the merge (storeOrder) refuses only an exact
// duplicate of the UNIQUE key, so a seed row and a mined-delta row collide only after both have passed
// their own loader. When that is fixed upstream this state stops being reachable and this case becomes
// a statement about something that no longer occurs — which is the right time to delete it, and the
// wrong time to read it as doctrine that two lines are DESIRABLE.
unsignedPair := []PickedEntry{
{entry: &entry{src: "元石", dst: "юаньши", status: "draft"}, Via: "元石", Disp: Ambiguous, KeyTrusted: true},
{entry: &entry{src: "元石", dst: "камень первоисточника", status: "auto"}, Via: "元石", Disp: Ambiguous, KeyTrusted: true},
}
got10 := RenderEditorConstraintBlock(sortByPriority(unsignedPair), tx)
if n := strings.Count(got10, "元石 → "); n != 2 {
t.Errorf("two UNSIGNED renderings of one surface must BOTH reach the model — there is no signature "+
"for either to yield to, and picking one by sort order hides a disagreement the run reports. "+
"Got %d lines:\n%s", n, got10)
}
// And the draft wire must say the same thing: the two wires disagreeing about how many renderings a
// surface has is the shape the original regression took.
if draft := RenderGlossaryBlock(sortByPriority(unsignedPair), tx); strings.Count(draft, "元石 → ") != 2 {
t.Errorf("the two wires disagree about the unsigned pair:\neditor: %s\ndraft: %s", got10, draft)
}
}
// TestTheBudgetKeepsTheTrustworthyMatchFirst is where priorityRank's DISPOSITION axis actually decides

View file

@ -1,8 +1,11 @@
package pipeline
import (
"os"
"reflect"
"strings"
"testing"
"unicode"
"textmachine/backend/internal/checks"
"textmachine/backend/internal/config"
@ -24,9 +27,22 @@ import (
// the same person who forgets to update the builder, which is the failure it is supposed to prevent. A new
// axis added to classifyInput and not wired in fails here on the day it is declared.
func TestClassifyInputForLeavesNoAxisUnset(t *testing.T) {
ck := checks.CompileCheckersFor(nil, lang.TargetChecksFor("ru"))
ck.SetSourceScripts(lang.LangScripts("zh"))
r := &Runner{Book: &config.Book{TargetLang: "ru"}, checkers: ck}
// ⚠ A NON-ru TARGET AND A NON-zh SOURCE, AND THAT IS THE WHOLE DESIGN OF THIS FIXTURE. The first
// version used ru/zh — the shipping pair — so a builder that ignored the book entirely and hard-coded
// `lang.LangScripts("ru")` produced exactly the values asserted, and three separate plantings that did
// precisely that survived the whole package. Nothing else in the battery could catch them either:
// every runner fixture translates INTO Russian. A pair the fixture does not share with the hard-code
// is the only shape of this test that can fail.
const target, source = "ja", "ko"
if len(lang.LangScripts(target)) == len(lang.LangScripts("ru")) || len(lang.LangScripts(source)) == len(lang.LangScripts("zh")) {
t.Fatalf("premise broken: this test can only catch a hard-coded ru/zh builder while the script COUNTS "+
"differ — %s has %d, ru has %d; %s has %d, zh has %d. Pick another pair or assert by identity.",
target, len(lang.LangScripts(target)), len(lang.LangScripts("ru")),
source, len(lang.LangScripts(source)), len(lang.LangScripts("zh")))
}
ck := checks.CompileCheckersFor(nil, lang.TargetChecksFor(target))
ck.SetSourceScripts(lang.LangScripts(source))
r := &Runner{Book: &config.Book{TargetLang: target}, checkers: ck}
// Every argument is chosen NON-ZERO, so that any zero field in the result is an unwired axis and not an
// artefact of the fixture.
@ -41,16 +57,16 @@ func TestClassifyInputForLeavesNoAxisUnset(t *testing.T) {
}
}
// The two axes the incident was actually about, asserted by value and not merely as non-zero.
if in.TargetLang != "ru" {
t.Errorf("TargetLang = %q, want the book's", in.TargetLang)
if in.TargetLang != target {
t.Errorf("TargetLang = %q, want the book's %q", in.TargetLang, target)
}
if len(in.TargetScripts) != len(lang.LangScripts("ru")) {
t.Errorf("TargetScripts must come from the book's target: got %d tables, want %d",
len(in.TargetScripts), len(lang.LangScripts("ru")))
if len(in.TargetScripts) != len(lang.LangScripts(target)) {
t.Errorf("TargetScripts must come from the BOOK's target, not a constant: got %d tables, want %d for %s",
len(in.TargetScripts), len(lang.LangScripts(target)), target)
}
if len(in.SourceScripts) != len(lang.LangScripts("zh")) {
t.Errorf("SourceScripts must come from the run's compiled checkers: got %d tables, want %d",
len(in.SourceScripts), len(lang.LangScripts("zh")))
if len(in.SourceScripts) != len(lang.LangScripts(source)) {
t.Errorf("SourceScripts must come from the RUN's compiled checkers, not a constant: got %d tables, want %d for %s",
len(in.SourceScripts), len(lang.LangScripts(source)), source)
}
if !in.NonProseReply {
t.Error("NonProseReply must pass through: a bank role's reply is not prose and must not be echo-screened")
@ -59,4 +75,79 @@ func TestClassifyInputForLeavesNoAxisUnset(t *testing.T) {
if r.classifyInputFor("源文本", "перевод", llm.FinishStop, false).NonProseReply {
t.Error("NonProseReply is pinned to true inside the builder — the prose path would be exempted from the echo rule")
}
// ⛔ TWO BOOKS, AND THIS IS THE ASSERTION A CONSTANT CANNOT SATISFY. Everything above compares the
// builder's output against values chosen for THIS book, so it catches a hard-code to the shipping pair
// and nothing else — a hard-code to ja/ko would sail through the very fixture written to catch
// hard-codes. Two books whose answers must DIFFER is the only shape that cannot be met by any constant,
// whichever one is picked.
other := checks.CompileCheckersFor(nil, lang.TargetChecksFor("ru"))
other.SetSourceScripts(lang.LangScripts("zh"))
r2 := &Runner{Book: &config.Book{TargetLang: "ru"}, checkers: other}
a := r.classifyInputFor("源文本", "перевод", llm.FinishStop, false)
b := r2.classifyInputFor("源文本", "перевод", llm.FinishStop, false)
if a.TargetLang == b.TargetLang {
t.Errorf("two books with different targets produced the same TargetLang %q — the builder is not "+
"reading the book at all", a.TargetLang)
}
if len(a.TargetScripts) == len(b.TargetScripts) {
t.Errorf("two books with different targets produced the same TargetScripts (%d tables) — the builder "+
"is not reading the book's target", len(a.TargetScripts))
}
if len(a.SourceScripts) == len(b.SourceScripts) {
t.Errorf("two runs with different compiled checkers produced the same SourceScripts (%d tables) — the "+
"builder is not reading the run's checkers", len(a.SourceScripts))
}
}
// TestTheRunCompilesTheBooksOwnSourceScripts pins the OTHER end of the same axis, one call site away from
// the builder above — and it was found by an adversarial pass over that very fix.
//
// ⚠ The builder reads r.checkers.SourceScripts(); loadLangPack is what FILLS them, from the book's
// SourceLang. Pinning the reader and not the writer leaves the whole axis decided by an unguarded line: a
// hard-code there gives a ko→ru or ja→ru book the wrong declared source, the echo detector then measures a
// script the book does not use, and nothing in the battery says a word. Same two-book shape as above,
// because the same reason applies — any constant makes two books agree.
func TestTheRunCompilesTheBooksOwnSourceScripts(t *testing.T) {
srv := newJSONProvider(&reqRec{}, draftEdit)
defer srv.Close()
sourceScriptsFor := func(t *testing.T, sourceLang string) []*unicode.RangeTable {
t.Helper()
bookPath := setupProjectOpts(t, srv.URL, projectOpts{})
if sourceLang != "ja" {
raw, err := os.ReadFile(bookPath)
if err != nil {
t.Fatal(err)
}
body := strings.Replace(string(raw), "source_lang: ja", "source_lang: "+sourceLang, 1)
if body == string(raw) {
t.Fatalf("premise broken: the fixture's book.yaml no longer carries `source_lang: ja`:\n%s", raw)
}
writeFile(t, bookPath, body)
}
r := newRunner(t, bookPath)
defer r.Close()
return r.checkers.SourceScripts()
}
// ja declares three scripts, ko two — so a constant of either kind is visible as a disagreement.
if len(lang.LangScripts("ja")) == len(lang.LangScripts("ko")) {
t.Fatalf("premise broken: this test needs two sources whose script COUNTS differ, got ja=%d ko=%d",
len(lang.LangScripts("ja")), len(lang.LangScripts("ko")))
}
ja := sourceScriptsFor(t, "ja")
ko := sourceScriptsFor(t, "ko")
if len(ja) != len(lang.LangScripts("ja")) {
t.Errorf("a ja-source book compiled %d source scripts, want %d — the run is not reading the book's "+
"source language, so the echo detector measures the wrong script", len(ja), len(lang.LangScripts("ja")))
}
if len(ko) != len(lang.LangScripts("ko")) {
t.Errorf("a ko-source book compiled %d source scripts, want %d", len(ko), len(lang.LangScripts("ko")))
}
if len(ja) == len(ko) {
t.Errorf("two books with different source languages compiled the SAME source scripts (%d tables) — "+
"the run is using a constant, and no assertion about one book alone can see that", len(ja))
}
}

View file

@ -198,3 +198,84 @@ func TestEveryPromptLoadPathRefusesAnUnrenderableTemplate(t *testing.T) {
})
}
}
// TestTheBanknoteWarningKeysOnEachConditionSeparately closes a WB41-class hole in the test above: its
// positive fixture has NEITHER mining input and its negative has BOTH, so every one of the guard's parts
// could be deleted and the pair still passed. Planted separately, three of them survived the whole package —
// dropping the pack half of the disjunction, dropping the contrast half, and dropping `forWrite` entirely.
//
// The guard is `forWrite && banknote.Enabled && (pack == nil || contrast == "")`. A disjunction pinned only
// at "neither" and "both" is pinned at neither of its arms.
func TestTheBanknoteWarningKeysOnEachConditionSeparately(t *testing.T) {
packRoot, err := filepath.Abs(filepath.Join("..", "..", "configs", "langpacks"))
if err != nil {
t.Fatal(err)
}
open := func(t *testing.T, bookPath string, readOnly bool) string {
t.Helper()
var log bytes.Buffer
h := slog.New(slog.NewTextHandler(&log, &slog.HandlerOptions{Level: slog.LevelInfo}))
var r *Runner
var oerr error
if readOnly {
r, oerr = NewReadOnlyRunner(bookPath, h)
} else {
r, oerr = NewRunner(bookPath, h)
}
if oerr != nil {
t.Fatalf("this configuration is legal and must open — it is wasteful, not broken: %v", oerr)
}
r.Close()
return log.String()
}
// ARM 1 — a pack, but no contrast artifact. The bank-mining stop returns before reading anything, so
// the banknote tokens are bought and unread; the pack half of the disjunction must not be what decides.
t.Run("a pack but no contrast artifact", func(t *testing.T) {
srv := newJSONProvider(&reqRec{}, draftEdit)
defer srv.Close()
bookPath := setupProjectOpts(t, srv.URL, projectOpts{banknote: true})
raw, rerr := os.ReadFile(bookPath)
if rerr != nil {
t.Fatal(rerr)
}
body := strings.Replace(string(raw), "source_lang: ja", "source_lang: zh\nlangpack_root: "+packRoot, 1)
if !strings.Contains(body, "langpack_root:") {
t.Fatalf("premise broken: the fixture's book.yaml no longer carries the anchor this test edits:\n%s", raw)
}
writeFile(t, bookPath, body)
if out := open(t, bookPath, false); !strings.Contains(out, "banknote channel is ON") {
t.Errorf("a book with a pack and NO contrast artifact still cannot reach the stop, and still pays "+
"for the term table on every draft call — it must be warned:\n%s", out)
}
})
// ARM 2 — a contrast artifact, but no pack. The mirror image, and the one a config loader structurally
// cannot see: the pack is book data, not pipeline data.
t.Run("a contrast artifact but no pack", func(t *testing.T) {
srv := newJSONProvider(&reqRec{}, draftEdit)
defer srv.Close()
bookPath := setupProjectOpts(t, srv.URL, projectOpts{banknote: true,
gatesYAML: "\nmining:\n contrast_path: mining-contrast.txt\n"})
dir := filepath.Dir(bookPath)
writeFile(t, filepath.Join(dir, "mining-contrast.txt"), miningContrastData)
if out := open(t, bookPath, false); !strings.Contains(out, "banknote channel is ON") {
t.Errorf("a book with a contrast artifact and NO langpack cannot mine either, and must be warned:\n%s", out)
}
})
// ARM 3 — `forWrite`. A $0 read path buys nothing, so warning there teaches an operator to ignore the
// line on the run where it matters. Planted, dropping the guard entirely stayed green.
t.Run("a read-only open is quiet", func(t *testing.T) {
srv := newJSONProvider(&reqRec{}, draftEdit)
defer srv.Close()
bookPath := setupProjectOpts(t, srv.URL, projectOpts{banknote: true})
// Premise: this very book WOULD be warned on a write open — otherwise the silence below proves nothing.
if out := open(t, bookPath, false); !strings.Contains(out, "banknote channel is ON") {
t.Fatalf("premise broken: the write open must warn for this fixture:\n%s", out)
}
if out := open(t, bookPath, true); strings.Contains(out, "banknote channel is ON") {
t.Errorf("a read-only open spends nothing, so it must not warn about what a write run would buy:\n%s", out)
}
})
}

View file

@ -4,7 +4,9 @@ import (
"go/ast"
"go/parser"
"go/token"
"os"
"strconv"
"strings"
"testing"
)
@ -22,31 +24,75 @@ import (
// and would then certify the rot. This is the guarantee that the next flag someone adds cannot land in the
// unknown bucket silently: it fails here on the day it is declared, not on the day it is misreported.
func TestEveryFlagReasonIsRanked(t *testing.T) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "disposition.go", nil, 0)
// ⚠ EVERY FILE OF THE PACKAGE, not disposition.go alone — the first version of this test named one
// file, and a planting that declared a FlagReason anywhere else walked straight past it while the test
// still reported «every declared reason is ranked». The guarantee was one file narrower than its own
// sentence, which is the defect class this file exists to prevent, committed by the file itself.
entries, err := os.ReadDir(".")
if err != nil {
t.Fatalf("parse disposition.go: %v", err)
t.Fatalf("read the package directory: %v", err)
}
var sources []string
for _, e := range entries {
n := e.Name()
if e.IsDir() || !strings.HasSuffix(n, ".go") || strings.HasSuffix(n, "_test.go") {
continue
}
sources = append(sources, n)
}
if len(sources) < 10 {
t.Fatalf("only %d source files found — the walk stopped seeing the package: %v", len(sources), sources)
}
fset := token.NewFileSet()
var decls []ast.Decl
for _, name := range sources {
f, perr := parser.ParseFile(fset, name, nil, 0)
if perr != nil {
t.Fatalf("parse %s: %v", name, perr)
}
decls = append(decls, f.Decls...)
}
var found int
for _, d := range f.Decls {
for _, d := range decls {
gd, ok := d.(*ast.GenDecl)
if !ok || gd.Tok != token.CONST {
continue
}
var lastType string
for _, sp := range gd.Specs {
vs, ok := sp.(*ast.ValueSpec)
if !ok {
continue
}
if id, ok := vs.Type.(*ast.Ident); ok {
lastType = id.Name
}
if lastType != "FlagReason" || len(vs.Values) == 0 {
// ⚠ THE TYPE MUST BE WRITTEN ON THIS SPEC. An earlier version carried the previous spec's type
// forward, which is not how Go types a const block: a spec that has a VALUE and no type is its
// own UNTYPED constant and inherits nothing. That version raised a FALSE ALARM — an ordinary
// string constant placed after a FlagReason spec in the same block was accused of being an
// unranked flag, and a doc-URL constant would have failed this test with a message about the
// chapter passport. A test that accuses the innocent is not enforcing its own sentence, and this
// one exists to say what IS a declared reason.
//
// ⚠ AND BOTH GO SPELLINGS COUNT. `X FlagReason = "…"` writes the type in the spec; the equally
// ordinary `X = FlagReason("…")` writes it as a CONVERSION, leaving vs.Type nil — and a walk
// that looked only at vs.Type was blind to it, which an adversarial pass exhibited as a pair:
// the typed spelling of one declaration RED, the conversion spelling of the same declaration
// SURVIVED. The sentence that used to stand here — «a missed declaration would trip the found
// floor rather than pass silently» — was simply false: one missing constant leaves the count at
// 16 against a floor of 15, and nothing trips.
if len(vs.Values) == 0 {
continue
}
lit, ok := vs.Values[0].(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
var lit *ast.BasicLit
switch {
case isFlagReasonIdent(vs.Type):
lit, _ = vs.Values[0].(*ast.BasicLit)
default:
call, isCall := vs.Values[0].(*ast.CallExpr)
if !isCall || !isFlagReasonIdent(call.Fun) || len(call.Args) != 1 {
continue
}
lit, _ = call.Args[0].(*ast.BasicLit)
}
if lit == nil || lit.Kind != token.STRING {
continue
}
val, err := strconv.Unquote(lit.Value)
@ -66,8 +112,8 @@ func TestEveryFlagReasonIsRanked(t *testing.T) {
// The reader of this test must know it actually read something: a parse that silently matched nothing
// would pass forever and guard nothing — the same failure class one layer up.
if found < 15 {
t.Fatalf("only %d FlagReason constants were read out of disposition.go — the walk stopped matching "+
"the source, and this test is no longer checking anything", found)
t.Fatalf("only %d FlagReason constants were read out of the package's %d source files — the walk "+
"stopped matching the source, and this test is no longer checking anything", found, len(sources))
}
if len(flagSeverity) != found {
t.Errorf("flagSeverity has %d entries for %d declared reasons — a rank exists for a reason that no "+
@ -99,3 +145,10 @@ func TestOffTargetLangIsRankedWithTheContentFailures(t *testing.T) {
t.Error("a hard refusal must still outrank an off-target completion")
}
}
// isFlagReasonIdent reports whether an expression names the FlagReason type — as a spec's type or as the
// function of a conversion. One helper for both spellings, so the walk cannot learn one and forget the other.
func isFlagReasonIdent(e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && id.Name == "FlagReason"
}

View file

@ -199,7 +199,13 @@ func TestTheCorpusStillHoldsItsHardestSpecimens(t *testing.T) {
// accounting under its own id — both invisible, because the extremum still held. The corpus is a
// frozen measurement, so a digest over every row's accounting is the honest pin: re-taking it is a
// deliberate act that re-records this line in the same commit.
corpusDigest = "8c77a0947b7bc72b0b62e99291bfbc0955df764635e368cf9b03590606f9d2c7"
// ⚠ IT COVERS EVERY FIELD, INCLUDING THE ONES NO MEASUREMENT READS. The first version folded only the
// six counting fields, and a planting that rewrote a specimen's `head` — and, worse, its
// `provenance` — passed with the whole ledger green. `provenance` is the ADDRESS a specimen was
// taken from and the address it would be re-taken from; a corpus whose numbers are right and whose
// provenance is wrong is unreproducible in the one way that matters, and nothing else in this file
// looks at that field beyond `!= ""`.
corpusDigest = "ac4c14ef47e0c42807f47c88abc49da672884864392930fb07ecf4fe71f8fa23"
)
corpus := loadOffTargetCorpus(t)
var offTargets, healthy int
@ -251,9 +257,10 @@ func TestTheCorpusStillHoldsItsHardestSpecimens(t *testing.T) {
// The whole population, so that a row which is not an extremum cannot be softened, relabelled, swapped
// for an easier one under its own id, or duplicated, without saying so.
if got := offTargetCorpusDigest(corpus); got != corpusDigest {
t.Errorf("the corpus accounting changed:\n is: %s\n recorded: %s\nEvery row's id, label and "+
"four counts feed this. If the corpus was legitimately re-taken, re-record the digest in the same "+
"commit and say so in the report; if it was not, find out what edited the fixture.", got, corpusDigest)
t.Errorf("the corpus accounting changed:\n is: %s\n recorded: %s\nAll EIGHT fields of every "+
"row feed this — id, label, the four counts, provenance and head. If the corpus was legitimately "+
"re-taken, re-record the digest in the same commit and say so in the report; if it was not, find "+
"out what edited the fixture.", got, corpusDigest)
}
}
@ -264,7 +271,8 @@ func offTargetCorpusDigest(corpus []offTargetSpecimen) string {
sort.Slice(rows, func(i, j int) bool { return rows[i].ID < rows[j].ID })
h := sha256.New()
for _, s := range rows {
fmt.Fprintf(h, "%s|%s|%d|%d|%d|%d\n", s.ID, s.Label, s.SourceScriptRunes, s.TotalRunes, s.Letters, s.TargetScriptLetters)
fmt.Fprintf(h, "%s|%s|%d|%d|%d|%d|%s|%s\n", s.ID, s.Label, s.SourceScriptRunes, s.TotalRunes,
s.Letters, s.TargetScriptLetters, s.Provenance, s.Head)
}
return hex.EncodeToString(h.Sum(nil))
}

View file

@ -846,3 +846,142 @@ func TestReconcileLeavesUnpaidAndReworkedUnitsAlone(t *testing.T) {
t.Errorf("the sentence's paying count would over-report by one: %+v", rework.stop)
}
}
// TestTheVolumeReportIsAskedBeforeTheCountersAreTruedUp pins the ORDER of bound() and reconcile() at the
// level where the order exists — the wave driver — because nothing did.
//
// ⚠ waverun.go's own comment calls this order load-bearing, says it was found by planting, and sends the
// reader to TestReconcileMovesAFlaggedCarryOutOfCarried. That test is a unit test of volumeScope: it proves
// reconcile moves a flagged carry out of Carried, and it cannot see WHERE the driver asks bound(). Planted —
// the bound() block moved to just after reconcile — the whole pipeline package stayed green while
// `res.Volume` came back nil, i.e. the run that finished a unit outside its grant and flagged it said
// NOTHING about it. That is the disclosure the report exists for, and «the comment explains it» is not a
// guarantee; this is the same «a claim about structure passed off as a guarantee» class the round found
// four times, here in a comment that is otherwise entirely correct.
//
// The fixture is the smallest run that can tell the two orders apart: ONE carried unit, nothing left, and
// the carry comes back FLAGGED — so bound() is true before reconcile (Carried==1) and false after it
// (Carried==0, Flagged==1). A carry that DELIVERS cannot see this, which is why the neighbouring
// TestOneCarriedUnitIsStillWorkOutsideTheGrant passes under the planting.
func TestTheVolumeReportIsAskedBeforeTheCountersAreTruedUp(t *testing.T) {
rec := &reqRec{}
var mu sync.Mutex
editorRefuses := false
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
rec.record(string(body))
if isEditBody(string(body)) {
mu.Lock()
refuse := editorRefuses
mu.Unlock()
if !refuse {
w.WriteHeader(http.StatusInternalServerError) // run 1: the unit is started and never ships
return
}
writeFakeCompletion(w, "I'm sorry, but I can't help with that request.", "refusal")
return
}
writeFakeCompletion(w, "ЧЕРНОВИК ПЕРЕВОДА", "stop")
}))
defer srv.Close()
bookPath := volumeBook(t, srv.URL, 1)
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
r1 := newRunner(t, bookPath)
if _, err := r1.TranslateBook(ctx); err == nil {
t.Fatal("precondition: the editor was supposed to fail this run, leaving one started-but-unshipped unit")
}
r1.Close()
mu.Lock()
editorRefuses = true
mu.Unlock()
r2 := newRunner(t, bookPath)
defer r2.Close()
r2.MaxUnits = 1
res, err := r2.TranslateBook(ctx)
if err != nil {
t.Fatal(err)
}
// Premise: the run's ONLY work was the carried unit, and it flagged. Without both halves the assertion
// below would pass under either order and prove nothing.
if res.Flagged != 1 {
t.Fatalf("premise broken: the carried unit must come back FLAGGED, got flagged=%d over %d chunk(s)",
res.Flagged, len(res.Chunks))
}
v := res.Volume
if v == nil {
t.Fatal("the run finished a unit outside its grant, paid for it, and it FLAGGED — and the run reported " +
"no volume at all. bound() is being asked AFTER reconcile has moved that carry out of Carried, so " +
"the one run that most needs the disclosure is the one that goes silent")
}
if !strings.Contains(v.String(), "PAID FOR BUT FLAGGED") {
t.Errorf("the report must name the money spent for no readable text: %s", v.String())
}
}
// TestTheVolumeReportIsAskedBeforeReconcileOnADraftOnlyPipeline is the draft-only twin of the test above,
// and it exists because the first version of that pin turned out to be carried by its neighbours.
//
// ⚠ The bound() decision sits BEFORE the fork into the draft-only and editor paths, so BOTH paths depend on
// its position — but only the editor path had a fixture that could tell the two orders apart. A planting
// that moved the decision after the editor path's reconcile reddened three draft-only tests for the wrong
// reason (they lost the report entirely, not out of order), and a planting that moved it after BOTH
// reconciles would have been caught by nothing on this shape. On a draft-only pipeline the SHIPPING wave is
// the draft one, so the same carried-and-flagged unit is reachable here and reports the same way.
func TestTheVolumeReportIsAskedBeforeReconcileOnADraftOnlyPipeline(t *testing.T) {
var mu sync.Mutex
calls, released := 0, false
rec := &reqRec{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
rec.record(string(body))
mu.Lock()
calls++
n, rel := calls, released
mu.Unlock()
switch {
case rel:
// Run 2: the second draft stage answers, and REFUSES — the carried unit is finished and flagged.
writeFakeCompletion(w, "I'm sorry, but I can't help with that request.", "refusal")
case n == 1:
writeFakeCompletion(w, "ЧЕРНОВИК ПЕРЕВОДА", "stop") // stage 1 of run 1 checkpoints
default:
w.WriteHeader(http.StatusInternalServerError) // stage 2 of run 1 dies: started, never shipped
}
}))
defer srv.Close()
eps := []chunktest.Chapter{{ID: "c1", Href: "c1.xhtml", Body: "<p>静かな図書館の朝1。</p>"}}
bookPath := setupProjectOpts(t, srv.URL, projectOpts{epub: eps, spine: []string{"c1"}, draftOnly: true, waveWorkers: 1})
addSecondDraftStage(t, bookPath)
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
r1 := newRunner(t, bookPath)
if _, err := r1.TranslateBook(ctx); err == nil {
t.Fatal("precondition: the second draft stage was supposed to fail this run")
}
r1.Close()
mu.Lock()
released = true
mu.Unlock()
r2 := newRunner(t, bookPath)
defer r2.Close()
r2.MaxUnits = 1
res, err := r2.TranslateBook(ctx)
if err != nil {
t.Fatal(err)
}
if res.Flagged != 1 {
t.Fatalf("premise broken: the carried unit must come back FLAGGED on the shipping (draft) wave, got "+
"flagged=%d over %d chunk(s)", res.Flagged, len(res.Chunks))
}
if res.Volume == nil {
t.Fatal("a draft-only run finished a unit outside its grant, paid for it, and it FLAGGED — and reported " +
"no volume at all. The bound() decision is being taken from counters reconcile has already trued up")
}
if !strings.Contains(res.Volume.String(), "PAID FOR BUT FLAGGED") {
t.Errorf("the report must name the money spent for no readable text: %s", res.Volume.String())
}
}