From 39cc108b5f0bf3d0238a1707d657a77b7a3df184 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 09:08:52 +0000 Subject: feat(event,storage): add events table substrate for OSS redesign New internal/event package defines the Event type with canonical Kind and Actor constants. New events table is appended via additive migration (id, task_id, seq, ts, kind, actor, payload_json) with seq as per-task monotonic, assigned by the storage layer inside a transaction. CreateEvent + ListEvents support the future migration of question_json, interactions_json, summary, and elaboration_input off the tasks table into a unified, replayable event stream. No existing code paths are modified yet; this is pure substrate for Phase 1. https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39 --- internal/event/event.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ internal/event/event_test.go | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 internal/event/event.go create mode 100644 internal/event/event_test.go (limited to 'internal/event') diff --git a/internal/event/event.go b/internal/event/event.go new file mode 100644 index 0000000..2573fde --- /dev/null +++ b/internal/event/event.go @@ -0,0 +1,50 @@ +// Package event defines the Event type and canonical kinds/actors for the +// task observability stream. Events are immutable, per-task, monotonically +// sequenced records persisted by internal/storage. +package event + +import ( + "encoding/json" + "time" +) + +// Kind enumerates the canonical event types. +type Kind string + +const ( + KindStateChange Kind = "state_change" + KindAgentMessage Kind = "agent_message" + KindClarificationRequest Kind = "clarification_request" + KindClarificationAnswer Kind = "clarification_answer" + KindHumanMessage Kind = "human_message" + KindSummary Kind = "summary" + KindCommitMade Kind = "commit_made" + KindSubtaskSpawned Kind = "subtask_spawned" + KindCostReport Kind = "cost_report" + KindExecutionStarted Kind = "execution_started" + KindExecutionEnded Kind = "execution_ended" +) + +// Actor identifies the originator of an event. +type Actor string + +const ( + ActorAgent Actor = "agent" + ActorUser Actor = "user" + ActorChatbot Actor = "chatbot" + ActorSystem Actor = "system" + ActorWebhook Actor = "webhook" +) + +// Event is a single timestamped record on a task's audit/observability stream. +// Once written, events are append-only — the seq column is per-task monotonic +// and assigned by the storage layer on insert. +type Event struct { + ID string `json:"id"` + TaskID string `json:"task_id"` + Seq int64 `json:"seq"` + Timestamp time.Time `json:"timestamp"` + Kind Kind `json:"kind"` + Actor Actor `json:"actor"` + Payload json.RawMessage `json:"payload"` +} diff --git a/internal/event/event_test.go b/internal/event/event_test.go new file mode 100644 index 0000000..a2cd143 --- /dev/null +++ b/internal/event/event_test.go @@ -0,0 +1,42 @@ +package event + +import ( + "encoding/json" + "testing" + "time" +) + +func TestEvent_JSONRoundTrip(t *testing.T) { + original := Event{ + ID: "evt-1", + TaskID: "task-1", + Seq: 42, + Timestamp: time.Date(2026, 5, 22, 12, 0, 0, 0, time.UTC), + Kind: KindClarificationRequest, + Actor: ActorAgent, + Payload: json.RawMessage(`{"question":"which env?"}`), + } + + data, err := json.Marshal(original) + if err != nil { + t.Fatalf("marshal: %v", err) + } + + var got Event + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + if got.ID != original.ID || got.TaskID != original.TaskID || got.Seq != original.Seq { + t.Errorf("id/task/seq mismatch: got %+v want %+v", got, original) + } + if !got.Timestamp.Equal(original.Timestamp) { + t.Errorf("timestamp mismatch: got %v want %v", got.Timestamp, original.Timestamp) + } + if got.Kind != original.Kind || got.Actor != original.Actor { + t.Errorf("kind/actor mismatch: got %v/%v want %v/%v", got.Kind, got.Actor, original.Kind, original.Actor) + } + if string(got.Payload) != string(original.Payload) { + t.Errorf("payload mismatch: got %s want %s", got.Payload, original.Payload) + } +} -- cgit v1.2.3