Let Orchestra establish plan progress instead of the implementer asserting it
A detailed plan that nothing enforces is a document. This makes the phases
executable: the implementer may write exactly one status, and every other
status is a conclusion Orchestra reaches by running the plan's own commands.
agent may request: ready_for_verification
agent may not assert: verified, awaiting_manual_verification, failed, skipped
The worker resolves commands from the coordinator, never from the request, so a
request cannot smuggle in a command the planner did not write. They run as argv
through exec with Dir set to the worktree, which is the quality gate's existing
envelope and not a weaker one. There is no shell, so a pipe is a literal
argument.
Project policy decides executable reach. registry.Project.Verification matches
argv positionally, and an absent policy refuses everything: a plan command is
agent-authored, so inheriting the operator-authored gate's reach by default
would be the wrong direction to fail in. A refused command is refused before
anything runs, and the refusal names the project and the command so the planner
learns its real reach.
Two bindings make the record mean something later. PlanRef, so progress earned
under plan A cannot survive into plan B. AtSHA, so "verified" does not outlive
the code that made it true: a record whose commit has moved is retained as
provenance and rendered as stale, never as a claim about the current tree.
Both are the same failure this codebase already fixed for reviews, which bind
to the commit they examined.
Manual steps hold a phase at awaiting_manual_verification. The sign-off is an
ordinary human decision whose subject carries the plan ref and the phase id, so
a later "looks good" on an unrelated thread cannot satisfy a gate nobody was
discussing.
A plan sealed before plan.md declares no executable unit, and says so: the
implement context states that phase progress is unavailable and the work
continues under the old semantics. Inventing phases it never had would be worse
than admitting it has none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
@@ -305,6 +305,12 @@ func renderTask(in Input) string {
|
||||
fmt.Fprintf(&b, "- uncommitted changes: %t\n", in.Git.Dirty)
|
||||
|
||||
b.WriteString(renderSealed(in))
|
||||
// Below the plan, above continuity. Progress is a fact about the plan, so
|
||||
// it follows the plan; continuity is one predecessor's account, so it
|
||||
// ranks under both.
|
||||
if in.Phase == domain.WorkPhaseImplement {
|
||||
b.WriteString(renderPlanProgress(in))
|
||||
}
|
||||
b.WriteString(renderFindings(in))
|
||||
b.WriteString(renderEvidence(in))
|
||||
|
||||
@@ -410,6 +416,49 @@ func fallback(s string) string {
|
||||
// plan -> accepted research
|
||||
// implement -> accepted research and accepted plan
|
||||
// review -> accepted plan
|
||||
// renderPlanProgress states what Orchestra established about the accepted
|
||||
// plan, which is the half a rotated successor cannot reconstruct. A verified
|
||||
// phase is named with the commit it was verified at, and labelled stale when
|
||||
// the tree has moved, so "verified" never reads as a claim about code that has
|
||||
// since changed.
|
||||
func renderPlanProgress(in Input) string {
|
||||
if in.Plan == nil || len(in.Plan.Phases) == 0 {
|
||||
return ""
|
||||
}
|
||||
records := in.Task.PlanPhases()
|
||||
byPhase := map[string]domain.PlanPhaseRecord{}
|
||||
for _, r := range records {
|
||||
byPhase[r.PhaseID] = r
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("\n## Plan progress\n\nOrchestra established this by running the plan's own verification. You cannot write it.\n\n")
|
||||
current := ""
|
||||
for _, phase := range in.Plan.Phases {
|
||||
rec, ok := byPhase[phase.ID]
|
||||
switch {
|
||||
case !ok:
|
||||
fmt.Fprintf(&b, "- %s (%s): not started\n", phase.ID, collapse(phase.Name))
|
||||
case rec.Status == domain.PlanPhaseVerified && rec.Stale(in.Git.HeadSHA):
|
||||
fmt.Fprintf(&b, "- %s (%s): verified at %s, stale because the tree is now at %s\n", phase.ID, collapse(phase.Name), short(rec.AtSHA), short(in.Git.HeadSHA))
|
||||
case rec.Status == domain.PlanPhaseVerified:
|
||||
fmt.Fprintf(&b, "- %s (%s): verified at %s\n", phase.ID, collapse(phase.Name), short(rec.AtSHA))
|
||||
case rec.Status == domain.PlanPhaseAwaitingManual:
|
||||
fmt.Fprintf(&b, "- %s (%s): automated checks passed at %s, waiting for the human to confirm the manual steps\n", phase.ID, collapse(phase.Name), short(rec.AtSHA))
|
||||
default:
|
||||
fmt.Fprintf(&b, "- %s (%s): in progress, last verification exited %v\n", phase.ID, collapse(phase.Name), rec.ExitCodes)
|
||||
}
|
||||
if current == "" && (!ok || rec.Status != domain.PlanPhaseVerified) {
|
||||
current = phase.ID
|
||||
}
|
||||
}
|
||||
if current == "" {
|
||||
b.WriteString("\nEvery phase is verified.\n")
|
||||
return b.String()
|
||||
}
|
||||
fmt.Fprintf(&b, "\nYour current phase is %s. When you believe it is done, write .orchestra/plan-progress.json:\n\n {\"phase\": %q, \"status\": \"ready_for_verification\"}\n\nThat is a request, not a result. Orchestra runs that phase's own automated commands and records what they exit. No other status is writable: you cannot mark a phase verified, and claiming one would be refused.\n", current, current)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func renderSealed(in Input) string {
|
||||
var b strings.Builder
|
||||
research := in.Research
|
||||
@@ -446,6 +495,12 @@ func renderSealed(in Input) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
if plan != nil && len(plan.Phases) == 0 {
|
||||
// A plan sealed before plan.md names no executable unit, so phase
|
||||
// progress cannot apply to it. Saying so beats a silently absent
|
||||
// progress section, which reads as "no phase is done yet".
|
||||
b.WriteString("\nThis is a legacy accepted plan, sealed before plan.md. Phase progress is unavailable for it: work from the plan text and finish the phase the usual way.\n")
|
||||
}
|
||||
if plan != nil {
|
||||
// Verbatim, never collapsed. The plan is the execution map an
|
||||
// implement session works from, and a rotated successor has to receive
|
||||
|
||||
Reference in New Issue
Block a user