From fcfe8149bd71720409efdb9084ff40786a8e5abe Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 15 Oct 2026 06:49:33 +0000 Subject: [PATCH] Verify durable work results and legacy card compatibility Why: Retain full results and exact answers across persistence and reload. Evidence: go test ./... passed locally, including full-tail and legacy JSON coverage. Impact: Add contract roundtrip tests and a guarded dispatching state. Refs: feature/workbench-flow-completion --- activity.go | 2 +- activity_work_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 activity_work_test.go diff --git a/activity.go b/activity.go index 26229ff..56fc862 100644 --- a/activity.go +++ b/activity.go @@ -40,7 +40,7 @@ type WorkCard struct { type WorkDecision struct { Mode string `json:"mode"` // single | multiple - State string `json:"state"` // open | deferred | submitted + State string `json:"state"` // open | deferred | dispatching | submitted Options []WorkOption `json:"options,omitempty"` SelectedIDs []string `json:"selectedIds,omitempty"` FreeText string `json:"freeText,omitempty"` diff --git a/activity_work_test.go b/activity_work_test.go new file mode 100644 index 0000000..ad4fced --- /dev/null +++ b/activity_work_test.go @@ -0,0 +1,46 @@ +package core + +import ( + "encoding/json" + "strings" + "testing" + "time" +) + +func TestWorkCardSurvivesPersistedSnapshot(t *testing.T) { + completed := time.Date(2026, 10, 15, 6, 0, 0, 0, time.UTC) + card := ActivityCard{ID: "card_work", Status: CardDone, Work: &WorkCard{ + Result: strings.Repeat("完整结果\n", 200) + "最后一项依据", + ResultVersion: 2, ReadVersion: 1, CompletedAt: &completed, + Decision: &WorkDecision{Mode: "multiple", State: "submitted", Options: []WorkOption{{ID: "summary", Label: "生成摘要"}}, + SelectedIDs: []string{"summary"}, FreeText: "请突出关键限制", RespondedAt: &completed}, + }} + data, err := json.Marshal(card) + if err != nil { + t.Fatal(err) + } + var restored ActivityCard + if err := json.Unmarshal(data, &restored); err != nil { + t.Fatal(err) + } + if restored.Work == nil || restored.Work.Result != card.Work.Result || restored.Work.ReadVersion != 1 || restored.Work.ResultVersion != 2 { + t.Fatalf("complete result or unread version lost: %+v", restored.Work) + } + if d := restored.Work.Decision; d.State != "submitted" || d.SelectedIDs[0] != "summary" || d.FreeText != "请突出关键限制" || !d.RespondedAt.Equal(completed) { + t.Fatalf("decision history lost: %+v", d) + } +} + +func TestLegacyCardOmitsOptionalWork(t *testing.T) { + var legacy ActivityCard + if err := json.Unmarshal([]byte(`{"id":"old","status":"resolved","body":[{"type":"markdown","markdown":"保留历史"}]}`), &legacy); err != nil { + t.Fatal(err) + } + data, err := json.Marshal(legacy) + if err != nil { + t.Fatal(err) + } + if legacy.Work != nil || strings.Contains(string(data), `"work"`) || legacy.Body[0].Markdown != "保留历史" { + t.Fatalf("legacy payload changed: %s", data) + } +} diff --git a/ts/types.ts b/ts/types.ts index 5bdb95f..2001311 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -158,7 +158,7 @@ export interface WorkCard { decision?: WorkDecision; } export interface WorkDecision { - mode: 'single' | 'multiple'; state: 'open' | 'deferred' | 'submitted'; + mode: 'single' | 'multiple'; state: 'open' | 'deferred' | 'dispatching' | 'submitted'; options?: { id: string; label: string }[]; selectedIds?: string[]; freeText?: string; respondedAt?: string; }