207 lines
9.8 KiB
Go
207 lines
9.8 KiB
Go
package runner
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"net"
|
||
"net/http"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"textmachine/platform/internal/ingest"
|
||
)
|
||
|
||
// The P10 mine, reproduced against the REAL engine (D39.165 §3; workflow errata 28.08-и): a bank
|
||
// correction moves the snapshot, and the engine's guard stops a CONTINUING run loudly unless
|
||
// `--resnapshot` travels — which this platform did not pass until P10. The state must be the
|
||
// errata's: the edit jobs EXIST before the correction (a finished book is the flagship case) —
|
||
// against fresh jobs the guard is silent and the reproduction is green without the fix, proving
|
||
// nothing. That precondition is therefore ASSERTED, not assumed: the no-flag run must die, and die
|
||
// naming the flag.
|
||
//
|
||
// Free of provider keys and of paid calls: the pipeline is the deployment's local $0 pair, served
|
||
// by an in-test OpenAI-compatible stub (the P9 live-probe's provider, ported); the guard itself
|
||
// fires before any model call.
|
||
//
|
||
// Mutation caught: TranslateArgs dropping either consent flag; the guard notice changing shape.
|
||
func TestTheSnapshotGuardIsLoudWithoutTheFlagsAndPassesWithThem(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 snapshot-guard mine is not reproduced against a real engine")
|
||
}
|
||
// The deployment's models.yaml points the $0 pair at this fixed address; a busy port means
|
||
// another stand is live on this host — skip loudly rather than fight it. ⚠ It is a question about
|
||
// the ADDRESS and never about money: the guard above is what answers that one.
|
||
ln, err := new(net.ListenConfig).Listen(t.Context(), "tcp", "127.0.0.1:11434")
|
||
if err != nil {
|
||
t.Skipf("the local provider address is busy (another stand?): %v", err)
|
||
}
|
||
srv := &http.Server{Handler: http.HandlerFunc(fakeProvider)}
|
||
go func() { _ = srv.Serve(ln) }()
|
||
defer srv.Close()
|
||
|
||
dir := t.TempDir()
|
||
writeProbeBook(t, tpl, dir, "bk_MINEPROBE")
|
||
// ⛔ CONSENT, ASKED OF WHAT WILL ACTUALLY RUN — and asked HERE rather than of the template, which is
|
||
// the correction the acceptance of 11.09 forced. `writeProbeBook` does not hand the engine the
|
||
// deployment's pipeline: it derives a zero-cost one from it (`zeroCostPipeline`) and points the
|
||
// book at that. A guard on the TEMPLATE therefore judged a document nothing loads — it refused
|
||
// stands whose real configuration was free, and it could never see a vendor that SURVIVED the
|
||
// derivation, which is the only way one reaches a model here. The derivation is a rewriter by key
|
||
// (`stage["model"]`, `delete escalate_to`, `gates[*].model`, `classify_model`) — the same shape
|
||
// this guard exists to be independent of — so what it produces is exactly what has to be checked.
|
||
if paid, decided, why := paidModelIn(filepath.Join(dir, ConfigFile)); os.Getenv(liveConsentEnv) != "1" {
|
||
switch {
|
||
case !decided:
|
||
t.Skipf("what this probe would hand the engine cannot be resolved to the models it would "+
|
||
"call (%s), so it is not started. Fix the deployment template, or set %s=1 to say you "+
|
||
"mean it", why, liveConsentEnv)
|
||
case paid != "":
|
||
// ⛔ FAIL, NOT SKIP, AND THE DIFFERENCE IS WHOSE ARTEFACT THIS IS. What the guard just judged
|
||
// was written by `writeProbeBook` two lines up, from this deployment's template: the
|
||
// derivation's whole promise is that what it produces is free. A paid render is therefore a
|
||
// DEFECT of ours and not a condition of the host — and answering it with a skip would file
|
||
// the one failure this guard exists to catch under «nothing to see».
|
||
//
|
||
// It is also what makes the guard's AIM measurable: point it back at the template and a
|
||
// stand whose template is paid turns red here, which is the mutation that survived a skip
|
||
// (acceptance of 11.09, finding 1).
|
||
t.Fatalf("the configuration this probe would hand the engine names %q, whose provider is not "+
|
||
"on this host: running the engine's translate under it would spend real credit. This file "+
|
||
"is produced by zeroCostPipeline from the deployment template, so a vendor in it is a hole "+
|
||
"in that derivation — fix it rather than the guard. (%s=1 says you mean it.)",
|
||
paid, liveConsentEnv)
|
||
}
|
||
}
|
||
// Chapter 1 carries the term (its units re-render after the correction and re-translate at $0
|
||
// stub prices); chapter 2 does not (its units are the $0 re-pin the flag permits).
|
||
source := "第一章 山路\n\n方源走在山路上,方源看着远方的云。\n\n第二章 夜色\n\n老人在夜色中叹息,风吹过山谷。\n"
|
||
if err := os.WriteFile(filepath.Join(dir, "source.txt"), []byte(source), 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
ceiling := []string{"--ceiling-usd", "1.000000"}
|
||
run := func(args []string) (int, string) {
|
||
cmd := exec.CommandContext(t.Context(), bin, args...)
|
||
cmd.Dir = dir
|
||
out, _ := cmd.CombinedOutput()
|
||
return cmd.ProcessState.ExitCode(), string(out)
|
||
}
|
||
// Prime: a full run through BOTH waves. After it the edit jobs exist — the errata's state.
|
||
if code, out := run(TranslateArgs(dir, false, "", false, 0, 0, ceiling)); code != 0 {
|
||
t.Fatalf("the priming run failed (%d): %s", code, lastLines(out, 12))
|
||
}
|
||
// The bank moves through the live verb — the exact door P9 landed.
|
||
doc, err := ingest.EncodeDecisions("bk_MINEPROBE", []ingest.BankDecision{
|
||
{Action: "approve", Src: "方源", Sense: "", Dst: "Фан Юань-P10", Kind: "name", Note: "P10 mine probe"},
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
decisions := filepath.Join(t.TempDir(), "decisions.json")
|
||
if err := os.WriteFile(decisions, doc, 0o600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out, err := New(nil).BankApply(t.Context(), bin, dir, decisions, false); err != nil ||
|
||
out.ExitCode != 0 || !out.Report.Changed {
|
||
t.Fatalf("the live correction did not land: %+v (%v; stderr %q)", out, err, out.Stderr)
|
||
}
|
||
// Post-fix invariant, half one — and the errata's precondition in the same breath: WITHOUT the
|
||
// flag the continuing run dies loudly, naming the flag. A green run here means the edit jobs
|
||
// did not exist and this fixture proves nothing about the mine.
|
||
code, out := run(TranslateArgs(dir, false, "", false, 0, 0, ceiling))
|
||
if code == 0 {
|
||
t.Fatal("no snapshot guard fired on a moved bank: the edit-job precondition is not met " +
|
||
"(errata 28.08-и) and this reproduction is degenerate")
|
||
}
|
||
if !strings.Contains(out, "--resnapshot") {
|
||
t.Fatalf("the guard's stop does not name --resnapshot (%d): %s", code, lastLines(out, 6))
|
||
}
|
||
// Half two: the flags the platform now stores on the run row and renders through TranslateArgs
|
||
// carry the same run through — re-pins at $0, re-translates the term's units at stub prices.
|
||
// The engine names its pre-run restore point by the SECOND; on this $0 stub two translates fit
|
||
// in one, and the second refuses to overwrite the first's backup — wait the second out.
|
||
time.Sleep(1100 * time.Millisecond)
|
||
// The consent travels live too — the funded cap the platform renders (P10): the projection here
|
||
// is $0 (stub prices), well under the cap, so the engine accepts and the flag's whole live path
|
||
// is exercised rather than left to the argv pin alone.
|
||
if code, out := run(TranslateArgs(dir, false, "", true, 30_000, 0, ceiling)); code != 0 {
|
||
t.Fatalf("with the consents the run still dies (%d): %s", code, lastLines(out, 6))
|
||
}
|
||
}
|
||
|
||
func lastLines(s string, n int) string {
|
||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||
if len(lines) > n {
|
||
lines = lines[len(lines)-n:]
|
||
}
|
||
return strings.Join(lines, "\n")
|
||
}
|
||
|
||
// fakeProvider is the P9 live-probe's $0 stand-in (fake_provider.py), ported: prose for the
|
||
// translator and editor, TSV for the terminology roles. The probe proves the CHAIN, not quality.
|
||
func fakeProvider(w http.ResponseWriter, r *http.Request) {
|
||
var req struct {
|
||
Model string `json:"model"`
|
||
Messages []struct {
|
||
Role string `json:"role"`
|
||
Content string `json:"content"`
|
||
} `json:"messages"`
|
||
}
|
||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||
var system, user strings.Builder
|
||
for _, m := range req.Messages {
|
||
switch m.Role {
|
||
case "system":
|
||
system.WriteString(m.Content)
|
||
case "user":
|
||
user.WriteString(m.Content)
|
||
}
|
||
}
|
||
content := proseFor(system.String(), user.String())
|
||
resp := map[string]any{
|
||
"id": "fake-1", "object": "chat.completion", "model": req.Model,
|
||
"choices": []map[string]any{{
|
||
"index": 0,
|
||
"message": map[string]any{"role": "assistant", "content": content},
|
||
"finish_reason": "stop",
|
||
}},
|
||
"usage": map[string]any{"prompt_tokens": 100, "completion_tokens": max(1, len(content)/3),
|
||
"total_tokens": 100 + max(1, len(content)/3)},
|
||
}
|
||
w.Header().Set("Content-Type", "application/json")
|
||
_ = json.NewEncoder(w).Encode(resp)
|
||
}
|
||
|
||
var termKeys = regexp.MustCompile(`(?m)^key: (.+)$`)
|
||
|
||
func proseFor(system, user string) string {
|
||
if strings.Contains(system, "терминолог") {
|
||
var b strings.Builder
|
||
for i, m := range termKeys.FindAllStringSubmatch(user, -1) {
|
||
fmt.Fprintf(&b, "%s\tЗаглушка-%d\t55\n", m[1], i+1)
|
||
}
|
||
return b.String()
|
||
}
|
||
if strings.Contains(system, "классиф") || strings.Contains(system, "класс (name | place | title | term)") {
|
||
var b strings.Builder
|
||
for _, m := range termKeys.FindAllStringSubmatch(user, -1) {
|
||
fmt.Fprintf(&b, "%s\tname\n", m[1])
|
||
}
|
||
return b.String()
|
||
}
|
||
const filler = "Фан Юань неторопливо шёл по горной тропе, и ветер приносил запах трав. " +
|
||
"Старейшина посмотрел на него и тяжело вздохнул, вспоминая давние годы. " +
|
||
"В долине клубился туман, и где-то вдалеке кричала ночная птица. "
|
||
out := filler
|
||
for len(out) < len(user) {
|
||
out += filler
|
||
}
|
||
return out[:max(len(user), len(filler))]
|
||
}
|