slice 10B: signed-op job completion (DELETE clear-job) (hub v0.10.0)

Add DELETE /hosts/{id}/jobs/{job_id} (per-host self-scoped, idempotent) so the
agent clears a job after executing or terminally rejecting it. The hub stores
the operator-signed blobs opaquely (no signing key — cannot forge or open);
the agent verifies + executes. Doc 03 §4/§6/§9 updated (operator-signed path
live; 8C wipe completes; 10B done).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:14:32 +02:00
parent 8c54775b6f
commit 0c843286a2
6 changed files with 134 additions and 35 deletions
+27
View File
@@ -244,6 +244,33 @@ func TestGetJobs_SelfScopedAndServesBlobs(t *testing.T) {
}
}
// DELETE /hosts/{id}/jobs/{job_id} clears a processed job (slice 10B), self-scoped + idempotent.
func TestDeleteJob_SelfScopedAndIdempotent(t *testing.T) {
h, st, _ := newTestHandler(t)
seedHost(t, st, "h1", "c1", "HKEY1")
seedHost(t, st, "h2", "c2", "HKEY2")
st.EnqueueSignedJob("h1", "jobA", []byte("blob"))
// h2 cannot clear h1's job (self-scope).
if rr := do(h, http.MethodDelete, "/hosts/h1/jobs/jobA", "HKEY2", ""); rr.Code != http.StatusForbidden {
t.Errorf("h2 clearing h1's job = %d, want 403", rr.Code)
}
if n, _ := st.CountSignedJobs("h1"); n != 1 {
t.Errorf("job was removed by an unauthorized delete (depth=%d)", n)
}
// h1 clears its own job → 200, queue empties.
if rr := do(h, http.MethodDelete, "/hosts/h1/jobs/jobA", "HKEY1", ""); rr.Code != http.StatusOK {
t.Fatalf("h1 clearing own job = %d, want 200", rr.Code)
}
if n, _ := st.CountSignedJobs("h1"); n != 0 {
t.Errorf("queue depth after delete = %d, want 0", n)
}
// Idempotent: deleting an absent job is a clean 200.
if rr := do(h, http.MethodDelete, "/hosts/h1/jobs/jobA", "HKEY1", ""); rr.Code != http.StatusOK {
t.Errorf("idempotent delete = %d, want 200", rr.Code)
}
}
// The admin enqueue-job endpoint (global key only) seeds the queue, reflected in has_signed_ops.
func TestAdminEnqueueJob_GlobalKeyOnly(t *testing.T) {
h, st, _ := newTestHandler(t)