stemedb/sdk/go/adk/tools_helpers.go
jordan b3e8a9a058 feat: Multi-application expansion with chaos testing and community UI
Major additions:
- Community Next.js app (port 18187) for browsing claims with API docs
- stemedb-chaos crate: Fault injection, chaos testing, CRDT properties
- Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents
- Disputed claims handling: Manual review workflows and validation
- Aphoria security scanner: New extractors (SQL injection, command
  injection, weak crypto, TLS version), policy-based ignores, UAT reports
- Docker infrastructure: Dockerfile, docker-compose.yml for full stack
- VulnBank demo: Intentionally vulnerable multi-language test corpus

SDK & API enhancements:
- Source registry handlers for tracking data provenance
- Metrics endpoint
- Skeptic filtering improvements

Code quality:
- Split 14 large files (>500 lines) into focused modules
- All files now under 500-line limit per project guidelines

Documentation:
- Chaos testing guide, circuit breakers, observability docs
- Phase 7 UAT documentation updates
- Martin Kleppmann technical writer agent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 01:24:14 -07:00

249 lines
6.5 KiB
Go

package adk
import (
"fmt"
"time"
"github.com/orchard9/stemedb-go/steme"
)
// parseLen converts a string lens name to steme.Lens.
func parseLen(lens string) steme.Lens {
switch lens {
case "recency":
return steme.LensRecency
case "consensus":
return steme.LensConsensus
case "authority":
return steme.LensAuthority
case "vote_aware_consensus":
return steme.LensVoteAwareConsensus
case "trust_aware_authority":
return steme.LensTrustAwareAuthority
default:
return steme.LensConsensus // Default to consensus
}
}
// parseLifecycle converts a string lifecycle stage to steme.LifecycleStage.
func parseLifecycle(lifecycle string) steme.LifecycleStage {
switch lifecycle {
case "proposed":
return steme.LifecycleProposed
case "under_review":
return steme.LifecycleUnderReview
case "approved":
return steme.LifecycleApproved
case "deprecated":
return steme.LifecycleDeprecated
case "rejected":
return steme.LifecycleRejected
default:
return steme.LifecycleProposed // Default to proposed
}
}
// parseSupersessionType converts a string supersession type to steme.SupersessionType.
func parseSupersessionType(t string) steme.SupersessionType {
switch t {
case "Invalidate", "invalidate":
return steme.SupersessionInvalidate
case "Temporal", "temporal":
return steme.SupersessionTemporal
case "Refinement", "refinement":
return steme.SupersessionRefinement
case "RequiresReview", "requires_review":
return steme.SupersessionRequiresReview
case "Additive", "additive":
return steme.SupersessionAdditive
default:
return steme.SupersessionInvalidate // Default to invalidate
}
}
// setObjectValue sets the object value on an assertion builder based on the input type.
func setObjectValue(builder *steme.AssertionBuilder, obj any) error {
switch v := obj.(type) {
case string:
builder.WithText(v)
case float64:
builder.WithNumber(v)
case bool:
builder.WithBoolean(v)
case map[string]any:
// Handle ObjectValue from JSON
if typeStr, ok := v["type"].(string); ok {
if value, ok := v["value"]; ok {
switch typeStr {
case "Text":
if str, ok := value.(string); ok {
builder.WithText(str)
}
case "Number":
if num, ok := value.(float64); ok {
builder.WithNumber(num)
}
case "Boolean":
if b, ok := value.(bool); ok {
builder.WithBoolean(b)
}
case "Reference":
if ref, ok := value.(string); ok {
builder.WithReference(ref)
}
}
}
}
default:
return fmt.Errorf("unsupported object type: %T", obj)
}
return nil
}
// convertQueryResult converts a steme.QueryResult to a QueryOutput.
func convertQueryResult(result *steme.QueryResult, minConfidence float32) QueryOutput {
if len(result.Assertions) == 0 {
return QueryOutput{
Error: "no results found",
}
}
// Take the first result (lens already applied resolution)
assertion := result.Assertions[0]
// Check confidence threshold
confidence := float32(assertion.Confidence)
if minConfidence > 0 && confidence < minConfidence {
return QueryOutput{
Value: assertion.Object.Value,
Confidence: confidence,
Lifecycle: string(assertion.Lifecycle),
QueryID: generateQueryID(),
Error: fmt.Sprintf("confidence %.2f below threshold %.2f", confidence, minConfidence),
}
}
// Convert sources
sources := make([]Source, 0, len(result.Assertions))
for _, a := range result.Assertions {
sources = append(sources, Source{
Hash: a.Hash,
SourceHash: a.SourceHash,
Weight: float32(a.Confidence),
})
}
return QueryOutput{
Value: assertion.Object.Value,
Confidence: confidence,
Lifecycle: string(assertion.Lifecycle),
Sources: sources,
QueryID: generateQueryID(),
}
}
// convertToConstraints converts query results to constraint objects.
func convertToConstraints(result *steme.QueryResult) []Constraint {
constraints := make([]Constraint, 0)
for _, assertion := range result.Assertions {
constraint := Constraint{
Subject: assertion.Subject,
}
// Determine if this is a must-use or forbidden constraint
// based on predicate
switch assertion.Predicate {
case "must_use":
if text, ok := assertion.Object.Value.(string); ok {
constraint.MustUse = text
}
case "forbidden":
if text, ok := assertion.Object.Value.(string); ok {
constraint.Forbidden = text
}
}
// Extract reason from parent assertion if available
// (This is a simplified implementation)
constraint.Reason = "Approved organizational pattern"
constraints = append(constraints, constraint)
}
return constraints
}
// generateQueryID generates a unique query ID for audit trails.
func generateQueryID() string {
// Generate a unique query ID for audit trail
// In production, this would be generated server-side
return fmt.Sprintf("query_%d", time.Now().UnixNano())
}
// convertTraceResult converts a steme.TraceResult to a TraceOutput.
func convertTraceResult(result *steme.TraceResult) TraceOutput {
queries := make([]QueryTrace, 0, len(result.Audits))
for _, audit := range result.Audits {
// Extract subject and predicate from params
subject := ""
if audit.Params.Subject != nil {
subject = *audit.Params.Subject
}
predicate := ""
if audit.Params.Predicate != nil {
predicate = *audit.Params.Predicate
}
// Extract lens (convert to lowercase for consistency)
lens := "consensus" // Default
if audit.Params.Lens != nil {
lensVal := string(*audit.Params.Lens)
// Convert "Consensus" -> "consensus", etc.
switch lensVal {
case "Recency":
lens = "recency"
case "Consensus":
lens = "consensus"
case "Authority":
lens = "authority"
case "VoteAwareConsensus":
lens = "vote_aware_consensus"
case "TrustAwareAuthority":
lens = "trust_aware_authority"
default:
lens = "consensus"
}
}
// Extract result value (simplified - just use hash if available)
resultValue := ""
if audit.ResultHash != nil {
resultValue = *audit.ResultHash
}
// Extract contributing assertion hashes
contributing := make([]string, 0, len(audit.ContributingAssertions))
for _, ca := range audit.ContributingAssertions {
contributing = append(contributing, ca.AssertionHash)
}
queries = append(queries, QueryTrace{
QueryID: audit.QueryID,
Timestamp: fmt.Sprintf("%d", audit.Timestamp),
Subject: subject,
Predicate: predicate,
Lens: lens,
Result: resultValue,
Confidence: audit.ResultConfidence,
Contributing: contributing,
})
}
return TraceOutput{
Queries: queries,
}
}