Files
orchestra/internal/domain/planprogress.go
T
kami b5c37f693b Bind a manual sign-off to the tree it was given against
F63, found live on run 19. A manual check on these projects is a human
reading what the code prints. RecordPlanPhaseVerification asked only
whether a sign-off for that plan and phase existed, and one exists forever,
so rerunning a phase's automated checks at a new commit carried the human
half along with it. The rig proved it twice: two operator commits and two
re-verification requests, each coming back verified without anyone looking.

The reducer now records which tree the human confirmed, the record carries
it forward as provenance, and a run whose commit does not match it waits
for the human again. A sign-off given before any run has no confirmed tree
and still counts, so the ordinary ordering is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
2026-08-29 19:41:50 +04:00

143 lines
5.5 KiB
Go

package domain
import (
"fmt"
"strings"
"time"
)
// EventPlanPhaseVerified records that Orchestra ran a plan phase's automated
// verification and what happened. The implementer never emits it: an agent may
// request verification, and only the plane can establish it.
const EventPlanPhaseVerified = "PlanPhaseVerified"
// PlanPhaseStatus is what Orchestra established about one phase.
type PlanPhaseStatus string
const (
// PlanPhaseInProgress is the default and the outcome of a failed run. It
// is never written by a request, only left in place by one.
PlanPhaseInProgress PlanPhaseStatus = "in_progress"
// PlanPhaseAwaitingManual means every automated check passed and manual
// steps remain. A human signs those off; the agent cannot.
PlanPhaseAwaitingManual PlanPhaseStatus = "awaiting_manual_verification"
// PlanPhaseVerified means nothing further is required for this phase.
PlanPhaseVerified PlanPhaseStatus = "verified"
)
func (s PlanPhaseStatus) Valid() bool {
switch s {
case PlanPhaseInProgress, PlanPhaseAwaitingManual, PlanPhaseVerified:
return true
}
return false
}
// PlanPhaseRequestStatus is the single value an implementer may write. Every
// other status is a conclusion Orchestra reaches, so allowing an agent to
// assert one would let it declare its own work verified.
const PlanPhaseRequestStatus = "ready_for_verification"
// PlanPhaseRecord is one verification run, bound to the plan it belongs to and
// the commit it ran against.
//
// Both bindings are load-bearing. Without PlanRef, a phase verified under plan
// A survives into plan B, which is the same class of bug as a review that
// outlives the commit it examined. Without AtSHA, "verified" outlives the code
// that made it true.
type PlanPhaseRecord struct {
PlanRef string `json:"plan_ref"`
PhaseID string `json:"phase_id"`
Status PlanPhaseStatus `json:"status"`
Commands [][]string `json:"commands,omitempty"`
ExitCodes []int `json:"exit_codes,omitempty"`
AtSHA string `json:"at_sha"`
// EvidenceRef is the CAS ref of the captured command output.
EvidenceRef string `json:"evidence_ref,omitempty"`
At time.Time `json:"at"`
// ManualAtSHA is the tree a human was actually looking at when they signed
// this phase off. A manual check on most projects is a human reading
// output, so a sign-off establishes something about one tree and nothing
// about the next one (F63). Rerunning the automated half re-establishes it
// at the new commit; the manual half has to be given again, and this is
// what makes the difference visible instead of assumed.
ManualAtSHA string `json:"manual_at_sha,omitempty"`
}
// Stale reports whether the tree has moved since this phase was verified. A
// stale record is retained and labelled rather than discarded: it is still
// true that the phase passed at that commit, and hiding it would lose the
// provenance. What it must never do is read as current.
func (r PlanPhaseRecord) Stale(headSHA string) bool {
return headSHA != "" && r.AtSHA != "" && r.AtSHA != headSHA
}
// PlanProgress is the durable verification state for one accepted plan.
type PlanProgress struct {
// PlanRef is the plan these records belong to. A record from a superseded
// plan is never counted, so a replan cannot inherit progress it did not
// earn.
PlanRef string `json:"plan_ref"`
Phases []PlanPhaseRecord `json:"phases,omitempty"`
}
// PlanPhases returns the records that belong to the currently accepted plan.
// A task whose plan was superseded reports none, whatever the log still holds.
func (t Task) PlanPhases() []PlanPhaseRecord {
if t.PlanProgress == nil || t.PlanRef == "" || t.PlanProgress.PlanRef != t.PlanRef {
return nil
}
return t.PlanProgress.Phases
}
// PlanPhase returns the record for one phase of the accepted plan.
func (t Task) PlanPhase(id string) (PlanPhaseRecord, bool) {
for _, r := range t.PlanPhases() {
if r.PhaseID == id {
return r, true
}
}
return PlanPhaseRecord{}, false
}
// PlanPhaseSubject is the decision subject a manual sign-off carries. The key
// binds the approval to one phase of one plan, so a later "looks good" on an
// unrelated thread cannot satisfy a gate nobody was talking about.
func PlanPhaseSubject(planRef, phaseID string) string {
return "plan_phase_verification:" + planRef + ":" + phaseID
}
const maxVerificationCommands = 16
func ValidatePlanPhaseVerified(p map[string]any) error {
planRef, _ := p["plan_ref"].(string)
if strings.TrimSpace(planRef) == "" {
return fmt.Errorf("%w: plan_ref required", ErrInvalid)
}
phaseID, _ := p["phase_id"].(string)
if strings.TrimSpace(phaseID) == "" {
return fmt.Errorf("%w: phase_id required", ErrInvalid)
}
status, _ := p["status"].(string)
if !PlanPhaseStatus(status).Valid() {
return fmt.Errorf("%w: status %q is not a plan phase status", ErrInvalid, status)
}
if sha, _ := p["at_sha"].(string); len(sha) != 40 {
return fmt.Errorf("%w: at_sha must be a full commit sha", ErrInvalid)
}
codes, _ := p["exit_codes"].([]any)
if len(codes) > maxVerificationCommands {
return fmt.Errorf("%w: %d exit codes exceeds the %d command bound", ErrInvalid, len(codes), maxVerificationCommands)
}
// A verified phase whose commands failed would be a contradiction the
// reducer could not detect later.
if PlanPhaseStatus(status) != PlanPhaseInProgress {
for _, c := range codes {
if code, ok := c.(float64); !ok || code != 0 {
return fmt.Errorf("%w: status %q cannot carry a non-zero exit code", ErrInvalid, status)
}
}
}
return nil
}