package runs // P8-REVIEW axis 3, REFUTER probe against the finder's F5 ("the second guard of `run abandon` cannot // be predicted from the operator's table"). // // The guard is `unit != "" || baseline != nil` (pgstore/runs.go:465). The table prints UNIT for the // first and SPENT for the second, and SPENT is "?" EXACTLY when the baseline is NULL — the column is // `case when a.spend_baseline_micro_usd is null then null else ... end` (runs.go:374-375), so the // mapping is a bijection, not an inference. The operator's runbook states both halves of the guard // in prose: deploy/README.md:247 "Команда ОТКАЖЕТ, если попытка ещё называет юнит ИЛИ несёт базовую // линию траты". // // This asserts the bijection over all four states of the two columns. import ( "errors" "testing" "textmachine/platform/internal/pgstore" ) func TestRefuteTheAbandonRefusalIsExactlyWhatTheTwoPrintedColumnsSay(t *testing.T) { for _, tc := range []struct { name string sql string wantUnit string wantSpnt string }{ {"a unit and a baseline", `update run_attempts set reconcile_failures=5 where run_id=$1`, "named", "a figure"}, {"no unit, baseline kept (ReleaseSpawnClaim)", `update run_attempts set unit_name=null, reconcile_failures=5 where run_id=$1`, "(none)", "a figure"}, {"a unit, no baseline (pre-column attempt)", `update run_attempts set spend_baseline_micro_usd=null, reconcile_failures=5 where run_id=$1`, "named", "?"}, {"neither: never reached the engine", `update run_attempts set unit_name=null, spend_baseline_micro_usd=null, reconcile_failures=5 where run_id=$1`, "(none)", "?"}, } { t.Run(tc.name, func(t *testing.T) { f := newFixture(t, "20", 500) run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), CeilingChapters: 10}) if err != nil { t.Fatal(err) } if err := f.svc.Spawn(f.ctx, run.ID); err != nil { t.Fatal(err) } if _, err := f.store.Pool().Exec(f.ctx, tc.sql, run.ID); err != nil { t.Fatal(err) } rows, err := f.store.StalledRuns(f.ctx, StalledAfter) if err != nil { t.Fatal(err) } if len(rows) != 1 { t.Fatalf("%d rows", len(rows)) } unit, spent := "named", "?" if rows[0].UnitName == "" { unit = "(none)" } if rows[0].SpentMicroUSD != nil { spent = "a figure" } if unit != tc.wantUnit || spent != tc.wantSpnt { t.Fatalf("the table printed UNIT=%s SPENT=%s, want UNIT=%s SPENT=%s", unit, spent, tc.wantUnit, tc.wantSpnt) } err = f.store.AbandonRun(f.ctx, run.ID, "the host is gone", false, f.now) refused := errors.Is(err, pgstore.ErrRunMayHaveAProcess) // This is what the runbook tells the operator to read off the two columns. predicted := unit != "(none)" || spent != "?" t.Logf("UNIT=%-6s SPENT=%-8s -> runbook predicts refusal=%v, handle refused=%v (%v)", unit, spent, predicted, refused, err) if predicted != refused { t.Fatalf("NOT REFUTED: the table said %v and the handle did %v", predicted, refused) } }) } }