Files
gastown/internal/protocol/types.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

183 lines
5.4 KiB
Go

// Package protocol provides inter-agent protocol message handling.
//
// This package defines protocol message types for Witness-Refinery communication
// and provides handlers for processing these messages.
//
// Protocol Message Types:
// - MERGE_READY: Witness → Refinery (branch ready for merge)
// - MERGED: Refinery → Witness (merge succeeded, cleanup ok)
// - MERGE_FAILED: Refinery → Witness (merge failed, needs rework)
// - REWORK_REQUEST: Refinery → Witness (rebase needed)
package protocol
import (
"strings"
"time"
)
// MessageType identifies the protocol message type.
type MessageType string
const (
// TypeMergeReady is sent from Witness to Refinery when a polecat's work
// is verified and ready for merge queue processing.
// Subject format: "MERGE_READY <polecat-name>"
TypeMergeReady MessageType = "MERGE_READY"
// TypeMerged is sent from Refinery to Witness when a branch has been
// successfully merged to the target branch.
// Subject format: "MERGED <polecat-name>"
TypeMerged MessageType = "MERGED"
// TypeMergeFailed is sent from Refinery to Witness when a merge attempt
// failed (tests, build, or other non-conflict error).
// Subject format: "MERGE_FAILED <polecat-name>"
TypeMergeFailed MessageType = "MERGE_FAILED"
// TypeReworkRequest is sent from Refinery to Witness when a polecat's
// branch needs rebasing due to conflicts with the target branch.
// Subject format: "REWORK_REQUEST <polecat-name>"
TypeReworkRequest MessageType = "REWORK_REQUEST"
)
// ParseMessageType extracts the protocol message type from a mail subject.
// Returns empty string if subject doesn't match a known protocol type.
func ParseMessageType(subject string) MessageType {
subject = strings.TrimSpace(subject)
// Check each known prefix
prefixes := []MessageType{
TypeMergeReady,
TypeMerged,
TypeMergeFailed,
TypeReworkRequest,
}
for _, prefix := range prefixes {
if strings.HasPrefix(subject, string(prefix)) {
return prefix
}
}
return ""
}
// MergeReadyPayload contains the data for a MERGE_READY message.
// Sent by Witness after verifying polecat work is complete.
type MergeReadyPayload struct {
// Branch is the polecat's work branch (e.g., "polecat/Toast/gt-abc").
Branch string `json:"branch"`
// Issue is the beads issue ID the polecat completed.
Issue string `json:"issue"`
// Polecat is the worker name.
Polecat string `json:"polecat"`
// Rig is the rig name containing the polecat.
Rig string `json:"rig"`
// Verified contains verification notes.
Verified string `json:"verified,omitempty"`
// Timestamp is when the message was created.
Timestamp time.Time `json:"timestamp"`
}
// MergedPayload contains the data for a MERGED message.
// Sent by Refinery after successful merge to target branch.
type MergedPayload struct {
// Branch is the source branch that was merged.
Branch string `json:"branch"`
// Issue is the beads issue ID.
Issue string `json:"issue"`
// Polecat is the worker name.
Polecat string `json:"polecat"`
// Rig is the rig name.
Rig string `json:"rig"`
// MergedAt is when the merge completed.
MergedAt time.Time `json:"merged_at"`
// MergeCommit is the SHA of the merge commit.
MergeCommit string `json:"merge_commit,omitempty"`
// TargetBranch is the branch merged into (e.g., "main").
TargetBranch string `json:"target_branch"`
}
// MergeFailedPayload contains the data for a MERGE_FAILED message.
// Sent by Refinery when merge fails due to tests, build, or other errors.
type MergeFailedPayload struct {
// Branch is the source branch that failed to merge.
Branch string `json:"branch"`
// Issue is the beads issue ID.
Issue string `json:"issue"`
// Polecat is the worker name.
Polecat string `json:"polecat"`
// Rig is the rig name.
Rig string `json:"rig"`
// FailedAt is when the failure occurred.
FailedAt time.Time `json:"failed_at"`
// FailureType categorizes the failure (tests, build, push, etc.).
FailureType string `json:"failure_type"`
// Error is the error message.
Error string `json:"error"`
// TargetBranch is the branch we tried to merge into.
TargetBranch string `json:"target_branch"`
}
// ReworkRequestPayload contains the data for a REWORK_REQUEST message.
// Sent by Refinery when a polecat's branch has conflicts requiring rebase.
type ReworkRequestPayload struct {
// Branch is the source branch that needs rebasing.
Branch string `json:"branch"`
// Issue is the beads issue ID.
Issue string `json:"issue"`
// Polecat is the worker name.
Polecat string `json:"polecat"`
// Rig is the rig name.
Rig string `json:"rig"`
// RequestedAt is when the rework was requested.
RequestedAt time.Time `json:"requested_at"`
// TargetBranch is the branch to rebase onto.
TargetBranch string `json:"target_branch"`
// ConflictFiles lists files with conflicts (if known).
ConflictFiles []string `json:"conflict_files,omitempty"`
// Instructions provides specific rebase instructions.
Instructions string `json:"instructions,omitempty"`
}
// IsProtocolMessage returns true if the subject matches a known protocol type.
func IsProtocolMessage(subject string) bool {
return ParseMessageType(subject) != ""
}
// ExtractPolecat extracts the polecat name from a protocol message subject.
// Subject format: "TYPE <polecat-name>"
func ExtractPolecat(subject string) string {
subject = strings.TrimSpace(subject)
parts := strings.SplitN(subject, " ", 2)
if len(parts) < 2 {
return ""
}
return strings.TrimSpace(parts[1])
}