Files
orchestra/internal/review/review.go
T
kami 7f12c7fc37 v3 workflow: intent, phases, review, submission, enforcement, burn-in
The v3 stack, previously an uncommitted working tree, plus this session's two
units and the burn-in instrument. This commit is the burn-in build identity:
coordinator and worker must both report this revision before a task is created.

Workflow (earlier sessions, uncommitted until now): human decision events and
reduction, source cursors and reconcile-before-launch, turn-boundary
reconciliation, internal/agentctx as the single renderer, ace-fca phases with
sealed artifacts, the trajectory gate, bounded grilling, independent review,
task pr enforcement, and human review reflection.

Capability restrictions at the agent boundary: an authz.Agent surface at
GatedWrite may ask and may not act. It also fixes two bugs the unit exposed --
gated surfaces could not reach the two endpoints written for them, and
RequestHumanDecision would block an unowned task while rejecting a question
from the session that did own it.

Turn-boundary reconcile-failure escalation: a streak of consecutive failures
asks the session to hand off, fenced on the lease epoch, with reconcile_failure
as a real handoff reason. The worker was dropping the coordinator's verdict on
the floor; it now acts on it.

Burn-in: herdr.WriteLaunchContext dumps the exact agentctx.Build result to
<worktree>/.orchestra/launch.md at every launch, local and federated. BURNIN.md
is the runbook. deploy/build.sh stamps both binaries from one commit.

go build, go vet and go test ./... pass, 20 packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 18:31:20 +04:00

184 lines
5.7 KiB
Go

// Package review holds independent review state: the verified evidence a
// reviewer is given, and the bounded findings it returns.
//
// Independence is structural, not a request. The reviewer receives the diff,
// the contract, the decisions, and the accepted plan. It does not receive the
// implementation's transcript, handoff, or completion claims, so it has to
// reconstruct whether the diff satisfies the contract instead of agreeing with
// whoever wrote it.
package review
import (
"encoding/json"
"fmt"
"strings"
)
type Severity string
const (
// Blocker and Important both send the work back. Minor is reported and
// left to judgement.
//
// There is deliberately no "invalid" severity. Whether a finding was
// wrong is a conclusion the implementer or an operator reaches later, not
// something a reviewer can report about its own output.
Blocker Severity = "blocker"
Important Severity = "important"
Minor Severity = "minor"
)
func (s Severity) Valid() bool {
switch s {
case Blocker, Important, Minor:
return true
}
return false
}
// Blocking reports whether this severity returns the task to implementation.
func (s Severity) Blocking() bool { return s == Blocker || s == Important }
type Finding struct {
ID string `json:"id"`
Severity Severity `json:"severity"`
File string `json:"file"`
Line int `json:"line,omitempty"`
Claim string `json:"claim"`
Evidence string `json:"evidence"`
}
// Result is one review, bound to the exact commit it was performed against.
// A review is never a free-floating boolean: if the code moves, the review
// describes a tree that no longer exists.
type Result struct {
ResultSHA string `json:"result_sha"`
Findings []Finding `json:"findings"`
}
// Evidence is what the reviewer is given about the change itself. Every field
// is verified by Orchestra rather than reported by the implementer.
type Evidence struct {
BaseSHA string `json:"base_sha"`
ResultSHA string `json:"result_sha"`
Diff string `json:"diff"`
GateCommand string `json:"gate_command,omitempty"`
GateExit int `json:"gate_exit"`
GateOutput string `json:"gate_output,omitempty"`
}
const (
maxFindings = 40
maxField = 500
// MaxDiffBytes bounds what reaches a context window. A change too large to
// render is a change too large to review in one session.
MaxDiffBytes = 256 << 10
// MaxGateOutputBytes keeps a failing gate's log from crowding out the diff.
MaxGateOutputBytes = 8 << 10
)
func (r Result) Validate() error {
if len(r.ResultSHA) != 40 {
return fmt.Errorf("review: result_sha must be a full commit sha")
}
if len(r.Findings) > maxFindings {
return fmt.Errorf("review: %d findings exceeds the %d bound", len(r.Findings), maxFindings)
}
seen := map[string]bool{}
for i, f := range r.Findings {
if strings.TrimSpace(f.ID) == "" {
return fmt.Errorf("review: findings[%d].id is required", i)
}
if seen[f.ID] {
return fmt.Errorf("review: duplicate finding id %q", f.ID)
}
seen[f.ID] = true
if !f.Severity.Valid() {
return fmt.Errorf("review: findings[%d].severity %q is not blocker, important, or minor", i, f.Severity)
}
if err := field(fmt.Sprintf("findings[%d].file", i), f.File, true); err != nil {
return err
}
if strings.HasPrefix(f.File, "/") {
return fmt.Errorf("review: findings[%d].file must be repository-relative", i)
}
if f.Line < 0 {
return fmt.Errorf("review: findings[%d].line cannot be negative", i)
}
if err := field(fmt.Sprintf("findings[%d].claim", i), f.Claim, true); err != nil {
return err
}
if err := field(fmt.Sprintf("findings[%d].evidence", i), f.Evidence, true); err != nil {
return err
}
}
return nil
}
// Blocking returns the findings that send the work back.
func (r Result) Blocking() []Finding {
var out []Finding
for _, f := range r.Findings {
if f.Severity.Blocking() {
out = append(out, f)
}
}
return out
}
// Accepted reports whether this review lets the task proceed. Minor findings
// are reported and left to judgement rather than forced.
func (r Result) Accepted() bool { return len(r.Blocking()) == 0 }
func field(name, v string, required bool) error {
s := strings.TrimSpace(v)
if s == "" {
if required {
return fmt.Errorf("review: %s is required", name)
}
return nil
}
if len(s) > maxField {
return fmt.Errorf("review: %s exceeds %d characters", name, maxField)
}
if strings.ContainsAny(s, "\n\r") {
return fmt.Errorf("review: %s must be a single line", name)
}
return nil
}
func Encode(r Result) ([]byte, error) {
if err := r.Validate(); err != nil {
return nil, err
}
return json.Marshal(r)
}
func Decode(b []byte) (Result, error) {
var r Result
if err := json.Unmarshal(b, &r); err != nil {
return Result{}, fmt.Errorf("review artifact: %w", err)
}
return r, r.Validate()
}
// Instructions is the reviewer's whole brief. It is narrow on purpose: an open
// invitation produces a list of ways the reviewer would have written it
// instead, which is not review.
const Instructions = `Review the supplied diff against, in order:
1. the task goal and acceptance criteria
2. the human decisions and constraints
3. the repository rules
4. the accepted plan
5. observable correctness and regressions
Report only concrete findings supported by the diff or by repository evidence
you can point at. Every finding needs a file, a claim, and the evidence for it.
Severity: blocker if it is wrong or unsafe, important if it will cause a real
defect or contradicts a decision, minor otherwise.
Do not redesign the solution. Do not suggest optional refactors. Do not edit
any file. Do not report style preferences unless they violate a repository
rule. You are not implementing this task and you do not decide its lifecycle.`