Bind a manual sign-off to the tree it was given against
F63, found live on run 19. A manual check on these projects is a human reading what the code prints. RecordPlanPhaseVerification asked only whether a sign-off for that plan and phase existed, and one exists forever, so rerunning a phase's automated checks at a new commit carried the human half along with it. The rig proved it twice: two operator commits and two re-verification requests, each coming back verified without anyone looking. The reducer now records which tree the human confirmed, the record carries it forward as provenance, and a run whose commit does not match it waits for the human again. A sign-off given before any run has no confirmed tree and still counts, so the ordinary ordering is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
const shaOne = "1111111111111111111111111111111111111111"
|
||||
const shaTwo = "2222222222222222222222222222222222222222"
|
||||
const shaThree = "3333333333333333333333333333333333333333"
|
||||
|
||||
func planProject() registry.Project {
|
||||
p := registry.Project{
|
||||
@@ -251,6 +252,14 @@ func TestLegacyPlanIsExplicitlyNonProgressable(t *testing.T) {
|
||||
// signOff records a human decision bound to one phase of one plan, which is
|
||||
// the only thing that satisfies a manual verification gate.
|
||||
func signOff(t *testing.T, s *store.Store, taskID, subject string) {
|
||||
t.Helper()
|
||||
signOffFrom(t, s, taskID, subject, "signoff-"+subject)
|
||||
}
|
||||
|
||||
// signOffFrom names the comment the sign-off came from. Two sign-offs on one
|
||||
// subject are a real sequence once a rerun sends a phase back to the human,
|
||||
// and provenance is unique per comment.
|
||||
func signOffFrom(t *testing.T, s *store.Store, taskID, subject, externalID string) {
|
||||
t.Helper()
|
||||
task, _ := s.Task(taskID)
|
||||
if err := s.Append(domain.Event{
|
||||
@@ -259,9 +268,60 @@ func signOff(t *testing.T, s *store.Store, taskID, subject string) {
|
||||
Payload: mustJSONBytes(t, map[string]any{
|
||||
"decision_id": domain.NewID(), "kind": "answer", "subject": subject,
|
||||
"value": "manual steps confirmed",
|
||||
"source": map[string]any{"provider": "gitea", "external_id": "signoff-" + subject},
|
||||
"source": map[string]any{"provider": "gitea", "external_id": externalID},
|
||||
}),
|
||||
}); err != nil {
|
||||
t.Fatalf("sign off: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// F63, found live on run 19. A manual sign-off says a human read what this
|
||||
// code prints. An edit afterwards can change exactly that, so rerunning the
|
||||
// automated half at a new commit must not carry the human half with it.
|
||||
func TestASignOffDoesNotSurviveTheTreeItWasGivenAgainst(t *testing.T) {
|
||||
s, project, id := planWith(t, twoPhasePlan)
|
||||
run := []VerificationRun{{Command: []string{"go", "test", "./internal/..."}, ExitCode: 0}}
|
||||
if _, err := RecordPlanPhaseVerification(s, project, id, "phase-2", shaOne, run); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task, _ := s.Task(id)
|
||||
signOff(t, s, id, domain.PlanPhaseSubject(task.PlanRef, "phase-2"))
|
||||
task, _ = s.Task(id)
|
||||
rec, _ := task.PlanPhase("phase-2")
|
||||
if rec.Status != domain.PlanPhaseVerified || rec.ManualAtSHA != shaOne {
|
||||
t.Fatalf("sign-off did not bind to the tree it read: %+v", rec)
|
||||
}
|
||||
|
||||
// The tree moves and the phase is re-verified. The commands pass again;
|
||||
// the human has not seen the new output.
|
||||
if _, err := RecordPlanPhaseVerification(s, project, id, "phase-2", shaTwo, run); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task, _ = s.Task(id)
|
||||
rec, _ = task.PlanPhase("phase-2")
|
||||
if rec.Status != domain.PlanPhaseAwaitingManual {
|
||||
t.Fatalf("status = %q at a tree the human never saw, want awaiting_manual_verification", rec.Status)
|
||||
}
|
||||
if rec.ManualAtSHA != shaOne {
|
||||
t.Fatalf("the confirmed tree was lost: %+v", rec)
|
||||
}
|
||||
|
||||
// A second rerun must not re-inherit it either, which is what carrying
|
||||
// ManualAtSHA forward is for.
|
||||
if _, err := RecordPlanPhaseVerification(s, project, id, "phase-2", shaThree, run); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task, _ = s.Task(id)
|
||||
rec, _ = task.PlanPhase("phase-2")
|
||||
if rec.Status != domain.PlanPhaseAwaitingManual {
|
||||
t.Fatalf("a second rerun re-inherited the sign-off: %q", rec.Status)
|
||||
}
|
||||
|
||||
// Signing off again, on the tree that is now current, verifies it.
|
||||
signOffFrom(t, s, id, domain.PlanPhaseSubject(task.PlanRef, "phase-2"), "signoff-second")
|
||||
task, _ = s.Task(id)
|
||||
rec, _ = task.PlanPhase("phase-2")
|
||||
if rec.Status != domain.PlanPhaseVerified || rec.ManualAtSHA != shaThree {
|
||||
t.Fatalf("a fresh sign-off did not verify the current tree: %+v", rec)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user