Witness: Verify commit on main before nuke (gt-v7zm7)

Add verifyCommitOnMain() check in HandleMerged to ensure the polecat's
commit is actually on main before allowing cleanup. This prevents work
loss when:
- MERGED signal is for a stale MR
- Merge attempt failed after signal was sent
- MR was already merged via different path

The verification uses git merge-base --is-ancestor to confirm the
polecat's HEAD is reachable from main.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/polecats/nux
2025-12-30 22:26:13 -08:00
committed by Steve Yegge
parent 12543d1450
commit c28a0d5c51

View File

@@ -5,10 +5,13 @@ import (
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/workspace"
)
// HandlerResult tracks the result of handling a protocol message.
@@ -151,6 +154,22 @@ func HandleMerged(workDir, rigName string, msg *mail.Message) *HandlerResult {
return result
}
// Verify the polecat's commit is actually on main before allowing nuke.
// This prevents work loss when MERGED signal is for a stale MR or the merge failed.
onMain, err := verifyCommitOnMain(workDir, rigName, payload.PolecatName)
if err != nil {
// Couldn't verify - log warning but continue with other checks
// The polecat may not exist anymore (already nuked) which is fine
result.Action = fmt.Sprintf("warning: couldn't verify commit on main for %s: %v", payload.PolecatName, err)
} else if !onMain {
// Commit is NOT on main - don't nuke!
result.Handled = true
result.WispCreated = wispID
result.Error = fmt.Errorf("polecat %s commit is NOT on main - MERGED signal may be stale, DO NOT NUKE", payload.PolecatName)
result.Action = fmt.Sprintf("BLOCKED: %s commit not verified on main, merge may have failed", payload.PolecatName)
return result
}
// ZFC #10: Check cleanup_status before allowing nuke
// This prevents work loss when MERGED signal arrives for stale MRs or
// when polecat has new unpushed work since the MR was created.
@@ -535,3 +554,43 @@ func UpdateCleanupWispState(workDir, wispID, newState string) error {
return nil
}
// verifyCommitOnMain checks if the polecat's current commit is on main.
// This prevents nuking a polecat whose work wasn't actually merged.
//
// Returns:
// - true, nil: commit is verified on main
// - false, nil: commit is NOT on main (don't nuke!)
// - false, error: couldn't verify (treat as unsafe)
func verifyCommitOnMain(workDir, rigName, polecatName string) (bool, error) {
// Find town root from workDir
townRoot, err := workspace.Find(workDir)
if err != nil || townRoot == "" {
return false, fmt.Errorf("finding town root: %v", err)
}
// Construct polecat path: <townRoot>/<rigName>/polecats/<polecatName>
polecatPath := filepath.Join(townRoot, rigName, "polecats", polecatName)
// Get git for the polecat worktree
g := git.NewGit(polecatPath)
// Get the current HEAD commit SHA
commitSHA, err := g.Rev("HEAD")
if err != nil {
return false, fmt.Errorf("getting polecat HEAD: %w", err)
}
// Verify it's an ancestor of main (i.e., it's been merged)
// We use the polecat's git context to check main
isOnMain, err := g.IsAncestor(commitSHA, "origin/main")
if err != nil {
// Try without origin/ prefix in case remote isn't set up
isOnMain, err = g.IsAncestor(commitSHA, "main")
if err != nil {
return false, fmt.Errorf("checking if commit is on main: %w", err)
}
}
return isOnMain, nil
}