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>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package human
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"orchestra/internal/domain"
|
||||
)
|
||||
|
||||
// ReviewObservation is one review the forge recorded on a pull request. It is
|
||||
// an observation, not a verdict Orchestra trusts: the trust boundary is the
|
||||
// actor, applied by the reconciler.
|
||||
type ReviewObservation struct {
|
||||
Actor string
|
||||
State string // approved | changes_requested | commented
|
||||
At time.Time
|
||||
Body string
|
||||
}
|
||||
|
||||
// PullRequestState is everything Orchestra needs to know about a submitted
|
||||
// pull request. HeadSHA is the commit the forge believes the pull request
|
||||
// carries, which is how a merge is tied back to a specific submission.
|
||||
type PullRequestState struct {
|
||||
ID string
|
||||
HeadSHA string
|
||||
State string // open | merged | closed
|
||||
MergeSHA string
|
||||
MergedAt time.Time
|
||||
Reviews []ReviewObservation
|
||||
Comments []Input
|
||||
}
|
||||
|
||||
// PullRequestSource reads the state of one submitted pull request. Polling is
|
||||
// enough: a webhook would add an inbound trust boundary for no new capability.
|
||||
type PullRequestSource interface {
|
||||
PullRequest(ctx context.Context, task domain.Task) (PullRequestState, error)
|
||||
}
|
||||
|
||||
// Trust decides whose words can move a task. Without it, a bot comment or
|
||||
// Orchestra's own reflection could reopen a finished implementation.
|
||||
type Trust struct {
|
||||
// Accepted, when non-empty, is the allow-list of actor identities. Empty
|
||||
// means anyone not explicitly ignored, which is only safe on a private
|
||||
// forge with no bots.
|
||||
Accepted []string
|
||||
// Ignored always loses, even when it appears in Accepted.
|
||||
Ignored []string
|
||||
}
|
||||
|
||||
// Allows reports whether this actor's words may move a task.
|
||||
func (t Trust) Allows(actor string) bool {
|
||||
actor = strings.TrimSpace(strings.ToLower(actor))
|
||||
if actor == "" {
|
||||
return false
|
||||
}
|
||||
for _, ignored := range t.Ignored {
|
||||
if strings.EqualFold(strings.TrimSpace(ignored), actor) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(t.Accepted) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, accepted := range t.Accepted {
|
||||
if strings.EqualFold(strings.TrimSpace(accepted), actor) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FeedbackAfter returns the trusted human input on a pull request that arrived
|
||||
// strictly after the submission. Anything at or before it was already visible
|
||||
// when the submission was made, so it cannot be a response to it.
|
||||
func (p PullRequestState) FeedbackAfter(provider string, submittedAt time.Time, trust Trust) []Input {
|
||||
var out []Input
|
||||
for _, c := range p.Comments {
|
||||
if !c.At.After(submittedAt) || !trust.Allows(c.Author) {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(c.Body) == "" {
|
||||
continue
|
||||
}
|
||||
if c.Provider == "" {
|
||||
c.Provider = provider
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
for _, r := range p.Reviews {
|
||||
if !r.At.After(submittedAt) || !trust.Allows(r.Actor) {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(r.Body) == "" && r.State != "changes_requested" {
|
||||
continue
|
||||
}
|
||||
body := strings.TrimSpace(r.Body)
|
||||
if body == "" {
|
||||
body = "changes requested with no comment"
|
||||
}
|
||||
out = append(out, Input{
|
||||
Provider: provider, ExternalID: "review:" + r.Actor + ":" + r.At.UTC().Format(time.RFC3339),
|
||||
Author: r.Actor, At: r.At, Body: body,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user