Withhold unsafe recipe sources from idea job results (BASE-01) (#170)
CI / ci (push) Failing after 6m39s

Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #170.
This commit is contained in:
2026-10-09 19:04:44 -04:00
committed by approver_agent
parent dcd35e78ec
commit 4dcfc3c27d
10 changed files with 399 additions and 94 deletions
+24 -23
View File
@@ -136,13 +136,13 @@ type runFunc func(ctx context.Context, payload string, publish func(json.RawMess
// handler is a registered kind: run runs a job, check reports whether a
// payload decodes into the kind's payload type, serviceOnly is set when
// Runner.Enqueue refuses the kind, and failedResult, when set, is what Get
// shows of a failed job's stored result (see Kind.FailedResult).
// Runner.Enqueue refuses the kind, and shownResult, when set, is what Get
// shows of a job's stored result (see Kind.ShownResult).
type handler struct {
run runFunc
check func(payload []byte) bool
serviceOnly bool
failedResult func(stored json.RawMessage) (json.RawMessage, error)
run runFunc
check func(payload []byte) bool
serviceOnly bool
shownResult func(status Status, stored json.RawMessage) (json.RawMessage, error)
}
// Runner owns the job handlers and the worker pool.
@@ -309,23 +309,24 @@ func (k *Kind[P]) ServiceOnly() error {
return nil
}
// FailedResult makes Get return, for a failed job of the kind with a
// stored result, show's view of that result instead of the result itself.
// It is applied on every read, so it holds however the job failed: by its
// handler, as interrupted by restart, or after a write meant to tidy the
// result failed. A show error fails Get. A kind that never calls it shows a
// failed job's last result as stored. It must be called before Start.
func (k *Kind[P]) FailedResult(show func(stored json.RawMessage) (json.RawMessage, error)) error {
// ShownResult makes Get return, for a job of the kind with a stored
// result, show's view of that result, given the job's status, instead of
// the result itself. It is applied on every read, so it holds for every
// stored result: however a job failed (by its handler, as interrupted by
// restart, or after a write meant to tidy the result failed) and whatever
// version of the kind stored it. A show error fails Get. A kind that never
// calls it shows a job's result as stored. It must be called before Start.
func (k *Kind[P]) ShownResult(show func(status Status, stored json.RawMessage) (json.RawMessage, error)) error {
if show == nil {
return fmt.Errorf("jobs: failed result %q: nil func", k.name)
return fmt.Errorf("jobs: shown result %q: nil func", k.name)
}
k.r.mu.Lock()
defer k.r.mu.Unlock()
if k.r.started {
return fmt.Errorf("jobs: failed result %q: runner already started", k.name)
return fmt.Errorf("jobs: shown result %q: runner already started", k.name)
}
h := k.r.handlers[k.name]
h.failedResult = show
h.shownResult = show
k.r.handlers[k.name] = h
return nil
}
@@ -425,8 +426,8 @@ func (r *Runner) Wait() {
r.wg.Wait()
}
// Get returns the job with id, or ErrNotFound. A failed job's result is
// shown through its kind's FailedResult, when the kind set one.
// Get returns the job with id, or ErrNotFound. A job's result is shown
// through its kind's ShownResult, when the kind set one.
func (r *Runner) Get(ctx context.Context, id string) (Job, error) {
row, err := db.New(r.st.Reader).GetJob(ctx, id)
if errors.Is(err, sql.ErrNoRows) {
@@ -442,17 +443,17 @@ func (r *Runner) Get(ctx context.Context, id string) (Job, error) {
if row.Error.Valid {
job.Error = &row.Error.String
}
if job.Status == StatusFailed && job.Result != nil {
if job.Result != nil {
r.mu.Lock()
show := r.handlers[job.Kind].failedResult
show := r.handlers[job.Kind].shownResult
r.mu.Unlock()
if show != nil {
shown, err := show(job.Result)
shown, err := show(job.Status, job.Result)
if err != nil {
return Job{}, fmt.Errorf("jobs: show failed %s result: %w", job.Kind, err)
return Job{}, fmt.Errorf("jobs: show %s %s result: %w", job.Status, job.Kind, err)
}
if !json.Valid(shown) {
return Job{}, fmt.Errorf("jobs: show failed %s result: not one JSON value", job.Kind)
return Job{}, fmt.Errorf("jobs: show %s %s result: not one JSON value", job.Status, job.Kind)
}
job.Result = shown
}
+18 -10
View File
@@ -773,7 +773,7 @@ func TestServiceOnlyKind(t *testing.T) {
}
}
func TestFailedResultAppliesOnlyToItsKindsFailedJobs(t *testing.T) {
func TestShownResultAppliesOnlyToItsKindsJobs(t *testing.T) {
t.Parallel()
st := migratedStore(t)
r := newRunner(t, st, 1)
@@ -788,17 +788,24 @@ func TestFailedResultAppliesOnlyToItsKindsFailedJobs(t *testing.T) {
}
hidden := register(t, r, "hidden", h, "stopped")
plain := register(t, r, "plain", h, "stopped")
if err := hidden.FailedResult(func(json.RawMessage) (json.RawMessage, error) {
return json.RawMessage(`{"done":0}`), nil
if err := hidden.ShownResult(func(status Status, stored json.RawMessage) (json.RawMessage, error) {
if status == StatusFailed {
return json.RawMessage(`{"done":0}`), nil
}
var res countResult
if err := json.Unmarshal(stored, &res); err != nil {
return nil, err
}
return json.Marshal(countResult{Done: res.Done * 10})
}); err != nil {
t.Fatal(err)
}
if err := hidden.FailedResult(nil); err == nil {
t.Error("FailedResult(nil) succeeded, want an error")
if err := hidden.ShownResult(nil); err == nil {
t.Error("ShownResult(nil) succeeded, want an error")
}
start(t, r)
if err := hidden.FailedResult(func(s json.RawMessage) (json.RawMessage, error) { return s, nil }); err == nil {
t.Error("FailedResult after Start succeeded, want an error")
if err := hidden.ShownResult(func(_ Status, s json.RawMessage) (json.RawMessage, error) { return s, nil }); err == nil {
t.Error("ShownResult after Start succeeded, want an error")
}
for _, tc := range []struct {
@@ -806,9 +813,10 @@ func TestFailedResultAppliesOnlyToItsKindsFailedJobs(t *testing.T) {
steps int
want string
}{
{hidden, 0, `{"done":0}`}, // failed: shown through FailedResult
{hidden, 3, `{"done":3}`}, // succeeded: as stored
{plain, 0, `{"done":5}`}, // failed, kind without FailedResult: as stored
{hidden, 0, `{"done":0}`}, // failed: shown through ShownResult
{hidden, 3, `{"done":30}`}, // succeeded: shown through ShownResult
{plain, 0, `{"done":5}`}, // failed, kind without ShownResult: as stored
{plain, 3, `{"done":3}`}, // succeeded, kind without ShownResult: as stored
} {
job := waitFor(t, r, enqueue(t, tc.kind, countPayload{Steps: tc.steps}), finished)
if string(job.Result) != tc.want {