events: verify, normalize, append-only store with provider-id dedup

Signature verification (gitea hex HMAC-SHA256 over the raw body via
constant-time hmac.Equal; redmine/discourse constant-time shared-secret
headers) with one generic ErrUnverified so rejects give attackers no
oracle. Tolerant normalization of the known Redmine/Discourse/Gitea
payload variants into one Event record (canonical subject ids, actor,
title, repo, sha256 payload digest) plus the DESIGN action mapping
(dispatch_turn / respond_turn / pipeline_step / ignore). Store: JSONL
under state dir, 0600, dedup keyed on provider event id (delivery
header, payload-digest fallback), index rebuilt at startup so replays
across restarts still dedup; torn tail lines skipped, not fatal.
This commit is contained in:
2026-08-28 21:38:16 -05:00
parent 164b14592b
commit 05ec1a4142
7 changed files with 948 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
package events
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestStoreAppendAndDedup(t *testing.T) {
dir := t.TempDir()
s, err := OpenStore(dir)
if err != nil {
t.Fatalf("OpenStore: %v", err)
}
base := Event{
Source: SourceGitea, Kind: "pr_approved", Action: ActionPipelineStep,
ProviderID: "d-1", PayloadDigest: strings.Repeat("a", 64), ReceivedAt: testNow,
}
if stored, err := s.Append(base); err != nil || !stored {
t.Fatalf("first append = stored=%v err=%v", stored, err)
}
if stored, err := s.Append(base); err != nil || stored {
t.Fatalf("replay append = stored=%v err=%v (want dup)", stored, err)
}
sameIDOtherSource := base
sameIDOtherSource.Source = SourceRedmine
if stored, err := s.Append(sameIDOtherSource); err != nil || !stored {
t.Fatalf("same provider id, other source = stored=%v err=%v (want stored)", stored, err)
}
if s.Len() != 2 {
t.Errorf("Len = %d, want 2", s.Len())
}
s.Close()
data, err := os.ReadFile(filepath.Join(dir, "events.jsonl"))
if err != nil {
t.Fatalf("read log: %v", err)
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(lines) != 2 {
t.Fatalf("log has %d lines, want 2:\n%s", len(lines), data)
}
if !strings.Contains(lines[0], `"provider_id":"d-1"`) {
t.Errorf("line 1 missing provider id: %s", lines[0])
}
if !strings.Contains(lines[0], `"source":"gitea"`) {
t.Errorf("line 1 wrong source: %s", lines[0])
}
}
func TestStoreDedupSurvivesRestart(t *testing.T) {
dir := t.TempDir()
s, err := OpenStore(dir)
if err != nil {
t.Fatal(err)
}
ev := Event{Source: SourceDiscourse, Kind: "post_created", ProviderID: "ev-9", ReceivedAt: time.Now()}
if stored, _ := s.Append(ev); !stored {
t.Fatal("first append must store")
}
s.Close()
s2, err := OpenStore(dir)
if err != nil {
t.Fatal(err)
}
defer s2.Close()
if stored, _ := s2.Append(ev); stored {
t.Fatal("duplicate after restart must not store again")
}
if s2.Len() != 1 {
t.Errorf("Len after restart = %d, want 1", s2.Len())
}
}
func TestStoreSkipsTornTailLine(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "events.jsonl")
good := `{"source":"gitea","provider_id":"d-1","kind":"push"}` + "\n"
torn := `{"source":"gitea","provider_id":"d-2","ki`
if err := os.WriteFile(path, []byte(good+torn), 0o600); err != nil {
t.Fatal(err)
}
s, err := OpenStore(dir)
if err != nil {
t.Fatalf("OpenStore with torn tail: %v", err)
}
defer s.Close()
if s.Len() != 1 {
t.Errorf("Len = %d, want 1 (torn line skipped)", s.Len())
}
if stored, _ := s.Append(Event{Source: SourceGitea, ProviderID: "d-2", ReceivedAt: time.Now()}); !stored {
t.Errorf("event from torn line must be appendable again")
}
}
func TestStorePermissions(t *testing.T) {
dir := t.TempDir()
s, err := OpenStore(dir)
if err != nil {
t.Fatal(err)
}
s.Close()
fi, err := os.Stat(filepath.Join(dir, "events.jsonl"))
if err != nil {
t.Fatal(err)
}
if fi.Mode().Perm() != 0o600 {
t.Errorf("events.jsonl mode = %v, want 0600", fi.Mode().Perm())
}
}