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>
249 lines
6.5 KiB
Go
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,
|
|
}
|
|
}
|