feat: add audited task review and linked manual retries

This commit is contained in:
2026-09-17 21:57:53 +08:00
parent 571107f96d
commit a50d5faac7
43 changed files with 2096 additions and 57 deletions

View File

@@ -32,6 +32,21 @@ func historyQuery(r *http.Request, paged bool) (service.HistoryPageInput, error)
return in, nil
}
func registerHistory(mux *http.ServeMux, s *service.Service) {
mux.HandleFunc("GET /v1/tasks/{task}/operations", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
in, err := historyQuery(r, true)
if err != nil {
return nil, 0, err
}
out, err := s.ListTaskOperations(r.Context(), t, core.ID(r.PathValue("task")), in)
return out, 200, err
}))
mux.HandleFunc("GET /v1/tasks/{task}/operations/{operation}", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
if _, err := historyQuery(r, false); err != nil {
return nil, 0, err
}
out, err := s.GetTaskOperation(r.Context(), t, core.ID(r.PathValue("task")), core.ID(r.PathValue("operation")))
return out, 200, err
}))
mux.HandleFunc("GET /v1/tasks/{task}/attempts", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
in, err := historyQuery(r, true)
if err != nil {

View File

@@ -0,0 +1,39 @@
package httpserver
import (
"git.yuntuo.hk/k2safe/orbit/pulse/service"
"net/http/httptest"
"strings"
"testing"
)
func TestRecoveryJSONRequiresExplicitConfirmation(t *testing.T) {
for _, tc := range []struct {
body string
retry, valid bool
}{
{`{"execution_settled":true,"expected_revision":"9007199254740993"}`, false, true},
{`{"execution_settled":false}`, false, true},
{`{}`, false, false},
{`{"execution_settled":null}`, false, false},
{`{"ExecutionSettled":true}`, false, false},
{`{"execution_settled":true,"actor_id":"spoof"}`, false, false},
{`{"execution_settled":true,"execution_settled":false}`, false, false},
{`{"execution_settled":true,"expected_revision":9007199254740993}`, false, false},
{`{"safe_to_retry":true}`, true, true},
{`{"safe_to_retry":null}`, true, false},
{`{}`, true, false},
{`{"safe_to_retry":true,"owner_id":"spoof"}`, true, false},
} {
r := httptest.NewRequest("POST", "/v1/tasks/example:review", strings.NewReader(tc.body))
r.Header.Set("Content-Type", "application/json")
var in any = &service.ReviewInput{}
if tc.retry {
in = &service.ManualRetryInput{}
}
err := decodeBody(r, in)
if (err == nil) != tc.valid {
t.Fatalf("%s: %v", tc.body, err)
}
}
}

View File

@@ -77,6 +77,30 @@ func registerTasks(mux *http.ServeMux, s *service.Service) {
}))
mux.HandleFunc("POST /v1/tasks/{command}", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
command := r.PathValue("command")
if strings.HasSuffix(command, ":review") {
var in service.ReviewInput
if err := decodeBody(r, &in); err != nil {
return nil, 0, err
}
key, err := idempotencyKey(r)
if err != nil {
return nil, 0, err
}
out, err := s.ReviewTask(r.Context(), t, core.ID(strings.TrimSuffix(command, ":review")), key, in)
return out, 200, err
}
if strings.HasSuffix(command, ":retry") {
var in service.ManualRetryInput
if err := decodeBody(r, &in); err != nil {
return nil, 0, err
}
key, err := idempotencyKey(r)
if err != nil {
return nil, 0, err
}
out, err := s.RetryTask(r.Context(), t, core.ID(strings.TrimSuffix(command, ":retry")), key, in)
return out, createdStatus(out.Deduplicated), err
}
if !strings.HasSuffix(command, ":cancel") {
return nil, 0, fault.NotFound
}