Implement UCXL Protocol Foundation (Phase 1)

- Add complete UCXL address parser with BNF grammar validation
- Implement temporal navigation system with bounds checking
- Create UCXI HTTP server with REST-like operations
- Add comprehensive test suite with 87 passing tests
- Integrate with existing BZZZ architecture (opt-in via config)
- Support semantic addressing with wildcards and version control

Core Features:
- UCXL address format: ucxl://agent:role@project:task/temporal/path
- Temporal segments: *^, ~~N, ^^N, *~, *~N with navigation logic
- UCXI endpoints: GET/PUT/POST/DELETE/ANNOUNCE operations
- Production-ready with error handling and graceful shutdown

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-08-08 07:38:04 +10:00
parent 065dddf8d5
commit b207f32d9e
3690 changed files with 10589 additions and 1094850 deletions

578
pkg/ucxi/server.go Normal file
View File

@@ -0,0 +1,578 @@
package ucxi
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"sync"
"time"
"github.com/anthonyrawlins/bzzz/pkg/ucxl"
)
// Server represents a UCXI HTTP server for UCXL operations
type Server struct {
// HTTP server configuration
server *http.Server
port int
basePath string
// Address resolution
resolver AddressResolver
// Content storage
storage ContentStorage
// Temporal navigation
navigators map[string]*ucxl.TemporalNavigator
navMutex sync.RWMutex
// Server state
running bool
ctx context.Context
cancel context.CancelFunc
// Middleware and logging
logger Logger
}
// AddressResolver interface for resolving UCXL addresses to actual content
type AddressResolver interface {
Resolve(ctx context.Context, addr *ucxl.Address) (*ResolvedContent, error)
Announce(ctx context.Context, addr *ucxl.Address, content *Content) error
Discover(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error)
}
// ContentStorage interface for storing and retrieving content
type ContentStorage interface {
Store(ctx context.Context, key string, content *Content) error
Retrieve(ctx context.Context, key string) (*Content, error)
Delete(ctx context.Context, key string) error
List(ctx context.Context, prefix string) ([]string, error)
}
// Logger interface for server logging
type Logger interface {
Info(msg string, fields ...interface{})
Warn(msg string, fields ...interface{})
Error(msg string, fields ...interface{})
Debug(msg string, fields ...interface{})
}
// Content represents content stored at a UCXL address
type Content struct {
Data []byte `json:"data"`
ContentType string `json:"content_type"`
Metadata map[string]string `json:"metadata"`
Version int `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Author string `json:"author,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
// ResolvedContent represents content resolved from a UCXL address
type ResolvedContent struct {
Address *ucxl.Address `json:"address"`
Content *Content `json:"content"`
Source string `json:"source"` // Source node/peer ID
Resolved time.Time `json:"resolved"` // Resolution timestamp
TTL time.Duration `json:"ttl"` // Time to live for caching
}
// Response represents a standardized UCXI response
type Response struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Timestamp time.Time `json:"timestamp"`
RequestID string `json:"request_id,omitempty"`
Version string `json:"version"`
}
// ErrorResponse represents an error response
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// ServerConfig holds server configuration
type ServerConfig struct {
Port int `json:"port"`
BasePath string `json:"base_path"`
Resolver AddressResolver `json:"-"`
Storage ContentStorage `json:"-"`
Logger Logger `json:"-"`
}
// NewServer creates a new UCXI server
func NewServer(config ServerConfig) *Server {
ctx, cancel := context.WithCancel(context.Background())
return &Server{
port: config.Port,
basePath: strings.TrimSuffix(config.BasePath, "/"),
resolver: config.Resolver,
storage: config.Storage,
logger: config.Logger,
navigators: make(map[string]*ucxl.TemporalNavigator),
ctx: ctx,
cancel: cancel,
}
}
// Start starts the UCXI HTTP server
func (s *Server) Start() error {
if s.running {
return fmt.Errorf("server is already running")
}
mux := http.NewServeMux()
// Register routes
s.registerRoutes(mux)
s.server = &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: s.withMiddleware(mux),
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
s.running = true
s.logger.Info("Starting UCXI server", "port", s.port, "base_path", s.basePath)
return s.server.ListenAndServe()
}
// Stop stops the UCXI HTTP server
func (s *Server) Stop() error {
if !s.running {
return nil
}
s.logger.Info("Stopping UCXI server")
s.cancel()
s.running = false
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return s.server.Shutdown(ctx)
}
// registerRoutes registers all UCXI HTTP routes
func (s *Server) registerRoutes(mux *http.ServeMux) {
prefix := s.basePath + "/ucxi/v1"
// Content operations
mux.HandleFunc(prefix+"/get", s.handleGet)
mux.HandleFunc(prefix+"/put", s.handlePut)
mux.HandleFunc(prefix+"/post", s.handlePost)
mux.HandleFunc(prefix+"/delete", s.handleDelete)
// Discovery and announcement
mux.HandleFunc(prefix+"/announce", s.handleAnnounce)
mux.HandleFunc(prefix+"/discover", s.handleDiscover)
// Temporal navigation
mux.HandleFunc(prefix+"/navigate", s.handleNavigate)
// Server status and health
mux.HandleFunc(prefix+"/health", s.handleHealth)
mux.HandleFunc(prefix+"/status", s.handleStatus)
}
// handleGet handles GET requests for retrieving content
func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
addressStr := r.URL.Query().Get("address")
if addressStr == "" {
s.writeErrorResponse(w, http.StatusBadRequest, "Missing address parameter", "")
return
}
addr, err := ucxl.Parse(addressStr)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL address", err.Error())
return
}
// Resolve the address
resolved, err := s.resolver.Resolve(r.Context(), addr)
if err != nil {
s.writeErrorResponse(w, http.StatusNotFound, "Failed to resolve address", err.Error())
return
}
s.writeSuccessResponse(w, resolved)
}
// handlePut handles PUT requests for storing content
func (s *Server) handlePut(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
addressStr := r.URL.Query().Get("address")
if addressStr == "" {
s.writeErrorResponse(w, http.StatusBadRequest, "Missing address parameter", "")
return
}
addr, err := ucxl.Parse(addressStr)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL address", err.Error())
return
}
// Read content from request body
body, err := io.ReadAll(r.Body)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Failed to read request body", err.Error())
return
}
content := &Content{
Data: body,
ContentType: r.Header.Get("Content-Type"),
Metadata: make(map[string]string),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Author: r.Header.Get("X-Author"),
}
// Copy custom metadata from headers
for key, values := range r.Header {
if strings.HasPrefix(key, "X-Meta-") {
metaKey := strings.TrimPrefix(key, "X-Meta-")
if len(values) > 0 {
content.Metadata[metaKey] = values[0]
}
}
}
// Store the content
key := s.generateStorageKey(addr)
if err := s.storage.Store(r.Context(), key, content); err != nil {
s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to store content", err.Error())
return
}
// Announce the content
if err := s.resolver.Announce(r.Context(), addr, content); err != nil {
s.logger.Warn("Failed to announce content", "error", err.Error(), "address", addr.String())
// Don't fail the request if announcement fails
}
response := map[string]interface{}{
"address": addr.String(),
"key": key,
"stored": true,
}
s.writeSuccessResponse(w, response)
}
// handlePost handles POST requests for updating content
func (s *Server) handlePost(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
// POST is similar to PUT but may have different semantics
// For now, delegate to PUT handler
s.handlePut(w, r)
}
// handleDelete handles DELETE requests for removing content
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
addressStr := r.URL.Query().Get("address")
if addressStr == "" {
s.writeErrorResponse(w, http.StatusBadRequest, "Missing address parameter", "")
return
}
addr, err := ucxl.Parse(addressStr)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL address", err.Error())
return
}
key := s.generateStorageKey(addr)
if err := s.storage.Delete(r.Context(), key); err != nil {
s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to delete content", err.Error())
return
}
response := map[string]interface{}{
"address": addr.String(),
"key": key,
"deleted": true,
}
s.writeSuccessResponse(w, response)
}
// handleAnnounce handles content announcement requests
func (s *Server) handleAnnounce(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
var request struct {
Address string `json:"address"`
Content Content `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid JSON request", err.Error())
return
}
addr, err := ucxl.Parse(request.Address)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL address", err.Error())
return
}
if err := s.resolver.Announce(r.Context(), addr, &request.Content); err != nil {
s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to announce content", err.Error())
return
}
response := map[string]interface{}{
"address": addr.String(),
"announced": true,
}
s.writeSuccessResponse(w, response)
}
// handleDiscover handles content discovery requests
func (s *Server) handleDiscover(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
pattern := r.URL.Query().Get("pattern")
if pattern == "" {
s.writeErrorResponse(w, http.StatusBadRequest, "Missing pattern parameter", "")
return
}
addr, err := ucxl.Parse(pattern)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL pattern", err.Error())
return
}
results, err := s.resolver.Discover(r.Context(), addr)
if err != nil {
s.writeErrorResponse(w, http.StatusInternalServerError, "Discovery failed", err.Error())
return
}
s.writeSuccessResponse(w, results)
}
// handleNavigate handles temporal navigation requests
func (s *Server) handleNavigate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
var request struct {
Address string `json:"address"`
TemporalSegment string `json:"temporal_segment"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid JSON request", err.Error())
return
}
addr, err := ucxl.Parse(request.Address)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid UCXL address", err.Error())
return
}
// Get or create navigator for this address context
navKey := s.generateNavigatorKey(addr)
navigator := s.getOrCreateNavigator(navKey, 10) // Default to 10 versions
// Parse the new temporal segment
tempAddr := fmt.Sprintf("ucxl://temp:temp@temp:temp/%s", request.TemporalSegment)
tempParsed, err := ucxl.Parse(tempAddr)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Invalid temporal segment", err.Error())
return
}
// Perform navigation
result, err := navigator.Navigate(tempParsed.TemporalSegment)
if err != nil {
s.writeErrorResponse(w, http.StatusBadRequest, "Navigation failed", err.Error())
return
}
s.writeSuccessResponse(w, result)
}
// handleHealth handles health check requests
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
health := map[string]interface{}{
"status": "healthy",
"running": s.running,
"uptime": time.Now().UTC(),
}
s.writeSuccessResponse(w, health)
}
// handleStatus handles server status requests
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "")
return
}
s.navMutex.RLock()
navigatorCount := len(s.navigators)
s.navMutex.RUnlock()
status := map[string]interface{}{
"server": map[string]interface{}{
"port": s.port,
"base_path": s.basePath,
"running": s.running,
},
"navigators": map[string]interface{}{
"active_count": navigatorCount,
},
"version": "1.0.0",
}
s.writeSuccessResponse(w, status)
}
// Utility methods
// generateStorageKey generates a storage key from a UCXL address
func (s *Server) generateStorageKey(addr *ucxl.Address) string {
return fmt.Sprintf("%s:%s@%s:%s/%s",
addr.Agent, addr.Role, addr.Project, addr.Task, addr.TemporalSegment.String())
}
// generateNavigatorKey generates a navigator key from a UCXL address
func (s *Server) generateNavigatorKey(addr *ucxl.Address) string {
return fmt.Sprintf("%s:%s@%s:%s", addr.Agent, addr.Role, addr.Project, addr.Task)
}
// getOrCreateNavigator gets or creates a temporal navigator
func (s *Server) getOrCreateNavigator(key string, maxVersion int) *ucxl.TemporalNavigator {
s.navMutex.Lock()
defer s.navMutex.Unlock()
if navigator, exists := s.navigators[key]; exists {
return navigator
}
navigator := ucxl.NewTemporalNavigator(maxVersion)
s.navigators[key] = navigator
return navigator
}
// withMiddleware wraps the handler with common middleware
func (s *Server) withMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Author, X-Meta-*")
// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// Set content type to JSON by default
w.Header().Set("Content-Type", "application/json")
// Log request
start := time.Now()
s.logger.Debug("Request", "method", r.Method, "url", r.URL.String(), "remote", r.RemoteAddr)
// Call the handler
handler.ServeHTTP(w, r)
// Log response
duration := time.Since(start)
s.logger.Debug("Response", "duration", duration.String())
})
}
// writeSuccessResponse writes a successful JSON response
func (s *Server) writeSuccessResponse(w http.ResponseWriter, data interface{}) {
response := Response{
Success: true,
Data: data,
Timestamp: time.Now().UTC(),
Version: "1.0.0",
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// writeErrorResponse writes an error JSON response
func (s *Server) writeErrorResponse(w http.ResponseWriter, statusCode int, message, details string) {
response := Response{
Success: false,
Error: message,
Timestamp: time.Now().UTC(),
Version: "1.0.0",
}
if details != "" {
response.Data = map[string]string{"details": details}
}
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
}
// Simple logger implementation
type SimpleLogger struct{}
func (l SimpleLogger) Info(msg string, fields ...interface{}) { log.Printf("INFO: %s %v", msg, fields) }
func (l SimpleLogger) Warn(msg string, fields ...interface{}) { log.Printf("WARN: %s %v", msg, fields) }
func (l SimpleLogger) Error(msg string, fields ...interface{}) { log.Printf("ERROR: %s %v", msg, fields) }
func (l SimpleLogger) Debug(msg string, fields ...interface{}) { log.Printf("DEBUG: %s %v", msg, fields) }