Release v1.0.0: Production-ready SWOOSH with durability guarantees

Major enhancements:
- Added production-grade durability guarantees with fsync operations
- Implemented BadgerDB WAL for crash recovery and persistence
- Added comprehensive HTTP API (GET/POST /state, POST /command)
- Exported ComputeStateHash for external use in genesis initialization
- Enhanced snapshot system with atomic write-fsync-rename sequence
- Added API integration documentation and durability guarantees docs

New files:
- api.go: HTTP server implementation with state and command endpoints
- api_test.go: Comprehensive API test suite
- badger_wal.go: BadgerDB-based write-ahead log
- cmd/swoosh/main.go: CLI entry point with API server
- API_INTEGRATION.md: API usage and integration guide
- DURABILITY.md: Durability guarantees and recovery procedures
- CHANGELOG.md: Version history and changes
- RELEASE_NOTES.md: Release notes for v1.0.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Codex Agent
2025-10-25 12:23:33 +11:00
parent 38707dd182
commit 6f90ad77a4
13 changed files with 2189 additions and 9 deletions

283
api_test.go Normal file
View File

@@ -0,0 +1,283 @@
package swoosh
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestHandleTransition(t *testing.T) {
// Setup executor with in-memory stores
wal := &mockWAL{}
snap := &mockSnapshotStore{}
initialState := OrchestratorState{
Meta: struct {
Version string
SchemaHash string
}{
Version: "1.0.0",
SchemaHash: "test",
},
}
hash, _ := computeStateHash(initialState)
initialState.StateHash = hash
snapshot := Snapshot{
State: initialState,
LastAppliedHLC: "0-0-0",
LastAppliedIndex: 0,
}
executor := NewExecutor(wal, snap, nil, snapshot)
// Create test proposal
proposal := TransitionProposal{
CurrentStateHash: initialState.StateHash,
TransitionName: "LICENSE_GRANTED",
InputsHash: "test-input",
Signer: "test-signer",
IdemKey: "test-idem-1",
HLC: "1-0-0000000000000001",
WindowID: "window-1",
Evidence: []string{"test-evidence"},
}
body, _ := json.Marshal(proposal)
req := httptest.NewRequest(http.MethodPost, "/transition", bytes.NewReader(body))
w := httptest.NewRecorder()
handler := handleTransition(executor)
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if success, ok := response["success"].(bool); !ok || !success {
t.Errorf("expected success=true, got %v", response)
}
if _, ok := response["state_hash"].(string); !ok {
t.Errorf("expected state_hash in response")
}
}
func TestHandleState(t *testing.T) {
wal := &mockWAL{}
snap := &mockSnapshotStore{}
initialState := OrchestratorState{
HLCLast: "1-0-0000000000000001",
}
hash, _ := computeStateHash(initialState)
initialState.StateHash = hash
snapshot := Snapshot{
State: initialState,
LastAppliedHLC: initialState.HLCLast,
LastAppliedIndex: 1,
}
executor := NewExecutor(wal, snap, nil, snapshot)
req := httptest.NewRequest(http.MethodGet, "/state", nil)
w := httptest.NewRecorder()
handler := handleState(executor)
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if stateHash, ok := response["state_hash"].(string); !ok || stateHash == "" {
t.Errorf("expected state_hash, got %v", response)
}
if hlcLast, ok := response["hlc_last"].(string); !ok || hlcLast != "1-0-0000000000000001" {
t.Errorf("expected hlc_last=1-0-0000000000000001, got %v", response)
}
}
func TestHandleHealth(t *testing.T) {
wal := &mockWAL{}
snap := &mockSnapshotStore{}
initialState := OrchestratorState{
Boot: struct {
Licensed bool
LicenseExpiry time.Time
NodeID string
}{
Licensed: true,
NodeID: "test-node",
},
Control: struct {
Paused bool
Degraded bool
Recovering bool
}{
Degraded: false,
},
Policy: struct {
Quarantined bool
Rationale string
}{
Quarantined: false,
},
HLCLast: "5-0-0000000000000005",
}
hash, _ := computeStateHash(initialState)
initialState.StateHash = hash
snapshot := Snapshot{
State: initialState,
LastAppliedHLC: "5-0-0000000000000005",
LastAppliedIndex: 5,
}
executor := NewExecutor(wal, snap, nil, snapshot)
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
handler := handleHealth(executor)
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if licensed, ok := response["licensed"].(bool); !ok || !licensed {
t.Errorf("expected licensed=true, got %v", response)
}
if quarantined, ok := response["quarantined"].(bool); !ok || quarantined {
t.Errorf("expected quarantined=false, got %v", response)
}
if degraded, ok := response["degraded"].(bool); !ok || degraded {
t.Errorf("expected degraded=false, got %v", response)
}
}
func TestHandleCouncilOpportunity_NotImplemented(t *testing.T) {
wal := &mockWAL{}
snap := &mockSnapshotStore{}
snapshot := Snapshot{State: OrchestratorState{}}
executor := NewExecutor(wal, snap, nil, snapshot)
payload := map[string]interface{}{
"council_id": "test-council",
"roles": []string{"developer", "reviewer"},
"window_id": "window-1",
"hlc": "1-0-0000000000000001",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/api/v1/opportunities/council", bytes.NewReader(body))
w := httptest.NewRecorder()
handler := handleCouncilOpportunity(executor)
handler(w, req)
// Should return 501 Not Implemented per spec
if w.Code != http.StatusNotImplemented {
t.Errorf("expected status 501, got %d", w.Code)
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if _, ok := response["error"].(string); !ok {
t.Errorf("expected error message in response")
}
}
func TestHandleTasks_NotImplemented(t *testing.T) {
wal := &mockWAL{}
snap := &mockSnapshotStore{}
snapshot := Snapshot{State: OrchestratorState{}}
executor := NewExecutor(wal, snap, nil, snapshot)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks", nil)
w := httptest.NewRecorder()
handler := handleTasks(executor)
handler(w, req)
// Should return 501 Not Implemented per spec
if w.Code != http.StatusNotImplemented {
t.Errorf("expected status 501, got %d", w.Code)
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if _, ok := response["error"].(string); !ok {
t.Errorf("expected error message in response")
}
}
// Mock implementations for testing
type mockWAL struct {
records []WALRecord
}
func (m *mockWAL) Append(record WALRecord) error {
m.records = append(m.records, record)
return nil
}
func (m *mockWAL) Replay(fromIndex uint64) ([]WALRecord, error) {
return []WALRecord{}, nil
}
func (m *mockWAL) Sync() error {
return nil
}
func (m *mockWAL) LastIndex() uint64 {
if len(m.records) == 0 {
return 0
}
return m.records[len(m.records)-1].Index
}
type mockSnapshotStore struct {
latest *Snapshot
}
func (m *mockSnapshotStore) Save(s Snapshot) error {
m.latest = &s
return nil
}
func (m *mockSnapshotStore) LoadLatest() (Snapshot, error) {
if m.latest == nil {
return Snapshot{}, fmt.Errorf("no snapshot")
}
return *m.latest, nil
}