a221502356
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
85 lines
2.8 KiB
Go
85 lines
2.8 KiB
Go
package registry
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// VerificationPolicy is what a plan's automated checks may execute in this
|
|
// project. It exists because a plan command is agent-authored, while the
|
|
// quality gate is operator-authored: the two must not share an execution
|
|
// envelope just because they run in the same worktree.
|
|
//
|
|
// Absence is a refusal, never a default-allow. A project that declares no
|
|
// policy runs no plan command, and the planner is told so at seal time.
|
|
type VerificationPolicy struct {
|
|
// Allowed is a list of argv patterns. A command runs only if some pattern
|
|
// matches it positionally.
|
|
Allowed [][]string `json:"allowed,omitempty"`
|
|
}
|
|
|
|
// Allows reports whether argv matches any pattern, and names the reason when
|
|
// it does not. The reason reaches the planner, so "not allowed" alone would
|
|
// send it guessing at the project's real reach.
|
|
func (p VerificationPolicy) Allows(argv []string) (bool, string) {
|
|
if len(argv) == 0 {
|
|
return false, "the command is empty"
|
|
}
|
|
if len(p.Allowed) == 0 {
|
|
return false, "this project declares no verification policy, so no plan command may run"
|
|
}
|
|
for _, pattern := range p.Allowed {
|
|
if matchArgv(pattern, argv) {
|
|
return true, ""
|
|
}
|
|
}
|
|
return false, fmt.Sprintf("%q is not in this project's verification policy, which allows: %s", strings.Join(argv, " "), p.describe())
|
|
}
|
|
|
|
func (p VerificationPolicy) describe() string {
|
|
out := make([]string, 0, len(p.Allowed))
|
|
for _, pattern := range p.Allowed {
|
|
out = append(out, strings.Join(pattern, " "))
|
|
}
|
|
return strings.Join(out, "; ")
|
|
}
|
|
|
|
// matchArgv matches positionally, never by prefix.
|
|
//
|
|
// - a literal element matches that element exactly
|
|
// - "*" matches exactly one element, any value
|
|
// - an element ending in "/..." matches a path argument under that prefix,
|
|
// so ./internal/... covers ./internal/store/... but never ./internalsecrets
|
|
// - "*" as the last pattern element matches every remaining element, and is
|
|
// the only way a pattern covers a longer command. An operator writes that
|
|
// deliberately for a runner that takes free-form arguments.
|
|
//
|
|
// A shorter pattern otherwise fails, so ["go", "test"] never authorises
|
|
// ["go", "test", "-exec", "curl"].
|
|
func matchArgv(pattern, argv []string) bool {
|
|
if len(pattern) == 0 {
|
|
return false
|
|
}
|
|
for i, want := range pattern {
|
|
if want == "*" && i == len(pattern)-1 && len(argv) >= len(pattern) {
|
|
// Trailing wildcard: everything from here on is covered.
|
|
return true
|
|
}
|
|
if i >= len(argv) {
|
|
return false
|
|
}
|
|
got := argv[i]
|
|
switch {
|
|
case want == "*":
|
|
// One element, any value. An empty argument is still an argument.
|
|
case strings.HasSuffix(want, "/..."):
|
|
if !strings.HasPrefix(got, strings.TrimSuffix(want, "...")) && got != strings.TrimSuffix(want, "/...") {
|
|
return false
|
|
}
|
|
case want != got:
|
|
return false
|
|
}
|
|
}
|
|
return len(pattern) == len(argv)
|
|
}
|