From 0b3392acead1f8c549ae6917a3f134e66a81bb5f Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 30 Sep 2026 18:56:47 +0000 Subject: [PATCH] Add ProjectID to stream event envelope for project channel (CF3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: D50 approved — the project inbox needs its own SSE stream reusing the conversation event mechanism via the pseudo-key project:; the envelope stamps project ownership (additive field). Evidence: conformance golden event_project_stream; go test green. Impact: additive only (omitempty); consumers ignore unknown fields. Refs: documents proposal 2026-09-30-card-stream-artifact-unify-project-inbox.md (D50). --- conformance_test.go | 6 ++++++ events.go | 3 +++ testdata/golden/event_project_stream.json | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 testdata/golden/event_project_stream.json diff --git a/conformance_test.go b/conformance_test.go index 4fbebf9..b8082f1 100644 --- a/conformance_test.go +++ b/conformance_test.go @@ -125,6 +125,12 @@ func TestGoldenCardEvents(t *testing.T) { Payload: json.RawMessage(`{"cardId":"card_abc123def456","actionId":"escalate"}`)}) } +func TestGoldenProjectStreamEvent(t *testing.T) { + // 项目流(D50):伪会话键 "project:" 复用会话流机制;信封带 ProjectID。 + golden(t, "event_project_stream", StreamEvent{Seq: 47, ConversationID: "project:prj_abc123def456", ProjectID: "prj_abc123def456", TS: "2026-09-30T11:00:00Z", V: 1, Type: EvCardCreated, + Payload: json.RawMessage(`{"card":{"id":"card_def456abc789","projectId":"prj_abc123def456","kind":"result","title":"项目级卡","status":"done","createdAt":"2026-09-30T11:00:00Z","updatedAt":"2026-09-30T11:00:00Z"}}`)}) +} + func TestGoldenUIVocabularies(t *testing.T) { golden(t, "ui_avatar", []UINode{ {Type: UIAvatar, Avatar: &UIAvatarSpec{Name: "weichen", Sub: "OWNER"}}, diff --git a/events.go b/events.go index a29acf8..0519673 100644 --- a/events.go +++ b/events.go @@ -4,10 +4,13 @@ import "encoding/json" // 事件信封:后端 → 前端的唯一增量通道(SSE),持久化后转发,Seq 在会话内单调。 // 消费方对未知 Type 静默忽略(原则 6 前向兼容)。 +// 项目流(D50):项目频道事件以伪会话键 "project:" 复用同一机制 +//(独立 seq、持久化、断线重放),信封以 ProjectID 标识归属(会话流事件为空)。 type StreamEvent struct { Seq int64 `json:"seq"` RunID ID `json:"runId,omitempty"` ConversationID ID `json:"conversationId"` + ProjectID ID `json:"projectID,omitempty"` // 项目流事件的项目归属(D50;只增不删) TS string `json:"ts"` // RFC3339 V int `json:"v"` Type StreamEventType `json:"type"` diff --git a/testdata/golden/event_project_stream.json b/testdata/golden/event_project_stream.json new file mode 100644 index 0000000..6f0a1d4 --- /dev/null +++ b/testdata/golden/event_project_stream.json @@ -0,0 +1,19 @@ +{ + "seq": 47, + "conversationId": "project:prj_abc123def456", + "projectID": "prj_abc123def456", + "ts": "2026-09-30T11:00:00Z", + "v": 1, + "type": "card.created", + "payload": { + "card": { + "id": "card_def456abc789", + "projectId": "prj_abc123def456", + "kind": "result", + "title": "项目级卡", + "status": "done", + "createdAt": "2026-09-30T11:00:00Z", + "updatedAt": "2026-09-30T11:00:00Z" + } + } +}