package runner import ( "os" "path/filepath" "strings" "testing" "gopkg.in/yaml.v3" "textmachine/platform/internal/ingest" ) // ⛔ THE SEAM AGAINST THE REAL ENGINE, and it exists because every other test of the price // projection reads a document THIS SIDE WROTE. // // The allowlist in `ingest` is a transcription of the engine's own JSON, and a transcription is // exactly the artefact that goes stale in silence: a key the engine renames leaves a zero behind, // `json.Unmarshal` says nothing, and the platform then sells a book with no floor under its hold — // which is the PD-440 wall coming back invisibly. A hand-written fixture cannot catch that, because // it agrees with the reader by construction. // // So this one asks the ENGINE. It runs `tmctl manifest --json` against a probe book, decodes the // bytes with the platform's own reader, and asserts that the projection ARRIVED — not merely that // the document parsed. // // Gated like every live-engine test: a bare clone stays green and says why. func TestTheRealEnginesPriceProjectionIsReadByThisBuild(t *testing.T) { bin := os.Getenv("TM_PLATFORM_TEST_ENGINE_BIN") tpl := os.Getenv("TM_PLATFORM_TEST_BOOK_TEMPLATE") if bin == "" || tpl == "" { t.Skip("TM_PLATFORM_TEST_ENGINE_BIN and TM_PLATFORM_TEST_BOOK_TEMPLATE not set: " + "the price projection is not checked against a real engine") } // Two chapters the engine's own heading rule will find, so the cut is `detected` and the tree has // something to price per chapter. const src = "第1章 测试\n这是一个测试文本。天空是蓝色的,云朵飘过。\n\n第2章 又一章\n他走进了那座古老的图书馆,书架高耸入云。\n" dir := writeReadProbeBook(t, tpl, "bk_PRICEPROBE", src) m, err := New(nil).Manifest(t.Context(), bin, dir) if err != nil { t.Fatalf("the live manifest did not run: %v", err) } if err := m.Whole(); err != nil { t.Fatalf("this build did not read the live manifest whole: %v", err) } // THE PROJECTION, read by the platform's own reader out of the engine's own bytes. p, ok := m.Priced() if !ok { t.Fatalf("this build read no projection out of a live manifest of %d chapters: the allowlist "+ "and the engine have drifted apart, and a book priced by nobody cannot be sold "+ "(ingest.Priced refuses it, which is the honest half — but the SEAM is what broke)", m.ChaptersTotal) } // ⚠ EVERY LOAD-BEARING FIGURE, each named for what depends on it rather than checked for being // non-zero in a loop: what a reader needs from a failure here is WHICH number stopped arriving. if p.StepMaxUSD <= 0 { t.Error("step_max_usd did not arrive: it is the floor of every hold, and without it a run is " + "admitted with a ceiling no single call can clear (PD-440)") } if p.ExpectedUSD <= 0 { t.Error("expected_usd did not arrive: the order has nothing to be priced from") } if p.SourceChars <= 0 { t.Error("source_chars did not arrive: the screen falls back to the intake's approximation") } // The per-chapter roll-up is what an ORDER is priced from, and its absence is the failure the // book-level figure alone cannot show. for _, c := range m.Chapters { if c.Price == nil || c.Price.ExpectedUSD <= 0 { t.Errorf("chapter %d arrived with no price, so no partial order over this book can be quoted", c.Number) } } // The per-UNIT figure, which is what a CHARACTER order resolves against — the only partial order // a book with no chapter structure can carry. for _, c := range m.Chapters { for _, u := range c.Units { if u.Price == nil || u.Price.SourceChars <= 0 { t.Errorf("unit %q arrived with no size, so a character order cannot be resolved", u.ID) } } } // The cut's PROVENANCE, and the vocabulary it comes in. This build reads three words and treats // anything else as untrusted; a live engine emitting a fourth is not a failure — it is the day // the platform stops offering chapter orders for this book, which is what should happen. switch m.Structure { case ingest.StructureDetected: if !m.ChapterOrdersOffered() { t.Error("a detected cut is not offered in chapters") } case ingest.StructureDeclared, ingest.StructureNone: t.Logf("the live engine cut this probe as %q rather than `detected`", m.Structure) case "": t.Error("the live engine published no `structure` at all: this build then refuses to sell " + "chapter orders for every book, which is safe and wrong") default: t.Logf("the live engine published a structure word this build does not know (%q); "+ "chapter orders are withheld, which is the intended degradation", m.Structure) } // ⛔ AND THE ONE ASSERTION A FIXTURE COULD NEVER MAKE: the book-level `expected_usd` INCLUDES a // flat `book_once_usd`, and on a short book it DOMINATES. Measured on this very probe: two // chapters whose own bills come to fractions of a cent against a book-level bound of whole // dollars. It is pinned because the platform's whole order arithmetic rests on NOT using the // book-level figure as the base of a hold — doing so would put a flat two-dollar threshold under // every purchase and make a short book unbuyable again. if p.BookOnceUSD > 0 { units := p.ExpectedUSD - p.BookOnceUSD if units <= 0 { t.Fatalf("book_once_usd (%s) is not less than expected_usd (%s): the book-level bound is "+ "not a PART of the expected bill, and every order priced from the difference is wrong", p.BookOnceUSD.USD(), p.ExpectedUSD.USD()) } t.Logf("live projection: expected %s of which book-level %s; the units' own share is %s", p.ExpectedUSD.USD(), p.BookOnceUSD.USD(), units.USD()) } // The chapters' own bills and the book figure describe ONE book. Two numbers that must agree are // where a silent drift lives, and here they come from two different keys of one document. sum := int64(0) for _, c := range m.Chapters { sum += int64(c.Price.ExpectedUSD) } if want := int64(p.ExpectedUSD - p.BookOnceUSD); sum < want-int64(len(m.Chapters))-1 || sum > want+int64(len(m.Chapters))+1 { t.Errorf("the live chapters sum to %d micro-USD and the book figure leaves %d for them", sum, want) } } // The engine's manifest is the ONE document the order form is built on, so this build's idea of its // shape has to be checkable without a database and without a run. It is the same probe as above with // the assertions inverted: what the engine publishes that this build does NOT read is listed, so a // field arriving for the first time is a decision somebody takes rather than a value dropped. func TestWhatTheLiveManifestCarriesAndThisBuildDeclinesToRead(t *testing.T) { bin := os.Getenv("TM_PLATFORM_TEST_ENGINE_BIN") tpl := os.Getenv("TM_PLATFORM_TEST_BOOK_TEMPLATE") if bin == "" || tpl == "" { t.Skip("TM_PLATFORM_TEST_ENGINE_BIN and TM_PLATFORM_TEST_BOOK_TEMPLATE not set") } dir := writeReadProbeBook(t, tpl, "bk_SHAPEPROBE", "第1章 测试\n这是一个测试文本。\n") doc, _, err := readEngine(t.Context(), bin, dir, ManifestArgs(dir), maxManifest, "manifest") if err != nil { t.Fatalf("the live manifest did not run: %v", err) } // The version this build was written against. It is a SHAPE gate and not a value pin — see // ingest.KnownManifestVersion — and the point of asserting it against a live engine is that the // constant stops being a copy of a copy. m, err := ingest.DecodeManifest(doc) if err != nil { t.Fatalf("the live manifest did not decode: %v", err) } if m.Version != ingest.KnownManifestVersion { t.Errorf("the live engine publishes manifest %q and this build reads %s: the allowlist is a "+ "transcription of a document that has moved", m.Version, ingest.KnownManifestVersion) } // ⚠ ASKED OF THE DECODED BOOK FIGURE, not grepped out of the bytes. A raw `strings.Contains` for // `"expected_usd"` is satisfied by any CHAPTER's price object, so a rename of the BOOK-level key — // the one the whole order form is built on — would leave this green. The keys that have no twin // elsewhere in the document are still worth a grep, and they are the ones grepped. if m.Price == nil { t.Fatal("the live manifest carries no book-level `price` object at all") } if m.Price.ExpectedUSD <= 0 || m.Price.StepMaxUSD <= 0 || m.Price.SourceChars <= 0 { t.Errorf("a book-level price key did not arrive: %+v", *m.Price) } for _, key := range []string{`"book_once_usd"`, `"step_max_usd"`, `"structure"`} { if !strings.Contains(string(doc), key) { t.Errorf("the live manifest carries no %s: the platform's order form is built on it", key) } } } // writeReadProbeBook is writeProbeBook's READ-PATH twin: the deployment's own template, with this // book's identity on it and nothing else changed. // // ⛔ IT DELIBERATELY DOES NOT REWRITE THE PIPELINE, and both halves of that matter. // // It does not swap the stage models for a local one, because `manifest` reaches no provider: it is a // $0, key-less verb (D20.4) that ingests, cuts and prints. The swap exists so a WRITE-path probe does // not buy its calls, and buying is not on this path. // // And it does not inherit the contrast-artefact SKIP. That condition is the write path's — its own // message says «a live write-path probe cannot run» — because the bank contour mines during a // translate. A manifest mines nothing. Inheriting it cost this probe its whole existence: the seam // test that is supposed to be the one independent witness of the allowlist SKIPPED on every host // without a multi-megabyte word list nobody needs to read a manifest, and skipped silently, which is // the failure shape this project keeps paying for. // // ⚠ AND IT WANTS THE SHIPPING PIPELINE'S OWN NUMBERS, which is the positive reason rather than the // absence of a negative one: the projection this test reads is made of that pipeline's stages, its // prices and its gate budgets. Rewritten, the probe would measure a book nobody sells. func writeReadProbeBook(t *testing.T, tpl, bookID, source string) string { t.Helper() dir := t.TempDir() raw, err := os.ReadFile(tpl) if err != nil { t.Fatal(err) } var cfg map[string]any if err := yaml.Unmarshal(raw, &cfg); err != nil { t.Fatal(err) } cfg["book_id"], cfg["source_lang"], cfg["target_lang"] = bookID, "zh", "ru" cfg["source_file"] = "source.txt" out, err := yaml.Marshal(cfg) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, ConfigFile), out, 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "source.txt"), []byte(source), 0o600); err != nil { t.Fatal(err) } return dir }