Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
528 lines
20 KiB
Go
528 lines
20 KiB
Go
package context
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"chorus/pkg/ucxl"
|
|
"chorus/pkg/config"
|
|
)
|
|
|
|
// ContextResolver defines the interface for hierarchical context resolution
|
|
//
|
|
// The resolver implements bounded hierarchy traversal with caching and
|
|
// role-based access control, providing efficient context resolution for
|
|
// UCXL addresses through cascading inheritance patterns.
|
|
type ContextResolver interface {
|
|
// Resolve resolves context for a UCXL address using bounded hierarchy traversal
|
|
// with default depth limits and role-based access control
|
|
Resolve(ctx context.Context, address ucxl.Address, role string) (*ResolvedContext, error)
|
|
|
|
// ResolveWithDepth resolves context with custom bounded depth limit
|
|
// providing fine-grained control over hierarchy traversal depth for
|
|
// performance optimization and resource management
|
|
ResolveWithDepth(ctx context.Context, address ucxl.Address, role string, maxDepth int) (*ResolvedContext, error)
|
|
|
|
// BatchResolve efficiently resolves multiple UCXL addresses in parallel
|
|
// uses request deduplication, shared caching, and role-based filtering
|
|
// for optimal performance with bulk operations
|
|
BatchResolve(ctx context.Context, request *BatchResolutionRequest) (*BatchResolutionResult, error)
|
|
|
|
// AddGlobalContext adds a global context that applies to all addresses
|
|
// global contexts are automatically merged into all resolution results
|
|
AddGlobalContext(ctx context.Context, globalCtx *ContextNode) error
|
|
|
|
// SetHierarchyDepthLimit sets the maximum hierarchy depth for bounded traversal
|
|
// prevents infinite loops and controls resource usage during resolution
|
|
SetHierarchyDepthLimit(maxDepth int)
|
|
|
|
// GetResolutionStatistics returns resolver performance and operational statistics
|
|
GetStatistics() *ResolutionStatistics
|
|
|
|
// InvalidateCache invalidates cached resolutions for an address pattern
|
|
// useful for cache invalidation when contexts change
|
|
InvalidateCache(pattern string) error
|
|
|
|
// ClearCache clears all cached resolutions
|
|
ClearCache() error
|
|
}
|
|
|
|
// HierarchyManager manages the context hierarchy with bounded traversal
|
|
//
|
|
// Provides operations for maintaining the hierarchical structure of
|
|
// context nodes while enforcing depth limits and consistency constraints.
|
|
type HierarchyManager interface {
|
|
// LoadHierarchy loads the context hierarchy from storage
|
|
LoadHierarchy(ctx context.Context) error
|
|
|
|
// AddNode adds a context node to the hierarchy with validation
|
|
AddNode(ctx context.Context, node *ContextNode) error
|
|
|
|
// UpdateNode updates an existing context node
|
|
UpdateNode(ctx context.Context, node *ContextNode) error
|
|
|
|
// RemoveNode removes a context node and handles orphaned children
|
|
RemoveNode(ctx context.Context, path string) error
|
|
|
|
// GetNode retrieves a context node by path
|
|
GetNode(ctx context.Context, path string) (*ContextNode, error)
|
|
|
|
// TraverseUp traverses up the hierarchy with bounded depth
|
|
TraverseUp(ctx context.Context, startPath string, maxDepth int) ([]*ContextNode, error)
|
|
|
|
// TraverseDown traverses down the hierarchy with bounded depth
|
|
TraverseDown(ctx context.Context, startPath string, maxDepth int) ([]*ContextNode, error)
|
|
|
|
// GetChildren gets immediate children of a node
|
|
GetChildren(ctx context.Context, path string) ([]*ContextNode, error)
|
|
|
|
// GetParent gets the immediate parent of a node
|
|
GetParent(ctx context.Context, path string) (*ContextNode, error)
|
|
|
|
// ValidateHierarchy validates hierarchy integrity and constraints
|
|
ValidateHierarchy(ctx context.Context) error
|
|
|
|
// GetHierarchyStats returns statistics about the hierarchy
|
|
GetHierarchyStats(ctx context.Context) (*HierarchyStats, error)
|
|
}
|
|
|
|
// GlobalContextManager manages global contexts that apply everywhere
|
|
//
|
|
// Global contexts provide system-wide applicable metadata that is
|
|
// automatically included in all context resolutions regardless of
|
|
// hierarchy position.
|
|
type GlobalContextManager interface {
|
|
// AddGlobalContext adds a context that applies globally
|
|
AddGlobalContext(ctx context.Context, globalCtx *ContextNode) error
|
|
|
|
// RemoveGlobalContext removes a global context
|
|
RemoveGlobalContext(ctx context.Context, contextID string) error
|
|
|
|
// UpdateGlobalContext updates an existing global context
|
|
UpdateGlobalContext(ctx context.Context, globalCtx *ContextNode) error
|
|
|
|
// ListGlobalContexts lists all global contexts ordered by priority
|
|
ListGlobalContexts(ctx context.Context) ([]*ContextNode, error)
|
|
|
|
// GetGlobalContext retrieves a specific global context
|
|
GetGlobalContext(ctx context.Context, contextID string) (*ContextNode, error)
|
|
|
|
// ApplyGlobalContexts applies global contexts to a resolution
|
|
ApplyGlobalContexts(ctx context.Context, resolved *ResolvedContext) error
|
|
|
|
// EnableGlobalContext enables/disables a global context
|
|
EnableGlobalContext(ctx context.Context, contextID string, enabled bool) error
|
|
|
|
// SetGlobalContextPriority sets priority for global context application
|
|
SetGlobalContextPriority(ctx context.Context, contextID string, priority int) error
|
|
}
|
|
|
|
// CacheManager manages caching for context resolution performance
|
|
type CacheManager interface {
|
|
// Get retrieves a cached resolution
|
|
Get(ctx context.Context, key string) (*ResolvedContext, error)
|
|
|
|
// Set stores a resolution in cache with TTL
|
|
Set(ctx context.Context, key string, resolved *ResolvedContext, ttl time.Duration) error
|
|
|
|
// Delete removes a specific cache entry
|
|
Delete(ctx context.Context, key string) error
|
|
|
|
// DeletePattern removes cache entries matching a pattern
|
|
DeletePattern(ctx context.Context, pattern string) error
|
|
|
|
// Clear clears all cached entries
|
|
Clear(ctx context.Context) error
|
|
|
|
// GetStats returns cache performance statistics
|
|
GetStats() *CacheStats
|
|
}
|
|
|
|
// CacheStats represents cache performance statistics
|
|
type CacheStats struct {
|
|
HitRate float64 `json:"hit_rate"` // Cache hit rate (0-1)
|
|
MissRate float64 `json:"miss_rate"` // Cache miss rate (0-1)
|
|
TotalHits int64 `json:"total_hits"` // Total cache hits
|
|
TotalMisses int64 `json:"total_misses"` // Total cache misses
|
|
CurrentSize int64 `json:"current_size"` // Current cache size
|
|
MaxSize int64 `json:"max_size"` // Maximum cache size
|
|
Evictions int64 `json:"evictions"` // Number of cache evictions
|
|
LastEviction time.Time `json:"last_eviction"` // When last eviction occurred
|
|
}
|
|
|
|
// ContextMerger handles merging contexts during resolution
|
|
type ContextMerger interface {
|
|
// MergeContexts merges multiple contexts using inheritance rules
|
|
MergeContexts(contexts []*ContextNode, options *MergeOptions) (*ResolvedContext, error)
|
|
|
|
// MergeWithGlobal merges context with global contexts
|
|
MergeWithGlobal(base *ResolvedContext, globals []*ContextNode) (*ResolvedContext, error)
|
|
|
|
// CalculateSpecificity calculates context specificity for merge priority
|
|
CalculateSpecificity(ctx *ContextNode) int
|
|
|
|
// ValidateMergeResult validates merged context quality
|
|
ValidateMergeResult(resolved *ResolvedContext) (*ValidationResult, error)
|
|
}
|
|
|
|
// ContextValidator validates context data quality and consistency
|
|
type ContextValidator interface {
|
|
// ValidateNode validates a single context node
|
|
ValidateNode(ctx context.Context, node *ContextNode) (*ValidationResult, error)
|
|
|
|
// ValidateResolved validates a resolved context
|
|
ValidateResolved(ctx context.Context, resolved *ResolvedContext) (*ValidationResult, error)
|
|
|
|
// ValidateHierarchyConsistency validates hierarchy-wide consistency
|
|
ValidateHierarchyConsistency(ctx context.Context) ([]*ValidationIssue, error)
|
|
|
|
// SuggestImprovements suggests improvements for context quality
|
|
SuggestImprovements(ctx context.Context, node *ContextNode) ([]string, error)
|
|
}
|
|
|
|
// Helper functions and integration examples
|
|
|
|
// ValidateContextResolutionRequest validates a context resolution request
|
|
func ValidateContextResolutionRequest(address ucxl.Address, role string, maxDepth int) error {
|
|
if err := address.Validate(); err != nil {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidAddress,
|
|
"invalid UCXL address in resolution request").WithUnderlying(err).WithAddress(address)
|
|
}
|
|
|
|
if role == "" {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidRole,
|
|
"role cannot be empty in resolution request").WithAddress(address)
|
|
}
|
|
|
|
if maxDepth < 0 {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeDepthExceeded,
|
|
"maxDepth cannot be negative").WithAddress(address).
|
|
WithContext("max_depth", fmt.Sprintf("%d", maxDepth))
|
|
}
|
|
|
|
if maxDepth > 50 { // Reasonable upper bound to prevent resource exhaustion
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeDepthExceeded,
|
|
"maxDepth exceeds reasonable limits").WithAddress(address).
|
|
WithContext("max_depth", fmt.Sprintf("%d", maxDepth))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateBatchResolutionRequest validates a batch resolution request
|
|
func ValidateBatchResolutionRequest(request *BatchResolutionRequest) error {
|
|
if request == nil {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidContext,
|
|
"batch resolution request cannot be nil")
|
|
}
|
|
|
|
if len(request.Addresses) == 0 {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidContext,
|
|
"batch resolution request must contain at least one address")
|
|
}
|
|
|
|
if len(request.Addresses) > 100 { // Prevent excessive batch sizes
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidContext,
|
|
"batch resolution request exceeds maximum size").
|
|
WithContext("size", fmt.Sprintf("%d", len(request.Addresses)))
|
|
}
|
|
|
|
for i, address := range request.Addresses {
|
|
if err := address.Validate(); err != nil {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidAddress,
|
|
fmt.Sprintf("invalid address at index %d", i)).WithUnderlying(err).WithAddress(address)
|
|
}
|
|
}
|
|
|
|
if request.Role == "" {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidRole,
|
|
"role cannot be empty in batch resolution request")
|
|
}
|
|
|
|
if request.MaxDepth < 0 {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeDepthExceeded,
|
|
"maxDepth cannot be negative in batch resolution request").
|
|
WithContext("max_depth", fmt.Sprintf("%d", request.MaxDepth))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CalculateResolutionConfidence calculates overall confidence from multiple context nodes
|
|
func CalculateResolutionConfidence(contexts []*ContextNode) float64 {
|
|
if len(contexts) == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
totalConfidence := 0.0
|
|
totalWeight := 0.0
|
|
|
|
for _, ctx := range contexts {
|
|
// Weight by specificity - higher specificity contexts have more influence
|
|
weight := float64(ctx.ContextSpecificity + 1)
|
|
totalConfidence += ctx.RAGConfidence * weight
|
|
totalWeight += weight
|
|
}
|
|
|
|
if totalWeight == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
confidence := totalConfidence / totalWeight
|
|
|
|
// Apply diminishing returns for multiple contexts
|
|
if len(contexts) > 1 {
|
|
// Slight boost for having multiple confirming contexts, but not linear
|
|
multiplier := 1.0 + (float64(len(contexts)-1) * 0.1)
|
|
confidence = confidence * multiplier
|
|
if confidence > 1.0 {
|
|
confidence = 1.0
|
|
}
|
|
}
|
|
|
|
return confidence
|
|
}
|
|
|
|
// FilterContextsByRole filters context nodes based on role access
|
|
func FilterContextsByRole(contexts []*ContextNode, role string, authority config.AuthorityLevel) []*ContextNode {
|
|
filtered := make([]*ContextNode, 0, len(contexts))
|
|
|
|
for _, ctx := range contexts {
|
|
if ctx.CanAccess(role, authority) {
|
|
filtered = append(filtered, ctx)
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
// MergeStringSlices merges multiple string slices with deduplication
|
|
func MergeStringSlices(slices ...[]string) []string {
|
|
seen := make(map[string]bool)
|
|
var result []string
|
|
|
|
for _, slice := range slices {
|
|
for _, item := range slice {
|
|
if !seen[item] && item != "" {
|
|
seen[item] = true
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// BuildInheritanceChain builds the inheritance chain for a resolved context
|
|
func BuildInheritanceChain(contexts []*ContextNode) []string {
|
|
chain := make([]string, 0, len(contexts))
|
|
|
|
// Sort by specificity (most specific first)
|
|
for _, ctx := range contexts {
|
|
chain = append(chain, ctx.Path)
|
|
}
|
|
|
|
return chain
|
|
}
|
|
|
|
// GenerateCacheKey generates a cache key for resolution requests
|
|
func GenerateCacheKey(address ucxl.Address, role string, maxDepth int) string {
|
|
return fmt.Sprintf("resolve:%s:%s:%d", address.String(), role, maxDepth)
|
|
}
|
|
|
|
// IsContextStale determines if a context node is stale and needs refresh
|
|
func IsContextStale(ctx *ContextNode, staleTTL time.Duration) bool {
|
|
return time.Since(ctx.GeneratedAt) > staleTTL
|
|
}
|
|
|
|
/*
|
|
Integration Examples:
|
|
|
|
1. DHT Integration Example:
|
|
|
|
// Store context in DHT with role-based encryption
|
|
func (resolver *DefaultContextResolver) storeContextInDHT(ctx *ContextNode, roles []string) error {
|
|
for _, role := range roles {
|
|
// Encrypt context for role
|
|
encrypted, err := resolver.crypto.EncryptForRole(ctx, role)
|
|
if err != nil {
|
|
return NewContextError(ErrorTypeEncryption, ErrorCodeEncryptionFailed,
|
|
"failed to encrypt context for role").WithAddress(ctx.UCXLAddress).
|
|
WithContext("role", role).WithUnderlying(err)
|
|
}
|
|
|
|
// Store in DHT
|
|
key := fmt.Sprintf("context:%s:%s", ctx.UCXLAddress.String(), role)
|
|
if err := resolver.dht.Put(key, encrypted); err != nil {
|
|
return NewContextError(ErrorTypeDHT, ErrorCodeDHTError,
|
|
"failed to store context in DHT").WithAddress(ctx.UCXLAddress).
|
|
WithContext("role", role).WithUnderlying(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
2. Leader Election Integration Example:
|
|
|
|
// Context generation only happens on leader node
|
|
func (manager *ContextManager) GenerateContextIfLeader(filePath string, role string) error {
|
|
if !manager.IsLeader() {
|
|
return NewContextError(ErrorTypeAccess, ErrorCodeAccessDenied,
|
|
"context generation is only allowed on leader nodes").
|
|
WithContext("current_role", "follower")
|
|
}
|
|
|
|
// Parse UCXL address from file path
|
|
address, err := manager.pathResolver.PathToUCXL(filePath)
|
|
if err != nil {
|
|
return NewContextError(ErrorTypeValidation, ErrorCodeInvalidAddress,
|
|
"failed to resolve file path to UCXL address").WithUnderlying(err).
|
|
WithContext("file_path", filePath)
|
|
}
|
|
|
|
// Generate context using intelligence engine
|
|
ctx, err := manager.intelligence.AnalyzeFile(context.Background(), filePath, role)
|
|
if err != nil {
|
|
return NewContextError(ErrorTypeIntelligence, ErrorCodeInternalError,
|
|
"failed to generate context").WithAddress(*address).WithUnderlying(err)
|
|
}
|
|
|
|
// Store in hierarchy manager
|
|
if err := manager.hierarchyManager.AddNode(context.Background(), ctx); err != nil {
|
|
return NewContextError(ErrorTypeHierarchy, ErrorCodeStorageError,
|
|
"failed to add context to hierarchy").WithAddress(ctx.UCXLAddress).
|
|
WithUnderlying(err)
|
|
}
|
|
|
|
// Distribute via DHT for role-based access
|
|
roles := manager.getRolesForContext(ctx)
|
|
return manager.distributor.DistributeContext(ctx, roles)
|
|
}
|
|
|
|
3. Crypto Integration Example:
|
|
|
|
// Decrypt context based on role authority
|
|
func (resolver *DefaultContextResolver) decryptContextForRole(encrypted []byte, role string) (*ContextNode, error) {
|
|
// Check if current agent can decrypt this role's content
|
|
canDecrypt, err := resolver.config.CanDecryptRole(role)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeAccess, ErrorCodeInvalidRole,
|
|
"failed to check decryption permissions").WithContext("role", role).
|
|
WithUnderlying(err)
|
|
}
|
|
|
|
if !canDecrypt {
|
|
return nil, NewContextError(ErrorTypeAccess, ErrorCodeAccessDenied,
|
|
"insufficient authority to decrypt context").WithContext("role", role).
|
|
WithContext("current_role", resolver.config.Agent.Role)
|
|
}
|
|
|
|
// Decrypt using role's private key
|
|
decrypted, err := resolver.crypto.DecryptWithRole(encrypted)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeEncryption, ErrorCodeDecryptionFailed,
|
|
"failed to decrypt context").WithContext("role", role).WithUnderlying(err)
|
|
}
|
|
|
|
// Deserialize context
|
|
var ctx ContextNode
|
|
if err := json.Unmarshal(decrypted, &ctx); err != nil {
|
|
return nil, NewContextError(ErrorTypeValidation, ErrorCodeInvalidContext,
|
|
"failed to deserialize decrypted context").WithUnderlying(err)
|
|
}
|
|
|
|
return &ctx, nil
|
|
}
|
|
|
|
4. Complete Resolution Flow Example:
|
|
|
|
// Resolve context with full CHORUS integration
|
|
func (resolver *DefaultContextResolver) ResolveWithIntegration(ctx context.Context, address ucxl.Address, role string, maxDepth int) (*ResolvedContext, error) {
|
|
// 1. Validate request
|
|
if err := ValidateContextResolutionRequest(address, role, maxDepth); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 2. Check cache first
|
|
cacheKey := GenerateCacheKey(address, role, maxDepth)
|
|
if cached, err := resolver.cache.Get(ctx, cacheKey); err == nil {
|
|
resolver.stats.CacheHits++
|
|
return cached, nil
|
|
}
|
|
resolver.stats.CacheMisses++
|
|
|
|
// 3. Try local hierarchy first
|
|
localContexts, err := resolver.hierarchyManager.TraverseUp(ctx, address.Path, maxDepth)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeHierarchy, ErrorCodeStorageError,
|
|
"failed to traverse local hierarchy").WithAddress(address).WithUnderlying(err)
|
|
}
|
|
|
|
// 4. If no local contexts, try DHT
|
|
var dhtContexts []*ContextNode
|
|
if len(localContexts) == 0 {
|
|
dhtContext, err := resolver.fetchContextFromDHT(address, role)
|
|
if err == nil {
|
|
dhtContexts = []*ContextNode{dhtContext}
|
|
}
|
|
}
|
|
|
|
// 5. Combine local and DHT contexts
|
|
allContexts := append(localContexts, dhtContexts...)
|
|
if len(allContexts) == 0 {
|
|
return nil, NewContextError(ErrorTypeResolution, ErrorCodeNotFound,
|
|
"no context found for address").WithAddress(address)
|
|
}
|
|
|
|
// 6. Filter by role access
|
|
authority, err := resolver.config.GetRoleAuthority(role)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeAccess, ErrorCodeInvalidRole,
|
|
"failed to get role authority").WithContext("role", role).WithUnderlying(err)
|
|
}
|
|
|
|
accessibleContexts := FilterContextsByRole(allContexts, role, authority)
|
|
if len(accessibleContexts) == 0 {
|
|
return nil, NewContextError(ErrorTypeAccess, ErrorCodeAccessDenied,
|
|
"no accessible contexts for role").WithAddress(address).WithContext("role", role)
|
|
}
|
|
|
|
// 7. Merge contexts using inheritance rules
|
|
resolved, err := resolver.merger.MergeContexts(accessibleContexts, resolver.mergeOptions)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeResolution, ErrorCodeInternalError,
|
|
"failed to merge contexts").WithAddress(address).WithUnderlying(err)
|
|
}
|
|
|
|
// 8. Apply global contexts if enabled
|
|
if resolver.globalContextsEnabled {
|
|
globalContexts, err := resolver.globalManager.ListGlobalContexts(ctx)
|
|
if err == nil && len(globalContexts) > 0 {
|
|
resolved, err = resolver.merger.MergeWithGlobal(resolved, globalContexts)
|
|
if err != nil {
|
|
return nil, NewContextError(ErrorTypeResolution, ErrorCodeInternalError,
|
|
"failed to apply global contexts").WithAddress(address).WithUnderlying(err)
|
|
}
|
|
resolved.GlobalContextsApplied = true
|
|
}
|
|
}
|
|
|
|
// 9. Validate resolved context
|
|
if err := resolved.Validate(); err != nil {
|
|
return nil, NewContextError(ErrorTypeValidation, ErrorCodeInvalidContext,
|
|
"resolved context failed validation").WithAddress(address).WithUnderlying(err)
|
|
}
|
|
|
|
// 10. Cache the result
|
|
if err := resolver.cache.Set(ctx, cacheKey, resolved, resolver.cacheTTL); err != nil {
|
|
// Log but don't fail the request
|
|
resolver.logger.Warn("failed to cache resolved context", "error", err)
|
|
}
|
|
|
|
// 11. Update statistics
|
|
resolver.stats.TotalResolutions++
|
|
|
|
return resolved, nil
|
|
}
|
|
*/ |