44376f0709
Amendments to due/description/inherent_priority were accepted and durably logged but silently discarded by the projection since store.apply only ever handled the title key. Also added the missing Task.Description field (TaskCreated never populated it either). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
233 lines
7.7 KiB
Go
233 lines
7.7 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"orchestra/internal/authz"
|
|
"orchestra/internal/domain"
|
|
)
|
|
|
|
func created(id string) domain.Event {
|
|
b, _ := json.Marshal(map[string]any{"source": "jsonl", "external_id": "42", "project": "demo", "capability": []string{"mechanical"}})
|
|
return domain.Event{ID: id, Type: "TaskCreated", TaskID: "task-1", Version: 1, Payload: b, Surface: string(authz.System)}
|
|
}
|
|
|
|
func TestAppendReplayAndDeduplicate(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, err := Open(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Append(created("e1")); 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)
|
|
}
|
|
ref, err := s.PutArtifact([]byte("report"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
completion, _ := json.Marshal(map[string]any{"report_ref": ref, "receipt": map[string]any{"harness_id": "h", "consumed": 1}})
|
|
if err := s.Append(domain.Event{Type: "TaskCompleted", TaskID: "task-1", Version: 2, Payload: completion, Surface: string(authz.System)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Append(domain.Event{Type: "TaskReleased", TaskID: "task-1", Version: 2, Surface: string(authz.System), Payload: json.RawMessage(`{"handoff_ref":"` + ref + `","anchor_sha":"0123456789012345678901234567890123456789"}`)}); err != domain.ErrConflict {
|
|
t.Fatalf("expected conflict, got %v", err)
|
|
}
|
|
s2, err := Open(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := s2.Tasks()[0].State; got != domain.StateCompleted {
|
|
t.Fatalf("replay state = %s", got)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "snapshot.json")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestTaskAmendedAppliesAllFields guards S7: the projection previously only
|
|
// applied "title" from a TaskAmended payload, silently discarding due,
|
|
// description, and inherent_priority amendments even though they were
|
|
// accepted and logged.
|
|
func TestTaskAmendedAppliesAllFields(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, err := Open(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Append(created("e1")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
amend, _ := json.Marshal(map[string]any{
|
|
"title": "new title",
|
|
"description": "new description",
|
|
"inherent_priority": 5.0,
|
|
"due": "2026-08-01T00:00:00Z",
|
|
})
|
|
if err := s.Append(domain.Event{Type: "TaskAmended", TaskID: "task-1", Version: 2, Payload: amend, Surface: string(authz.System)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tk := s.Tasks()[0]
|
|
if tk.Title != "new title" || tk.Description != "new description" || tk.InherentPriority != 5 {
|
|
t.Fatalf("unexpected task after amendment: %+v", tk)
|
|
}
|
|
if tk.Due == nil || !tk.Due.Equal(time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)) {
|
|
t.Fatalf("unexpected due after amendment: %+v", tk.Due)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
t.Fatal(err)
|
|
}
|
|
h1, err := s.PutArtifact([]byte("proof"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h2, err := s.PutArtifact([]byte("proof"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h1 != h2 {
|
|
t.Fatal("same artifact received different hashes")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(s.cas, h1)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLifecycleEventsRequireEvidence(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
typ string
|
|
body string
|
|
}{
|
|
{"release", "TaskReleased", `{}`},
|
|
{"complete", "TaskCompleted", `{}`},
|
|
{"block", "TaskBlocked", `{}`},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := domain.ValidateEvent(domain.Event{Type: tc.typ, TaskID: "task-1", Version: 1, Payload: json.RawMessage(tc.body), Surface: string(authz.System)})
|
|
if err == nil {
|
|
t.Fatal("expected lifecycle evidence validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAppendEnforcesAuthorizationAtTheBus proves authorization is checked
|
|
// once at the append boundary (spec §7.1/§1.4), not only in HTTP handlers:
|
|
// a caller writing to the store directly with a notify-only surface, or with
|
|
// no declared surface at all, is rejected exactly like an HTTP request would
|
|
// be — there is no in-process bypass for the router, coordinator, or a
|
|
// provider adapter that forgets to declare who it's acting as.
|
|
func TestAppendEnforcesAuthorizationAtTheBus(t *testing.T) {
|
|
s, err := Open(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := json.Marshal(map[string]any{"source": "jsonl", "external_id": "1", "project": "demo"})
|
|
|
|
// A notify-only surface (e.g. Telegram) must never be able to create a
|
|
// task by calling the store directly, even though it bypasses HTTP.
|
|
if err := s.Append(domain.Event{ID: "e1", Type: "TaskCreated", TaskID: "task-1", Version: 1, Payload: b, Surface: string(authz.Telegram)}); err == nil {
|
|
t.Fatal("notify-only surface created a task via direct store access")
|
|
}
|
|
|
|
// An internal producer that forgets to declare a surface is rejected,
|
|
// not silently trusted as the plane.
|
|
if err := s.Append(domain.Event{ID: "e2", Type: "TaskCreated", TaskID: "task-1", Version: 1, Payload: b}); err == nil {
|
|
t.Fatal("event with no declared surface was accepted")
|
|
}
|
|
|
|
// The plane (router/coordinator/provider) authorizes as System and
|
|
// succeeds.
|
|
if err := s.Append(domain.Event{ID: "e3", Type: "TaskCreated", TaskID: "task-1", Version: 1, Payload: b, Surface: string(authz.System)}); err != nil {
|
|
t.Fatalf("system surface rejected: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExpectedVersionIsCheckedForEveryWriter(t *testing.T) {
|
|
s, err := Open(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Append(created("create")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p := json.RawMessage(`{"reason":"rotate","expected_version":0}`)
|
|
err = s.Append(domain.Event{Type: "TaskReleased", TaskID: "task-1", Version: 2, Payload: p, Surface: string(authz.System)})
|
|
if err != domain.ErrConflict {
|
|
t.Fatalf("expected CAS conflict, got %v", err)
|
|
}
|
|
}
|