mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
cba058c6ab
The bushido continuous-validation backstop: watch main -> full gate on each new commit -> classify deterministic-vs-flaky (re-run N=3; the 18-30% flake rate makes naive escalation noise) -> on deterministic blocking FAIL: poison beacon (.refinery-poison + git note) + fix-bead (bd create) + alert. NEVER reverts (P2.5 quorum-gated revert deferred to ag-k99u). Backstop-not-gatekeeper: down = merges still succeed; resumes from .refinery-state. Injectable ports (git/bd/ gates adapters); core tested with fakes (deterministic escalates, flaky doesn't, green clears, never-reverts). ao refinery once|run; systemd unit + runbook for bushido. go test green. ag-qidx.
168 lines
4.9 KiB
Go
168 lines
4.9 KiB
Go
package refinery
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/boshu2/agentops/cli/internal/gates"
|
|
"github.com/boshu2/agentops/cli/internal/ports"
|
|
)
|
|
|
|
// NewProduction wires the refinery with git/bd/gates-backed adapters rooted at
|
|
// repoRoot. The registry is gates.Default (the seed registry).
|
|
func NewProduction(repoRoot string) *Refinery {
|
|
runner := gates.NewScriptRunner(repoRoot)
|
|
return &Refinery{
|
|
Commits: &gitCommitSource{repoRoot: repoRoot},
|
|
Gate: &gatesChecker{repoRoot: repoRoot, runner: runner},
|
|
Rerun: &gatesRerunner{repoRoot: repoRoot, runner: runner},
|
|
Beads: &bdBeadFiler{repoRoot: repoRoot},
|
|
Beacon: &fileBeacon{repoRoot: repoRoot},
|
|
Store: &fileStateStore{path: filepath.Join(repoRoot, ".refinery-state")},
|
|
RerunN: 3,
|
|
Log: func(s string) { fmt.Fprintln(os.Stderr, s) },
|
|
}
|
|
}
|
|
|
|
// --- CommitSource: origin/main HEAD via git ---
|
|
|
|
type gitCommitSource struct{ repoRoot string }
|
|
|
|
func (g *gitCommitSource) MainHead(ctx context.Context) (string, error) {
|
|
_, _ = run(ctx, g.repoRoot, "git", "fetch", "origin", "main", "--quiet") // best-effort
|
|
out, err := run(ctx, g.repoRoot, "git", "rev-parse", "origin/main")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(out), nil
|
|
}
|
|
|
|
// --- GateChecker: the full gate over gates.Default ---
|
|
|
|
type gatesChecker struct {
|
|
repoRoot string
|
|
runner ports.GateRunnerPort
|
|
}
|
|
|
|
func (g *gatesChecker) CheckFull(ctx context.Context) (*gates.Report, error) {
|
|
o := gates.NewOrchestrator(gates.Default, g.runner, gates.NewGitChangedFiles(g.repoRoot), g.repoRoot)
|
|
return o.Run(ctx, gates.RunOptions{Mode: gates.Full})
|
|
}
|
|
|
|
// --- Rerunner: re-run one check by ID ---
|
|
|
|
type gatesRerunner struct {
|
|
repoRoot string
|
|
runner ports.GateRunnerPort
|
|
}
|
|
|
|
func (g *gatesRerunner) Rerun(ctx context.Context, checkID string) (ports.GateVerdict, error) {
|
|
c, ok := gates.Default.Get(checkID)
|
|
if !ok {
|
|
return ports.GateVerdict{}, fmt.Errorf("refinery: unknown check %q", checkID)
|
|
}
|
|
if c.Run != nil {
|
|
return c.Run(ctx, gates.RunContext{RepoRoot: g.repoRoot, Mode: gates.Full})
|
|
}
|
|
return g.runner.Run(ctx, ports.GateRunRequest{Name: ports.GateName(c.Backing)})
|
|
}
|
|
|
|
// --- BeadFiler: bd create ---
|
|
|
|
type bdBeadFiler struct{ repoRoot string }
|
|
|
|
func (b *bdBeadFiler) FileFixBead(ctx context.Context, sha string, checks []string) (string, error) {
|
|
short := sha
|
|
if len(short) > 8 {
|
|
short = short[:8]
|
|
}
|
|
title := fmt.Sprintf("fix: main %s poisoned — deterministic gate failure (%s)", short, strings.Join(checks, ", "))
|
|
out, err := run(ctx, b.repoRoot, "bd", "create", title, "--type", "task", "--labels", "refinery,blocking", "--json")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var parsed struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if jerr := json.Unmarshal([]byte(out), &parsed); jerr != nil {
|
|
return "", nil // bead may have been created; ID just unparsed
|
|
}
|
|
return parsed.ID, nil
|
|
}
|
|
|
|
// --- Beacon: status file + best-effort git note ---
|
|
|
|
type fileBeacon struct{ repoRoot string }
|
|
|
|
type poisonFile struct {
|
|
SHA string `json:"sha"`
|
|
Checks []string `json:"checks"`
|
|
}
|
|
|
|
func (b *fileBeacon) path() string { return filepath.Join(b.repoRoot, ".refinery-poison") }
|
|
|
|
func (b *fileBeacon) Set(ctx context.Context, sha string, checks []string) error {
|
|
data, err := json.MarshalIndent(poisonFile{SHA: sha, Checks: checks}, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(b.path(), data, 0o644); err != nil { // #nosec G306 -- a beacon meant to be world-readable by any pusher
|
|
return err
|
|
}
|
|
// best-effort git note so the poison travels with the commit
|
|
_, _ = run(ctx, b.repoRoot, "git", "notes", "--ref=refinery", "add", "-f",
|
|
"-m", "POISON: "+strings.Join(checks, ", "), sha)
|
|
return nil
|
|
}
|
|
|
|
func (b *fileBeacon) Clear(ctx context.Context, sha string) error {
|
|
if err := os.Remove(b.path()); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
_, _ = run(ctx, b.repoRoot, "git", "notes", "--ref=refinery", "remove", sha)
|
|
return nil
|
|
}
|
|
|
|
// --- StateStore: JSON file ---
|
|
|
|
type fileStateStore struct{ path string }
|
|
|
|
func (s *fileStateStore) Load() (State, error) {
|
|
data, err := os.ReadFile(s.path)
|
|
if os.IsNotExist(err) {
|
|
return State{}, nil
|
|
}
|
|
if err != nil {
|
|
return State{}, err
|
|
}
|
|
var st State
|
|
if err := json.Unmarshal(data, &st); err != nil {
|
|
return State{}, fmt.Errorf("refinery: parse state %s: %w", s.path, err)
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
func (s *fileStateStore) Save(st State) error {
|
|
data, err := json.MarshalIndent(st, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(s.path, data, 0o644) // #nosec G306 -- non-secret refinery state
|
|
}
|
|
|
|
// run executes a command in dir and returns combined stdout.
|
|
func run(ctx context.Context, dir, name string, args ...string) (string, error) {
|
|
cmd := exec.CommandContext(ctx, name, args...)
|
|
cmd.Dir = dir
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("%s %s: %w", name, strings.Join(args, " "), err)
|
|
}
|
|
return string(out), nil
|
|
}
|