#!/usr/bin/env python3 """P8-REVIEW: mutation harness. Plants one named mutation into the WORKING COPY, leaving the repository untouched, and prints the file it changed. Reverting is a copy back from the repo. Usage: plant.py plant|revert """ import shutil, subprocess, sys, os # Оба переопределяются: копия — там, где её положила сессия, репозиторий — источник, из которого # делается откат. Дефолты совпадают с рецептом в README этого каталога. REPO = os.environ.get("P8_REPO", os.path.expanduser("~/projects/textmachine/platform")) COPY = os.environ.get("P8_COPY", os.path.expanduser("~/tm-p8-review/mine/platform")) M = { # id: (file, old, new, packages that must be re-run) # --- дописано после ревью старшей моделью: улики M1/M2/M9 не были воспроизводимы из артефактов, # --- плюс шесть пере-проверок «выживших» на ПОЛНОЙ батарее вместо пакетного сабсета. "M1": ("internal/pgstore/credits.go", '\t\tif unit != nil {\n\t\t\treturn fmt.Errorf("%w: %s", ErrAttemptSpawned, *unit)\n\t\t}', '\t\t_ = unit // MUTATION M1: re-check removed', "ALL"), "M2": ("internal/runs/reconcile.go", '\tif bound != nil && *bound < committed {\n\t\tcommitted = *bound\n\t}', '\t_ = bound // MUTATION M2: SpendBound clamp removed', "ALL"), "M9": ("internal/metrics/metrics.go", '\tif unfinished {\n\t\tm.sweepUnfinished.WithLabelValues(name).Inc()\n\t}\n', '\t// MUTATION M9: an unfinished pass is not counted\n', "ALL"), "M11": ("internal/pgstore/runs.go", '\t\tselect min(a.spend_baseline_micro_usd)', '\t\tselect max(a.spend_baseline_micro_usd) -- MUTATION M11', "ALL"), "M12": ("internal/pgstore/sessions.go", 'now.Add(idleTTL), now.Add(maxAge))', 'now.Add(idleTTL), now.Add(100*maxAge))', "ALL"), "M13": ("internal/auth/cookie.go", 'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", -time.Second) }', 'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", time.Hour) } // MUTATION M13', "ALL"), "M14": ("internal/auth/cookie.go", '\tif ttl < 0 {\n\t\tmaxAge = -1\n\t}', '\tif ttl < 0 {\n\t\tmaxAge = 3600 // MUTATION M14\n\t}', "ALL"), "M15": ("cmd/tmplatformd/main.go", 'const loginJournalRetention = 180 * 24 * time.Hour', 'const loginJournalRetention = 180 * 365 * 24 * time.Hour // MUTATION M15', "ALL"), "M16": ("internal/pgstore/identity.go", '`delete from login_events where at < $1`', '`delete from login_events where at < $1 and 1=0`', "ALL"), "M3": ("internal/pgstore/credits.go", '\tif !applied {\n\t\t// The ledger already holds this attempt id, so nothing was debited. Reporting success\n\t\t// would spawn a run against credit that was never reserved.\n\t\treturn ErrDuplicateHold\n\t}', '\tif !applied {\n\t\treturn nil // MUTATION M3\n\t}', "./internal/pgstore/ ./internal/runs/"), "M4": ("internal/pgstore/credits.go", '\tif spent < 0 {\n\t\treturn fmt.Errorf("pgstore: spend cannot be negative, got %d", spent)\n\t}\n', '\t// MUTATION M4: negative-spend guard removed\n', "./internal/pgstore/ ./internal/runs/"), "M5": ("internal/runs/reconcile.go", '\tif committed < *l.SpendBaseline {\n\t\treturn 0, nil\n\t}\n', '\t// MUTATION M5: clamp-at-zero removed\n', "./internal/runs/ ./internal/pgstore/"), "M6": ("internal/auth/cookie.go", '\t\tSecure: !c.Insecure,', '\t\tSecure: false, // MUTATION M6', "./internal/auth/ ./internal/login/ ./internal/httpapi/ ./cmd/tmplatformd/"), "M7": ("internal/runs/reconcile.go", '\treturn context.WithTimeout(ctx, time.Until(deadline)/2)', '\treturn context.WithTimeout(ctx, time.Until(deadline)) // MUTATION M7', "./internal/runs/ ./cmd/tmplatformd/"), "M8": ("internal/metrics/metrics.go", '\tm.stalledRuns.Set(float64(r.StalledRuns))\n', '\t// MUTATION M8: the stalled gauge is never set\n', "./internal/metrics/ ./cmd/tmplatformd/ ./internal/runs/"), "M10": ("internal/metrics/metrics.go", '\tm.abandonedSurfaces.Set(float64(r.AbandonedSurfaces))\n', '\t// MUTATION M10: the abandoned-surfaces gauge is never set\n', "ALL"), } mid, action = sys.argv[1], sys.argv[2] f, old, new, pkgs = M[mid] src, dst = os.path.join(REPO, f), os.path.join(COPY, f) shutil.copyfile(src, dst) if action == "revert": print(f"{mid}: reverted {f}") sys.exit(0) s = open(dst).read() if old not in s: sys.exit(f"{mid}: ANCHOR NOT FOUND in {f}") open(dst, "w").write(s.replace(old, new, 1)) print(f"{mid}: planted in {f}; packages to run: {pkgs}")