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:
kami
2026-07-27 19:27:43 +04:00
parent 7211238590
commit ca85b65557
5 changed files with 105 additions and 10 deletions
+13 -1
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"io"
"log"
"net"
@@ -168,6 +169,18 @@ func main() {
b, _ := json.Marshal(p)
e := domain.Event{ID: id(), Type: "TaskCreated", TaskID: id(), Version: 1, Payload: b, Surface: string(surface(r))}
if err := s.Append(e); err != nil {
if errors.Is(err, domain.ErrDuplicate) {
// (source, external_id) was already ingested. Nothing was
// appended; report the existing task idempotently rather
// than fabricating a 201 with an unrelated event (S6).
source, _ := p["source"].(string)
externalID, _ := p["external_id"].(string)
if t, ok := s.TaskBySource(source, externalID); ok {
w.WriteHeader(200)
json.NewEncoder(w).Encode(t)
return
}
}
http.Error(w, err.Error(), 400)
return
}
@@ -176,7 +189,6 @@ func main() {
log.Printf("route task: %v", err)
}
}
e = s.Events(0)[len(s.Events(0))-1]
w.WriteHeader(201)
json.NewEncoder(w).Encode(e)
})