Files
gastown/internal/protocol/refinery_handlers.go
Steve Yegge 96ffbf0188 Implement Witness-Refinery protocol handlers (gt-m5w4g.2)
Add internal/protocol/ package with handlers for:
- MERGE_READY (witness→refinery): worker done, branch ready
- MERGED (refinery→witness): merge succeeded, cleanup ok
- MERGE_FAILED (refinery→witness): merge failed, needs rework
- REWORK_REQUEST (refinery→witness): rebase needed due to conflicts

Package structure:
- types.go: Protocol message types and payload structs
- messages.go: Message builders and body parsers
- handlers.go: Handler interface and registry for dispatch
- witness_handlers.go: DefaultWitnessHandler implementation
- refinery_handlers.go: DefaultRefineryHandler implementation
- protocol_test.go: Comprehensive test coverage

Also updated docs/mail-protocol.md with:
- MERGE_FAILED and REWORK_REQUEST message type documentation
- Merge failure and rebase required flow diagrams
- Reference to internal/protocol/ package

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 10:48:00 -08:00

148 lines
4.9 KiB
Go

package protocol
import (
"fmt"
"io"
"os"
"time"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/mrqueue"
)
// DefaultRefineryHandler provides the default implementation for Refinery protocol handlers.
// It receives MERGE_READY messages from the Witness and adds work to the merge queue.
type DefaultRefineryHandler struct {
// Rig is the name of the rig this refinery processes.
Rig string
// WorkDir is the working directory for operations.
WorkDir string
// Queue is the merge request queue.
Queue *mrqueue.Queue
// Router is used to send mail messages.
Router *mail.Router
// Output is where to write status messages.
Output io.Writer
}
// NewRefineryHandler creates a new DefaultRefineryHandler.
func NewRefineryHandler(rig, workDir string) *DefaultRefineryHandler {
return &DefaultRefineryHandler{
Rig: rig,
WorkDir: workDir,
Queue: mrqueue.New(workDir),
Router: mail.NewRouter(workDir),
Output: os.Stdout,
}
}
// SetOutput sets the output writer for status messages.
func (h *DefaultRefineryHandler) SetOutput(w io.Writer) {
h.Output = w
}
// HandleMergeReady handles a MERGE_READY message from Witness.
// When a polecat's work is verified and ready, the Refinery:
// 1. Validates the merge request
// 2. Adds it to the merge queue
// 3. Acknowledges receipt
func (h *DefaultRefineryHandler) HandleMergeReady(payload *MergeReadyPayload) error {
fmt.Fprintf(h.Output, "[Refinery] MERGE_READY received for polecat %s\n", payload.Polecat)
fmt.Fprintf(h.Output, " Branch: %s\n", payload.Branch)
fmt.Fprintf(h.Output, " Issue: %s\n", payload.Issue)
fmt.Fprintf(h.Output, " Verified: %s\n", payload.Verified)
// Validate required fields
if payload.Branch == "" {
return fmt.Errorf("missing branch in MERGE_READY payload")
}
if payload.Polecat == "" {
return fmt.Errorf("missing polecat in MERGE_READY payload")
}
// Create merge request (ID is generated by Submit if empty)
mr := &mrqueue.MR{
Branch: payload.Branch,
Worker: payload.Polecat,
SourceIssue: payload.Issue,
Target: "main", // Default target, could be passed in payload
Rig: payload.Rig,
Title: fmt.Sprintf("Merge %s work on %s", payload.Polecat, payload.Issue),
CreatedAt: time.Now(),
}
// Add to queue
if err := h.Queue.Submit(mr); err != nil {
fmt.Fprintf(h.Output, "[Refinery] Error adding to queue: %v\n", err)
return fmt.Errorf("failed to add merge request to queue: %w", err)
}
fmt.Fprintf(h.Output, "[Refinery] ✓ Added to merge queue: %s\n", mr.ID)
fmt.Fprintf(h.Output, " Queue length: %d\n", h.Queue.Count())
return nil
}
// SendMerged sends a MERGED message to the Witness.
// Called by the Refinery after successfully merging a branch.
func (h *DefaultRefineryHandler) SendMerged(polecat, branch, issue, targetBranch, mergeCommit string) error {
msg := NewMergedMessage(h.Rig, polecat, branch, issue, targetBranch, mergeCommit)
return h.Router.Send(msg)
}
// SendMergeFailed sends a MERGE_FAILED message to the Witness.
// Called by the Refinery when a merge fails.
func (h *DefaultRefineryHandler) SendMergeFailed(polecat, branch, issue, targetBranch, failureType, errorMsg string) error {
msg := NewMergeFailedMessage(h.Rig, polecat, branch, issue, targetBranch, failureType, errorMsg)
return h.Router.Send(msg)
}
// SendReworkRequest sends a REWORK_REQUEST message to the Witness.
// Called by the Refinery when a branch has conflicts.
func (h *DefaultRefineryHandler) SendReworkRequest(polecat, branch, issue, targetBranch string, conflictFiles []string) error {
msg := NewReworkRequestMessage(h.Rig, polecat, branch, issue, targetBranch, conflictFiles)
return h.Router.Send(msg)
}
// NotifyMergeOutcome is a convenience method that sends the appropriate message
// based on the merge result.
type MergeOutcome struct {
// Success indicates whether the merge was successful.
Success bool
// Conflict indicates the failure was due to conflicts (needs rebase).
Conflict bool
// FailureType categorizes the failure (e.g., "tests", "build").
FailureType string
// Error is the error message if the merge failed.
Error string
// MergeCommit is the SHA of the merge commit on success.
MergeCommit string
// ConflictFiles lists files with conflicts (if Conflict is true).
ConflictFiles []string
}
// NotifyMergeOutcome sends the appropriate protocol message based on the outcome.
func (h *DefaultRefineryHandler) NotifyMergeOutcome(polecat, branch, issue, targetBranch string, outcome MergeOutcome) error {
if outcome.Success {
return h.SendMerged(polecat, branch, issue, targetBranch, outcome.MergeCommit)
}
if outcome.Conflict {
return h.SendReworkRequest(polecat, branch, issue, targetBranch, outcome.ConflictFiles)
}
return h.SendMergeFailed(polecat, branch, issue, targetBranch, outcome.FailureType, outcome.Error)
}
// Ensure DefaultRefineryHandler implements RefineryHandler.
var _ RefineryHandler = (*DefaultRefineryHandler)(nil)