Complete BZZZ functionality port to CHORUS
🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
369
pkg/ucxl/address.go
Normal file
369
pkg/ucxl/address.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package ucxl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Address represents a parsed UCXL address
|
||||
// Format: ucxl://agent:role@project:task/temporal_segment/path
|
||||
type Address struct {
|
||||
// Core components
|
||||
Agent string `json:"agent"`
|
||||
Role string `json:"role"`
|
||||
Project string `json:"project"`
|
||||
Task string `json:"task"`
|
||||
|
||||
// Temporal component
|
||||
TemporalSegment TemporalSegment `json:"temporal_segment"`
|
||||
|
||||
// Path component
|
||||
Path string `json:"path"`
|
||||
|
||||
// Original raw address for reference
|
||||
Raw string `json:"raw"`
|
||||
}
|
||||
|
||||
// TemporalSegment represents temporal navigation information
|
||||
type TemporalSegment struct {
|
||||
Type TemporalType `json:"type"`
|
||||
Direction Direction `json:"direction,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
}
|
||||
|
||||
// TemporalType defines the type of temporal navigation
|
||||
type TemporalType string
|
||||
|
||||
const (
|
||||
TemporalLatest TemporalType = "latest" // *^
|
||||
TemporalAny TemporalType = "any" // *~
|
||||
TemporalSpecific TemporalType = "specific" // *~N
|
||||
TemporalRelative TemporalType = "relative" // ~~N, ^^N
|
||||
)
|
||||
|
||||
// Direction defines temporal navigation direction
|
||||
type Direction string
|
||||
|
||||
const (
|
||||
DirectionBackward Direction = "backward" // ~~
|
||||
DirectionForward Direction = "forward" // ^^
|
||||
)
|
||||
|
||||
// ValidationError represents an address validation error
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
Raw string
|
||||
}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return fmt.Sprintf("UCXL address validation error in %s: %s (address: %s)", e.Field, e.Message, e.Raw)
|
||||
}
|
||||
|
||||
// Regular expressions for validation
|
||||
var (
|
||||
// Component validation patterns
|
||||
componentPattern = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$|^any$`)
|
||||
pathPattern = regexp.MustCompile(`^[a-zA-Z0-9_\-/\.]*$`)
|
||||
|
||||
// Temporal segment patterns
|
||||
temporalLatestPattern = regexp.MustCompile(`^\*\^$`) // *^
|
||||
temporalAnyPattern = regexp.MustCompile(`^\*~$`) // *~
|
||||
temporalSpecificPattern = regexp.MustCompile(`^\*~(\d+)$`) // *~N
|
||||
temporalBackwardPattern = regexp.MustCompile(`^~~(\d+)$`) // ~~N
|
||||
temporalForwardPattern = regexp.MustCompile(`^\^\^(\d+)$`) // ^^N
|
||||
|
||||
// Full address pattern for initial validation
|
||||
ucxlAddressPattern = regexp.MustCompile(`^ucxl://([^:]+):([^@]+)@([^:]+):([^/]+)/([^/]+)/?(.*)$`)
|
||||
)
|
||||
|
||||
// Parse parses a UCXL address string into an Address struct
|
||||
func Parse(address string) (*Address, error) {
|
||||
if address == "" {
|
||||
return nil, &ValidationError{
|
||||
Field: "address",
|
||||
Message: "address cannot be empty",
|
||||
Raw: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the address (trim whitespace, convert to lowercase for scheme)
|
||||
normalized := strings.TrimSpace(address)
|
||||
if !strings.HasPrefix(strings.ToLower(normalized), "ucxl://") {
|
||||
return nil, &ValidationError{
|
||||
Field: "scheme",
|
||||
Message: "address must start with 'ucxl://'",
|
||||
Raw: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Check scheme manually since our format doesn't follow standard URL format
|
||||
if !strings.HasPrefix(strings.ToLower(normalized), "ucxl://") {
|
||||
return nil, &ValidationError{
|
||||
Field: "scheme",
|
||||
Message: "scheme must be 'ucxl'",
|
||||
Raw: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Use regex for detailed component extraction
|
||||
// Convert to lowercase for scheme but keep original for case-sensitive parts
|
||||
normalizedForPattern := strings.ToLower(normalized[:7]) + normalized[7:] // normalize "ucxl://" part
|
||||
matches := ucxlAddressPattern.FindStringSubmatch(normalizedForPattern)
|
||||
if matches == nil || len(matches) != 7 {
|
||||
return nil, &ValidationError{
|
||||
Field: "format",
|
||||
Message: "address format must be 'ucxl://agent:role@project:task/temporal_segment/path'",
|
||||
Raw: address,
|
||||
}
|
||||
}
|
||||
|
||||
addr := &Address{
|
||||
Agent: normalizeComponent(matches[1]),
|
||||
Role: normalizeComponent(matches[2]),
|
||||
Project: normalizeComponent(matches[3]),
|
||||
Task: normalizeComponent(matches[4]),
|
||||
Path: matches[6], // Path can be empty
|
||||
Raw: address,
|
||||
}
|
||||
|
||||
// Parse temporal segment
|
||||
temporalSegment, err := parseTemporalSegment(matches[5])
|
||||
if err != nil {
|
||||
return nil, &ValidationError{
|
||||
Field: "temporal_segment",
|
||||
Message: err.Error(),
|
||||
Raw: address,
|
||||
}
|
||||
}
|
||||
addr.TemporalSegment = *temporalSegment
|
||||
|
||||
// Validate all components
|
||||
if err := addr.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// parseTemporalSegment parses the temporal segment component
|
||||
func parseTemporalSegment(segment string) (*TemporalSegment, error) {
|
||||
if segment == "" {
|
||||
return nil, fmt.Errorf("temporal segment cannot be empty")
|
||||
}
|
||||
|
||||
// Check for latest (*^)
|
||||
if temporalLatestPattern.MatchString(segment) {
|
||||
return &TemporalSegment{Type: TemporalLatest}, nil
|
||||
}
|
||||
|
||||
// Check for any (*~)
|
||||
if temporalAnyPattern.MatchString(segment) {
|
||||
return &TemporalSegment{Type: TemporalAny}, nil
|
||||
}
|
||||
|
||||
// Check for specific version (*~N)
|
||||
if matches := temporalSpecificPattern.FindStringSubmatch(segment); matches != nil {
|
||||
count, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid version number in specific temporal segment: %s", matches[1])
|
||||
}
|
||||
if count < 0 {
|
||||
return nil, fmt.Errorf("version number cannot be negative: %d", count)
|
||||
}
|
||||
return &TemporalSegment{
|
||||
Type: TemporalSpecific,
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check for backward navigation (~~N)
|
||||
if matches := temporalBackwardPattern.FindStringSubmatch(segment); matches != nil {
|
||||
count, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid count in backward temporal segment: %s", matches[1])
|
||||
}
|
||||
if count < 0 {
|
||||
return nil, fmt.Errorf("backward count cannot be negative: %d", count)
|
||||
}
|
||||
return &TemporalSegment{
|
||||
Type: TemporalRelative,
|
||||
Direction: DirectionBackward,
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check for forward navigation (^^N)
|
||||
if matches := temporalForwardPattern.FindStringSubmatch(segment); matches != nil {
|
||||
count, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid count in forward temporal segment: %s", matches[1])
|
||||
}
|
||||
if count < 0 {
|
||||
return nil, fmt.Errorf("forward count cannot be negative: %d", count)
|
||||
}
|
||||
return &TemporalSegment{
|
||||
Type: TemporalRelative,
|
||||
Direction: DirectionForward,
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid temporal segment format: %s", segment)
|
||||
}
|
||||
|
||||
// normalizeComponent normalizes address components (case-insensitive)
|
||||
func normalizeComponent(component string) string {
|
||||
return strings.ToLower(strings.TrimSpace(component))
|
||||
}
|
||||
|
||||
// Validate validates the Address components according to BNF grammar rules
|
||||
func (a *Address) Validate() error {
|
||||
// Validate agent component
|
||||
if err := validateComponent("agent", a.Agent); err != nil {
|
||||
return &ValidationError{
|
||||
Field: "agent",
|
||||
Message: err.Error(),
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate role component
|
||||
if err := validateComponent("role", a.Role); err != nil {
|
||||
return &ValidationError{
|
||||
Field: "role",
|
||||
Message: err.Error(),
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate project component
|
||||
if err := validateComponent("project", a.Project); err != nil {
|
||||
return &ValidationError{
|
||||
Field: "project",
|
||||
Message: err.Error(),
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate task component
|
||||
if err := validateComponent("task", a.Task); err != nil {
|
||||
return &ValidationError{
|
||||
Field: "task",
|
||||
Message: err.Error(),
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate path component (can be empty)
|
||||
if a.Path != "" && !pathPattern.MatchString(a.Path) {
|
||||
return &ValidationError{
|
||||
Field: "path",
|
||||
Message: "path can only contain alphanumeric characters, underscores, hyphens, forward slashes, and dots",
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateComponent validates individual address components
|
||||
func validateComponent(name, component string) error {
|
||||
if component == "" {
|
||||
return fmt.Errorf("%s cannot be empty", name)
|
||||
}
|
||||
|
||||
if !componentPattern.MatchString(component) {
|
||||
return fmt.Errorf("%s can only contain alphanumeric characters, underscores, hyphens, or be 'any'", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the canonical string representation of the address
|
||||
func (a *Address) String() string {
|
||||
temporalStr := a.TemporalSegment.String()
|
||||
if a.Path != "" {
|
||||
return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s/%s", a.Agent, a.Role, a.Project, a.Task, temporalStr, a.Path)
|
||||
}
|
||||
return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s", a.Agent, a.Role, a.Project, a.Task, temporalStr)
|
||||
}
|
||||
|
||||
// String returns the string representation of the temporal segment
|
||||
func (ts *TemporalSegment) String() string {
|
||||
switch ts.Type {
|
||||
case TemporalLatest:
|
||||
return "*^"
|
||||
case TemporalAny:
|
||||
return "*~"
|
||||
case TemporalSpecific:
|
||||
return fmt.Sprintf("*~%d", ts.Count)
|
||||
case TemporalRelative:
|
||||
if ts.Direction == DirectionBackward {
|
||||
return fmt.Sprintf("~~%d", ts.Count)
|
||||
}
|
||||
return fmt.Sprintf("^^%d", ts.Count)
|
||||
default:
|
||||
return "*^" // Default to latest
|
||||
}
|
||||
}
|
||||
|
||||
// IsWildcard returns true if the address uses wildcard patterns
|
||||
func (a *Address) IsWildcard() bool {
|
||||
return a.Agent == "any" || a.Role == "any" || a.Project == "any" || a.Task == "any"
|
||||
}
|
||||
|
||||
// Matches returns true if this address matches the pattern address
|
||||
// Supports wildcard matching where "any" matches any value
|
||||
func (a *Address) Matches(pattern *Address) bool {
|
||||
if pattern == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check each component for wildcard or exact match
|
||||
if pattern.Agent != "any" && a.Agent != pattern.Agent {
|
||||
return false
|
||||
}
|
||||
if pattern.Role != "any" && a.Role != pattern.Role {
|
||||
return false
|
||||
}
|
||||
if pattern.Project != "any" && a.Project != pattern.Project {
|
||||
return false
|
||||
}
|
||||
if pattern.Task != "any" && a.Task != pattern.Task {
|
||||
return false
|
||||
}
|
||||
|
||||
// Path matching (if pattern has path, address must match or be subset)
|
||||
if pattern.Path != "" {
|
||||
if a.Path == "" {
|
||||
return false
|
||||
}
|
||||
// Simple prefix matching for paths
|
||||
if !strings.HasPrefix(a.Path, pattern.Path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the address
|
||||
func (a *Address) Clone() *Address {
|
||||
return &Address{
|
||||
Agent: a.Agent,
|
||||
Role: a.Role,
|
||||
Project: a.Project,
|
||||
Task: a.Task,
|
||||
TemporalSegment: a.TemporalSegment, // TemporalSegment is a value type, safe to copy
|
||||
Path: a.Path,
|
||||
Raw: a.Raw,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid performs comprehensive validation and returns true if the address is valid
|
||||
func (a *Address) IsValid() bool {
|
||||
return a.Validate() == nil
|
||||
}
|
||||
Reference in New Issue
Block a user