fix(store): unique event IDs on lease/expiry, honest duplicate-ingest signal (S5, S6)
S5: Store.Lease and Store.ExpireLeases both set Event.ID to the task id, so
every TaskLeased/TaskReleased event for a given task collided on ID across
every lease of that task — unsound for ApplyAdvisory or any future
ID-based lookup. Both now call domain.NewID().
S6: Append's TaskCreated dedup path returned nil (success) without
appending anything. main.go's handler then did
`s.Events(0)[len(s.Events(0))-1]` and returned that — an unrelated event —
with 201 Created, and every other Append caller (Gitea poll/webhook, JSONL
ingest) had no way to distinguish "duplicate, as expected" from "genuinely
appended".
Add domain.ErrDuplicate, returned instead of nil on a duplicate
(source, external_id). Add Store.TaskBySource to resolve the
already-ingested task by that same dedup key. Update every caller:
- main.go's POST /v1/tasks now returns 200 with the existing task on
ErrDuplicate instead of fabricating a 201 with the wrong event.
- provider.Gitea.Poll/IngestWebhook and provider.JSONL.Ingest treat
ErrDuplicate as expected (already-seen issue/line), not a failure —
without this, Gitea polling would have errored out of its loop on the
first already-ingested issue in every batch, since Poll previously
relied on the old nil-on-dup behavior to keep scanning.
TestLeaseAndExpireEventIDsAreUnique and TestTaskBySourceResolvesDuplicate
cover the store-level fixes; TestAppendReplayAndDeduplicate updated for the
new error signal.
AUDIT.md S5, S6.
This commit is contained in:
@@ -2,9 +2,11 @@ package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"orchestra/internal/authz"
|
||||
"orchestra/internal/domain"
|
||||
@@ -24,8 +26,8 @@ func TestAppendReplayAndDeduplicate(t *testing.T) {
|
||||
if err := s.Append(created("e1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Append(created("e2")); err != nil {
|
||||
t.Fatal(err)
|
||||
if err := s.Append(created("e2")); !errors.Is(err, domain.ErrDuplicate) {
|
||||
t.Fatalf("expected ErrDuplicate, got %v", err)
|
||||
}
|
||||
if got := len(s.Events(0)); got != 1 {
|
||||
t.Fatalf("duplicate ingest appended %d events", got)
|
||||
@@ -53,6 +55,63 @@ func TestAppendReplayAndDeduplicate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaseAndExpireEventIDsAreUnique guards S5: Event.ID was set to the
|
||||
// task id in both Lease and ExpireLeases, so every lease of the same task
|
||||
// produced a TaskLeased/TaskReleased event with a colliding ID — unsound
|
||||
// for ApplyAdvisory or any future ID-based lookup.
|
||||
func TestLeaseAndExpireEventIDsAreUnique(t *testing.T) {
|
||||
s, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Append(created("e1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := s.Tasks()[0]
|
||||
leaseEvt, err := s.Lease(task.ID, "h1", time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if leaseEvt.ID == task.ID || leaseEvt.ID == "" {
|
||||
t.Fatalf("lease event ID %q collides with task ID %q", leaseEvt.ID, task.ID)
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
expired, err := s.ExpireLeases(time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(expired) != 1 {
|
||||
t.Fatalf("expected 1 expiry, got %d", len(expired))
|
||||
}
|
||||
if expired[0].ID == task.ID || expired[0].ID == leaseEvt.ID || expired[0].ID == "" {
|
||||
t.Fatalf("expiry event ID %q collides", expired[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTaskBySourceResolvesDuplicate covers the S6 fix: a caller that gets
|
||||
// ErrDuplicate from Append must be able to look up the already-ingested
|
||||
// task by its dedup key instead of guessing at "the last event in the log".
|
||||
func TestTaskBySourceResolvesDuplicate(t *testing.T) {
|
||||
s, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Append(created("e1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := s.Tasks()[0]
|
||||
if err := s.Append(created("e2")); !errors.Is(err, domain.ErrDuplicate) {
|
||||
t.Fatalf("expected ErrDuplicate, got %v", err)
|
||||
}
|
||||
got, ok := s.TaskBySource("jsonl", "42")
|
||||
if !ok || got.ID != want.ID {
|
||||
t.Fatalf("TaskBySource = %+v, ok=%v, want %+v", got, ok, want)
|
||||
}
|
||||
if _, ok := s.TaskBySource("jsonl", "does-not-exist"); ok {
|
||||
t.Fatal("expected not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactIsContentAddressed(t *testing.T) {
|
||||
s, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user