Refuse a plan command outside project policy when the plan seals
The brief tells the planner "a command outside its policy is refused when you seal, not later". It was not. The only caller of VerificationPolicy.Allows was PlanPhaseCommands, which runs when the implementer asks to verify: one phase, one session and one rotation after the planner could have fixed it. Run 9 sealed ["bash", "scripts/test_healthcheck.sh"] against a policy that allows neither shape, and the phase request was accepted. The check now runs beside citation resolution, on the coordinator, where the project is already in scope. A project with no verification policy can still seal a plan; it cannot seal one that declares run: lines, which matches what an absent policy already meant at verification time. Test fixtures gained a policy for the same reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
@@ -84,6 +84,15 @@ func advanceWorkPhase(s *store.Store, project registry.Project, taskID string, a
|
||||
if err := resolvePlanReferences(s, t, doc); err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
// The project decides what a plan command may execute, and the
|
||||
// brief promises the planner it learns that at seal time. Run 9
|
||||
// sealed ["bash", "scripts/test_healthcheck.sh"] against a policy
|
||||
// that allows neither shape: the only check lived in
|
||||
// PlanPhaseCommands, which runs when the implementer asks to
|
||||
// verify, one phase and one session too late.
|
||||
if err := resolvePlanCommands(project, doc); err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
}
|
||||
ref, err := s.PutArtifact(artifact)
|
||||
if err != nil {
|
||||
@@ -180,6 +189,20 @@ func phaseOperation(s *store.Store, taskID, operationID string) (domain.Event, b
|
||||
return domain.Event{}, false
|
||||
}
|
||||
|
||||
// resolvePlanCommands refuses a plan whose automated checks fall outside the
|
||||
// project's verification policy. It fails on the planner, whose session is
|
||||
// still alive to correct it, rather than on the implementer that inherits it.
|
||||
func resolvePlanCommands(project registry.Project, doc workphase.PlanDoc) error {
|
||||
for _, phase := range doc.Phases {
|
||||
for _, argv := range phase.Automated {
|
||||
if allowed, why := project.Verification.Allows(argv); !allowed {
|
||||
return fmt.Errorf("%w: %s verification: %s", domain.ErrInvalid, phase.ID, why)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolvePlanReferences refuses a plan that cites research the task never
|
||||
// sealed. Its cost is one CAS read against a ref the coordinator already
|
||||
// holds.
|
||||
|
||||
Reference in New Issue
Block a user