enforce lifecycle evidence contracts

This commit is contained in:
kami
2026-07-26 20:17:03 +04:00
parent b2b24cbe09
commit 6bf6445d40
3 changed files with 31 additions and 15 deletions
+9 -15
View File
@@ -221,27 +221,21 @@ func main() {
}
types := map[string]string{"release": "TaskReleased", "complete": "TaskCompleted", "block": "TaskBlocked"}
var p map[string]any
if r.Body != nil {
if json.NewDecoder(r.Body).Decode(&p) != nil {
p = map[string]any{}
}
}
if p == nil {
p = map[string]any{}
if r.Body == nil || json.NewDecoder(r.Body).Decode(&p) != nil || p == nil {
http.Error(w, "invalid lifecycle payload", http.StatusBadRequest)
return
}
if action == "release" && p["reason"] == nil && p["handoff_ref"] == nil {
p["reason"] = "released_by_api"
http.Error(w, "reason or handoff_ref required", http.StatusBadRequest)
return
}
if action == "block" && p["blocker"] == nil {
p["blocker"] = "blocked_by_api"
http.Error(w, "blocker required", http.StatusBadRequest)
return
}
if action == "complete" && p["report_ref"] == nil {
ref, putErr := s.PutArtifact([]byte("completed without an attached report\n"))
if putErr != nil {
http.Error(w, putErr.Error(), 500)
return
}
p["report_ref"] = ref
http.Error(w, "report_ref required", http.StatusBadRequest)
return
}
ePayload, _ := json.Marshal(p)
e = domain.Event{ID: id(), Type: types[action], TaskID: taskID, Version: t.Version + 1, Payload: ePayload}
+20
View File
@@ -72,3 +72,23 @@ func TestArtifactIsContentAddressed(t *testing.T) {
t.Fatal(err)
}
}
func TestLifecycleEventsRequireEvidence(t *testing.T) {
cases := []struct {
name string
typ string
body string
}{
{"release", "TaskReleased", `{}`},
{"complete", "TaskCompleted", `{}`},
{"block", "TaskBlocked", `{}`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := domain.ValidateEvent(domain.Event{Type: tc.typ, TaskID: "task-1", Version: 1, Payload: json.RawMessage(tc.body)})
if err == nil {
t.Fatal("expected lifecycle evidence validation error")
}
})
}
}
+2
View File
@@ -51,6 +51,8 @@ The first pass closed the store/API defects (lifecycle defaults, CAS content ver
The latest pass now applies `AuthorizeEvent` to lifecycle and approval writes and adds `/readyz` with router/provider configuration checks. Readiness is configuration-level only; it does not yet probe herdr/provider health.
The lifecycle API contract pass now requires callers to provide explicit release evidence (`reason` or `handoff_ref`), a block `blocker`, and a completion `report_ref`. The server no longer creates generated placeholder completion artifacts or converts malformed/empty lifecycle bodies into defaults. Regression coverage validates that release, completion, and block events reject missing evidence.
Recommended order:
1. Add the orchestration coordinator: lease → worktree → harness session → bootstrap → lifecycle events.