close worktree transport and lifecycle contract gaps

This commit is contained in:
kami
2026-07-26 20:44:15 +04:00
parent d32887a91d
commit c3d8271e15
6 changed files with 185 additions and 5 deletions
+11
View File
@@ -185,6 +185,17 @@ func (s *Store) Append(e domain.Event) error {
if taskExists && e.Version != t.Version+1 {
return domain.ErrConflict
}
// Every optimistic lifecycle writer may carry its observed version. Enforce
// it at the append boundary so non-HTTP producers receive the same CAS.
var contract map[string]any
if err := json.Unmarshal(e.Payload, &contract); err != nil {
return err
}
if expected, ok := contract["expected_version"].(float64); ok {
if expected != float64(int(expected)) || !taskExists || int(expected) != t.Version {
return domain.ErrConflict
}
}
if e.Type == "TaskLeased" {
var p struct {
ExpectedVersion *int `json:"expected_version"`
+15
View File
@@ -92,3 +92,18 @@ func TestLifecycleEventsRequireEvidence(t *testing.T) {
})
}
}
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})
if err != domain.ErrConflict {
t.Fatalf("expected CAS conflict, got %v", err)
}
}