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:
@@ -137,10 +137,13 @@ func (j JSONL) Ingest(r io.Reader, sink Sink) (int, error) {
|
||||
return count, fmt.Errorf("line %d: %w", line, err)
|
||||
}
|
||||
b, _ := json.Marshal(p)
|
||||
if err := sink.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: domain.NewID(), Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
|
||||
err := sink.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: domain.NewID(), Version: 1, Payload: b, Surface: string(authz.System)})
|
||||
if err != nil && !errors.Is(err, domain.ErrDuplicate) {
|
||||
return count, fmt.Errorf("line %d: %w", line, err)
|
||||
}
|
||||
count++
|
||||
if err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, sc.Err()
|
||||
}
|
||||
@@ -321,7 +324,10 @@ func (g Gitea) IngestWebhook(body []byte, signature string, sink Sink) error {
|
||||
project = h.Repository.FullName
|
||||
}
|
||||
}
|
||||
return sink.Append(g.event(h.Issue, g.sourceName(), project))
|
||||
if err := sink.Append(g.event(h.Issue, g.sourceName(), project)); err != nil && !errors.Is(err, domain.ErrDuplicate) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func validSignature(body []byte, got, secret string) bool {
|
||||
if secret == "" || got == "" {
|
||||
@@ -379,7 +385,7 @@ func (g Gitea) Poll(ctx context.Context, sink Sink) (int, error) {
|
||||
project = g.Repo
|
||||
}
|
||||
for _, i := range issues {
|
||||
if err := sink.Append(g.event(i, g.sourceName(), project)); err != nil {
|
||||
if err := sink.Append(g.event(i, g.sourceName(), project)); err != nil && !errors.Is(err, domain.ErrDuplicate) {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user