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:
452
API_INTEGRATION.md
Normal file
452
API_INTEGRATION.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# SWOOSH API Integration Guide
|
||||
|
||||
**Date:** 2025-10-25
|
||||
**CHORUS Commit:** `17673c3` (v0.5.5)
|
||||
**Integration Method:** HTTP REST API
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the HTTP API layer for SWOOSH, designed to integrate with CHORUS agents at commit `17673c3` using the existing WHOOSH HTTP integration pattern.
|
||||
|
||||
**Key Principle:** The `Executor` is the **single source of truth**. The API layer is a thin adapter that submits transitions and retrieves snapshots. No state is maintained outside the executor.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
CHORUS Agents (TCP/libp2p mesh)
|
||||
↓
|
||||
HTTP REST API
|
||||
↓
|
||||
SWOOSH API Layer (api.go)
|
||||
↓
|
||||
Executor (executor.go)
|
||||
↓
|
||||
Reducer (reducer.go) ← Single source of truth
|
||||
↓
|
||||
WAL + Snapshot
|
||||
```
|
||||
|
||||
### Strict Constraints
|
||||
|
||||
1. **No state outside Executor**: API handlers only call `SubmitTransition()` or `GetStateSnapshot()`
|
||||
2. **No background goroutines**: All concurrency managed by Executor's single-threaded loop
|
||||
3. **No invented transitions**: Only catalogued transitions in `reducer.go` are valid
|
||||
4. **Deterministic mapping required**: External requests must map to exactly one transition
|
||||
5. **501 for unmappable requests**: If input cannot be deterministically mapped, return HTTP 501
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Core SWOOSH Endpoints
|
||||
|
||||
#### `POST /transition`
|
||||
|
||||
Submit a state transition proposal.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"current_state_hash": "abc123...",
|
||||
"transition": "LICENSE_GRANTED",
|
||||
"inputs_hash": "def456...",
|
||||
"signer": "node-001",
|
||||
"idem_key": "unique-operation-id",
|
||||
"hlc": "1-0-0000000000000001",
|
||||
"window_id": "window-1",
|
||||
"evidence": ["ucxl://proof/123"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"state_hash": "new-hash-xyz...",
|
||||
"quarantined": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response (400 Bad Request):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "guard rejected transition",
|
||||
"state_hash": "unchanged-hash",
|
||||
"quarantined": false
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Calls `executor.SubmitTransition(proposal)`
|
||||
- Blocks on result channel
|
||||
- Returns transition outcome
|
||||
|
||||
---
|
||||
|
||||
#### `GET /state`
|
||||
|
||||
Retrieve current orchestrator state snapshot.
|
||||
|
||||
**Query Parameters:**
|
||||
- `projection` (reserved for future use)
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"state_hash": "abc123...",
|
||||
"hlc_last": "5-0-0000000000000005",
|
||||
"projection": {
|
||||
"Meta": {...},
|
||||
"Boot": {...},
|
||||
"Ingestion": {...},
|
||||
"Council": {...},
|
||||
"Environment": {...},
|
||||
"Execution": {...},
|
||||
"Control": {...},
|
||||
"Policy": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Calls `executor.GetStateSnapshot()` (deep copy)
|
||||
- Returns full state structure
|
||||
|
||||
---
|
||||
|
||||
#### `GET /health`
|
||||
|
||||
Health check endpoint for monitoring.
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"licensed": true,
|
||||
"quarantined": false,
|
||||
"degraded": false,
|
||||
"recovering": false,
|
||||
"hlc_last": "10-0-0000000000000010",
|
||||
"state_hash": "current-hash"
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Calls `executor.GetStateSnapshot()`
|
||||
- Extracts health-relevant fields
|
||||
|
||||
---
|
||||
|
||||
### WHOOSH-Compatible Adapter Endpoints
|
||||
|
||||
#### `POST /api/v1/opportunities/council`
|
||||
|
||||
**Status:** ⚠️ **501 Not Implemented**
|
||||
|
||||
**Reason:** Cannot deterministically map WHOOSH council lifecycle to SWOOSH transitions without defining specific `COUNCIL_*` transitions in `reducer.go`.
|
||||
|
||||
**Future Implementation:**
|
||||
Once `reducer.go` defines transitions like:
|
||||
- `COUNCIL_PROFILES_LOADED`
|
||||
- `COUNCIL_QUORUM_CERT`
|
||||
- `COUNCIL_ELECT_COMPLETE`
|
||||
|
||||
This handler will:
|
||||
1. Parse WHOOSH council opportunity payload
|
||||
2. Map to appropriate SWOOSH transition
|
||||
3. Build `TransitionProposal`
|
||||
4. Submit via `executor.SubmitTransition()`
|
||||
|
||||
**Current Response (501 Not Implemented):**
|
||||
```json
|
||||
{
|
||||
"error": "council opportunity mapping not yet implemented",
|
||||
"reason": "cannot deterministically map WHOOSH council lifecycle to SWOOSH transitions",
|
||||
"contact": "define COUNCIL_* transitions in reducer.go first"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/tasks`
|
||||
|
||||
**Status:** ⚠️ **501 Not Implemented**
|
||||
|
||||
**Reason:** `OrchestratorState` does not contain a task queue. SWOOSH uses deterministic state-machine phases, not task lists.
|
||||
|
||||
**Architecture Note:** SWOOSH's state machine tracks execution phases (`PLAN`, `WORK`, `REVIEW`, `REVERB`) but not individual tasks. Tasks are managed externally (e.g., GITEA issues) and referenced via:
|
||||
- `Execution.ActiveWindowID`
|
||||
- `Execution.BeatIndex`
|
||||
- Evidence arrays in transition proposals
|
||||
|
||||
**Current Response (501 Not Implemented):**
|
||||
```json
|
||||
{
|
||||
"error": "task listing not yet implemented",
|
||||
"reason": "OrchestratorState does not contain task queue",
|
||||
"note": "SWOOSH uses deterministic state-machine, not task queues"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with CHORUS
|
||||
|
||||
### Docker Compose Configuration
|
||||
|
||||
**In `/home/tony/chorus/project-queues/active/CHORUS/docker/docker-compose.yml`:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
chorus:
|
||||
image: anthonyrawlins/chorus:0.5.48
|
||||
environment:
|
||||
- WHOOSH_API_BASE_URL=${SWOOSH_API_BASE_URL:-http://swoosh:8080}
|
||||
- WHOOSH_API_ENABLED=true
|
||||
# ... other CHORUS env vars
|
||||
|
||||
swoosh:
|
||||
image: your-registry/swoosh:latest
|
||||
ports:
|
||||
- target: 8080
|
||||
published: 8800
|
||||
protocol: tcp
|
||||
mode: ingress
|
||||
environment:
|
||||
- SWOOSH_LISTEN_ADDR=:8080
|
||||
- SWOOSH_WAL_DIR=/app/data/wal
|
||||
- SWOOSH_SNAPSHOT_DIR=/app/data/snapshots
|
||||
- SWOOSH_DATABASE_DB_HOST=postgres
|
||||
- SWOOSH_DATABASE_DB_PORT=5432
|
||||
- SWOOSH_DATABASE_DB_NAME=swoosh
|
||||
# ... other SWOOSH config
|
||||
volumes:
|
||||
- swoosh_data:/app/data
|
||||
networks:
|
||||
- tengig
|
||||
- chorus_ipvlan
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
|
||||
### Transition Mapping
|
||||
|
||||
When CHORUS communicates with SWOOSH, external API calls must map to internal transitions:
|
||||
|
||||
| CHORUS Request | SWOOSH Transition | Status |
|
||||
|----------------|-------------------|--------|
|
||||
| `POST /api/v1/opportunities/council` | `COUNCIL_PROFILES_LOADED` (proposed) | 501 - Not Implemented |
|
||||
| `POST /api/v1/councils/{id}/claims` | `COUNCIL_ROLE_CLAIMED` (proposed) | 501 - Not Implemented |
|
||||
| `GET /api/v1/tasks` | N/A (state query, not transition) | 501 - Not Implemented |
|
||||
| `POST /api/v1/tasks/{id}/claim` | `EXECUTION_TASK_CLAIMED` (proposed) | 501 - Not Implemented |
|
||||
| `POST /api/v1/tasks/{id}/complete` | `EXECUTION_TASK_COMPLETE` (proposed) | 501 - Not Implemented |
|
||||
|
||||
**Action Required:** Define these transitions in `reducer.go` to enable WHOOSH-compatible endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Running SWOOSH Server
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cd /home/tony/chorus/SWOOSH
|
||||
go build -o build/swoosh-server ./cmd/swoosh-server
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
export SWOOSH_LISTEN_ADDR=:8080
|
||||
export SWOOSH_WAL_DIR=/path/to/wal
|
||||
export SWOOSH_SNAPSHOT_DIR=/path/to/snapshots
|
||||
|
||||
./build/swoosh-server
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:8080/health
|
||||
|
||||
# Get state
|
||||
curl http://localhost:8080/state
|
||||
|
||||
# Submit transition
|
||||
curl -X POST http://localhost:8080/transition \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"current_state_hash": "genesis",
|
||||
"transition": "LICENSE_GRANTED",
|
||||
"inputs_hash": "test",
|
||||
"signer": "test-node",
|
||||
"idem_key": "unique-1",
|
||||
"hlc": "1-0-0000000000000001",
|
||||
"window_id": "window-1",
|
||||
"evidence": []
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd /home/tony/chorus/SWOOSH
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
**Test Coverage:**
|
||||
- ✅ `POST /transition` - Success and failure cases
|
||||
- ✅ `GET /state` - Snapshot retrieval
|
||||
- ✅ `GET /health` - Health status
|
||||
- ✅ `POST /api/v1/opportunities/council` - Returns 501
|
||||
- ✅ `GET /api/v1/tasks` - Returns 501
|
||||
|
||||
---
|
||||
|
||||
## Next Steps for Full CHORUS Integration
|
||||
|
||||
### 1. Define CHORUS-Compatible Transitions
|
||||
|
||||
In `reducer.go`, add:
|
||||
|
||||
```go
|
||||
case "COUNCIL_PROFILES_LOADED":
|
||||
if state.Council.Phase != "PLAN_ROLES" {
|
||||
return false, ErrInvalidPhase
|
||||
}
|
||||
// Parse PlannedRoles from proposal.Evidence
|
||||
// Update state.Council.PlannedRoles
|
||||
state.Council.Phase = "ELECT"
|
||||
state.Council.Epoch++
|
||||
return true, nil
|
||||
|
||||
case "COUNCIL_ROLE_CLAIMED":
|
||||
if state.Council.Phase != "ELECT" {
|
||||
return false, ErrInvalidPhase
|
||||
}
|
||||
// Parse CouncilMember from proposal.Evidence
|
||||
// Append to state.Council.Members
|
||||
// Check if quorum reached
|
||||
if quorumReached(state.Council.Members) {
|
||||
state.Council.Phase = "TOOLING_SYNC"
|
||||
}
|
||||
state.Council.Epoch++
|
||||
return true, nil
|
||||
|
||||
// ... additional transitions
|
||||
```
|
||||
|
||||
### 2. Update API Handlers
|
||||
|
||||
Once transitions are defined, update `api.go`:
|
||||
|
||||
```go
|
||||
func handleCouncilOpportunity(executor *Executor) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse request
|
||||
var req CouncilOpportunityRequest
|
||||
json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
// Build transition proposal
|
||||
proposal := TransitionProposal{
|
||||
TransitionName: "COUNCIL_PROFILES_LOADED",
|
||||
Evidence: []string{
|
||||
encodeCouncilRoles(req.Roles),
|
||||
},
|
||||
HLC: req.HLC,
|
||||
WindowID: req.WindowID,
|
||||
// ... other fields
|
||||
}
|
||||
|
||||
// Submit to executor
|
||||
resultCh, _ := executor.SubmitTransition(proposal)
|
||||
result := <-resultCh
|
||||
|
||||
// Return response
|
||||
writeJSON(w, http.StatusOK, result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Implement GuardProvider
|
||||
|
||||
Create production guard implementations:
|
||||
|
||||
```go
|
||||
type ProductionGuardProvider struct {
|
||||
kachingClient *KachingClient
|
||||
backbeatClient *BackbeatClient
|
||||
hmmmClient *HMMMClient
|
||||
shhhClient *SHHHClient
|
||||
mcpHealthClient *MCPHealthClient
|
||||
}
|
||||
|
||||
func (p *ProductionGuardProvider) Evaluate(t TransitionProposal, s OrchestratorState) (GuardOutcome, error) {
|
||||
outcome := GuardOutcome{}
|
||||
|
||||
// Check KACHING license
|
||||
outcome.LicenseOK = p.kachingClient.ValidateLicense(s.Boot.NodeID)
|
||||
|
||||
// Check BACKBEAT heartbeat
|
||||
outcome.BackbeatOK = p.backbeatClient.CheckHeartbeat()
|
||||
|
||||
// Check HMMM quorum
|
||||
outcome.QuorumOK = p.hmmmClient.CheckQuorum()
|
||||
|
||||
// Check SHHH policy
|
||||
outcome.PolicyOK = p.shhhClient.CheckPolicy(t)
|
||||
|
||||
// Check MCP health
|
||||
outcome.MCPHealthy = p.mcpHealthClient.CheckHealth()
|
||||
|
||||
return outcome, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Persistent WAL/Snapshot Stores
|
||||
|
||||
Replace in-memory implementations with BadgerDB WAL and atomic file snapshots (already defined in `wal.go` and `snapshot.go`).
|
||||
|
||||
---
|
||||
|
||||
## Design Compliance Checklist
|
||||
|
||||
- ✅ **Single source of truth**: Only `Executor` mutates state
|
||||
- ✅ **No external state**: API handlers have no local caches or maps
|
||||
- ✅ **Deterministic transitions only**: All mutations via `reducer.go`
|
||||
- ✅ **HTTP 501 for unmappable requests**: WHOOSH endpoints return 501 until transitions defined
|
||||
- ✅ **Blocking on executor**: All API calls wait for executor result channel
|
||||
- ✅ **Deep copies for reads**: `GetStateSnapshot()` returns isolated state
|
||||
- ✅ **No background goroutines**: Single-threaded executor model preserved
|
||||
- ✅ **Standard library only**: Uses `net/http` and `encoding/json`
|
||||
- ✅ **Compiles successfully**: `go build ./...` passes
|
||||
- ✅ **Test coverage**: All handlers tested
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The HTTP API layer (`api.go`) is a **thin, stateless adapter** that:
|
||||
|
||||
1. Accepts HTTP requests
|
||||
2. Maps to `TransitionProposal` structures
|
||||
3. Submits to `executor.SubmitTransition()`
|
||||
4. Blocks on result channel
|
||||
5. Returns HTTP response
|
||||
|
||||
**No orchestration logic lives in the API layer.** All state transitions, validation, and persistence are handled by the `Executor` and `Reducer`.
|
||||
|
||||
WHOOSH-compatible endpoints return **HTTP 501 Not Implemented** until corresponding transitions are defined in `reducer.go`, ensuring we never mutate state without deterministic transitions.
|
||||
|
||||
For CHORUS integration at commit `17673c3`, simply point `WHOOSH_API_BASE_URL` to SWOOSH's HTTP endpoint and define the required transitions.
|
||||
Reference in New Issue
Block a user