stemedb/sdk/go/adk/adk_test.go
jordan c59066949a feat: Add quickstart "Beyond Hello World" sections with Skeptic and Layered endpoints
- Add Layered() method to Go SDK for per-source-class consensus queries
- Add LayeredQueryParams, LayeredResult, TierResolution types to Go SDK
- Create conflict example demonstrating Skeptic and Layered endpoints
- Update quickstart.md with sections 6 (conflict detection) and 7 (authority tiers)
- Remove tracked Go binary and add data/ to .gitignore

The new quickstart sections demonstrate Episteme's differentiating features:
- Skeptic endpoint shows "Trust but Verify" conflict analysis
- Layered endpoint shows per-tier resolution (Clinical vs Anecdotal)

Note: Pre-existing large files flagged by pre-commit hook (technical debt from prior sessions)

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

619 lines
16 KiB
Go

package adk
import (
"context"
"encoding/json"
"testing"
"github.com/orchard9/stemedb-go/steme"
)
// mockClient implements EpistemeClient for testing
type mockClient struct {
queryFunc func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error)
assertFunc func(ctx context.Context, assertion steme.Assertion) (string, error)
traceFunc func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error)
supersedeFunc func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error)
}
func (m *mockClient) Query(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
if m.queryFunc != nil {
return m.queryFunc(ctx, params)
}
return &steme.QueryResult{}, nil
}
func (m *mockClient) Assert(ctx context.Context, assertion steme.Assertion) (string, error) {
if m.assertFunc != nil {
return m.assertFunc(ctx, assertion)
}
return "mock_hash", nil
}
func (m *mockClient) Trace(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) {
if m.traceFunc != nil {
return m.traceFunc(ctx, params)
}
return &steme.TraceResult{}, nil
}
func (m *mockClient) Supersede(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) {
if m.supersedeFunc != nil {
return m.supersedeFunc(ctx, params)
}
return &steme.SupersedeResult{
Status: "superseded",
TargetHash: params.TargetHash,
SupersessionType: params.SupersessionType,
Timestamp: 1704067200,
}, nil
}
func TestQueryTool(t *testing.T) {
// Mock client that returns a test assertion
client := &mockClient{
queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
lifecycle := steme.LifecycleApproved
return &steme.QueryResult{
Assertions: []steme.AssertionResponse{
{
Hash: "test_hash",
Subject: "Tesla_Inc",
Predicate: "has_revenue",
Object: steme.NewNumberValue(96.7),
Confidence: 0.95,
SourceHash: "source_hash",
Lifecycle: lifecycle,
},
},
TotalCount: 1,
HasMore: false,
}, nil
},
}
tool := NewQueryTool(client)
// Test basic query
input := QueryInput{
Subject: "Tesla_Inc",
Predicate: "has_revenue",
Lens: "consensus",
}
inputBytes, err := json.Marshal(input)
if err != nil {
t.Fatalf("failed to marshal input: %v", err)
}
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("query failed: %v", err)
}
var output QueryOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Verify output
if output.Confidence != 0.95 {
t.Errorf("expected confidence 0.95, got %f", output.Confidence)
}
if output.Lifecycle != "Approved" {
t.Errorf("expected lifecycle Approved, got %s", output.Lifecycle)
}
if output.QueryID == "" {
t.Error("expected non-empty query ID")
}
if len(output.Sources) == 0 {
t.Error("expected at least one source")
}
}
func TestQueryToolConfidenceThreshold(t *testing.T) {
// Mock client that returns low confidence
client := &mockClient{
queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
lifecycle := steme.LifecycleApproved
return &steme.QueryResult{
Assertions: []steme.AssertionResponse{
{
Hash: "test_hash",
Subject: "test",
Predicate: "test",
Object: steme.NewTextValue("test"),
Confidence: 0.6, // Low confidence
SourceHash: "source_hash",
Lifecycle: lifecycle,
},
},
TotalCount: 1,
}, nil
},
}
tool := NewQueryTool(client)
input := QueryInput{
Subject: "test",
Predicate: "test",
MinConfidence: 0.8, // Require high confidence
}
inputBytes, _ := json.Marshal(input)
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("query failed: %v", err)
}
var output QueryOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Should have error about low confidence
if output.Error == "" {
t.Error("expected error for low confidence")
}
}
func TestAssertTool(t *testing.T) {
// Track what assertion was created
var capturedAssertion steme.Assertion
client := &mockClient{
assertFunc: func(ctx context.Context, assertion steme.Assertion) (string, error) {
capturedAssertion = assertion
return "created_hash", nil
},
}
tool := NewAssertTool(client)
input := AssertInput{
Subject: "Tesla_Inc",
Predicate: "has_revenue",
Object: 96.7,
SourceHash: "0000000000000000000000000000000000000000000000000000000000000000",
Confidence: 0.95,
Lifecycle: "approved",
}
inputBytes, _ := json.Marshal(input)
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("assert failed: %v", err)
}
var output AssertOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Verify output
if !output.Success {
t.Errorf("expected success, got error: %s", output.Error)
}
if output.Hash != "created_hash" {
t.Errorf("expected hash created_hash, got %s", output.Hash)
}
// Verify captured assertion
if capturedAssertion.Subject != "Tesla_Inc" {
t.Errorf("expected subject Tesla_Inc, got %s", capturedAssertion.Subject)
}
// Use approximate comparison for floating point
if capturedAssertion.Confidence < 0.94 || capturedAssertion.Confidence > 0.96 {
t.Errorf("expected confidence ~0.95, got %f", capturedAssertion.Confidence)
}
}
func TestConstraintCheckTool(t *testing.T) {
// Mock client that returns constraint assertions
client := &mockClient{
queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
lifecycle := steme.LifecycleApproved
return &steme.QueryResult{
Assertions: []steme.AssertionResponse{
{
Hash: "constraint1",
Subject: "auth_jwt",
Predicate: "must_use",
Object: steme.NewTextValue("Ed25519"),
Confidence: 1.0,
SourceHash: "source",
Lifecycle: lifecycle,
},
{
Hash: "constraint2",
Subject: "auth_jwt",
Predicate: "forbidden",
Object: steme.NewTextValue("MD5"),
Confidence: 1.0,
SourceHash: "source",
Lifecycle: lifecycle,
},
},
TotalCount: 2,
}, nil
},
}
tool := NewConstraintCheckTool(client)
input := ConstraintCheckInput{
Context: "auth_jwt",
}
inputBytes, _ := json.Marshal(input)
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("constraint check failed: %v", err)
}
var output ConstraintCheckOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Verify constraints were returned
if len(output.Constraints) != 2 {
t.Errorf("expected 2 constraints, got %d", len(output.Constraints))
}
// Check for must-use constraint
foundMustUse := false
for _, c := range output.Constraints {
if c.MustUse == "Ed25519" {
foundMustUse = true
}
}
if !foundMustUse {
t.Error("expected to find Ed25519 must-use constraint")
}
// Check for forbidden constraint
foundForbidden := false
for _, c := range output.Constraints {
if c.Forbidden == "MD5" {
foundForbidden = true
}
}
if !foundForbidden {
t.Error("expected to find MD5 forbidden constraint")
}
}
func TestTraceTool(t *testing.T) {
// Mock client that returns audit records
agentID := "deadbeef00000000000000000000000000000000000000000000000000000000"
subject := "Tesla_Inc"
predicate := "has_revenue"
resultHash := "result_hash_123"
lens := steme.LensConsensus
client := &mockClient{
traceFunc: func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) {
return &steme.TraceResult{
Audits: []steme.QueryAuditRecord{
{
QueryID: "query_123",
AgentID: &agentID,
Timestamp: 1704067200,
Params: steme.QueryParamsAudit{
Subject: &subject,
Predicate: &predicate,
Lens: &lens,
},
ResultHash: &resultHash,
ResultConfidence: 0.95,
ContributingAssertions: []steme.ContributingAssertion{
{
AssertionHash: "assertion_1",
Weight: 1.0,
SourceHash: "source_1",
Lifecycle: steme.LifecycleApproved,
},
{
AssertionHash: "assertion_2",
Weight: 0.5,
SourceHash: "source_2",
Lifecycle: steme.LifecycleProposed,
},
},
},
},
TotalCount: 1,
AgentID: agentID,
FromTimestamp: 1704067200,
ToTimestamp: 1704153600,
}, nil
},
}
tool := NewTraceTool(client)
input := TraceInput{
AgentID: agentID,
From: "1704067200",
To: "1704153600",
Subject: "Tesla*",
}
inputBytes, _ := json.Marshal(input)
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("trace failed: %v", err)
}
var output TraceOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Verify output
if len(output.Queries) != 1 {
t.Errorf("expected 1 query, got %d", len(output.Queries))
}
if len(output.Queries) > 0 {
query := output.Queries[0]
if query.QueryID != "query_123" {
t.Errorf("expected query_id query_123, got %s", query.QueryID)
}
if query.Subject != "Tesla_Inc" {
t.Errorf("expected subject Tesla_Inc, got %s", query.Subject)
}
if query.Predicate != "has_revenue" {
t.Errorf("expected predicate has_revenue, got %s", query.Predicate)
}
if query.Lens != "consensus" {
t.Errorf("expected lens consensus, got %s", query.Lens)
}
if query.Confidence != 0.95 {
t.Errorf("expected confidence 0.95, got %f", query.Confidence)
}
if len(query.Contributing) != 2 {
t.Errorf("expected 2 contributing assertions, got %d", len(query.Contributing))
}
}
}
func TestSupersedeTool(t *testing.T) {
// Track what supersession was created
var capturedParams steme.SupersedeParams
client := &mockClient{
supersedeFunc: func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) {
capturedParams = params
return &steme.SupersedeResult{
Status: "superseded",
TargetHash: params.TargetHash,
SupersessionType: params.SupersessionType,
Timestamp: 1704067200,
}, nil
},
}
tool := NewSupersedeTool(client)
input := SupersedeInput{
Hash: "abc123def456",
Type: "Invalidate",
Reason: "Proposal treated as approved. See incident INC-2024-001",
NewHash: "def456abc123",
AgentID: "deadbeef00000000000000000000000000000000000000000000000000000000",
Signature: "0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000",
}
inputBytes, err := json.Marshal(input)
if err != nil {
t.Fatalf("failed to marshal input: %v", err)
}
outputBytes, err := tool.Execute(context.Background(), inputBytes)
if err != nil {
t.Fatalf("supersede failed: %v", err)
}
var output SupersedeOutput
if err := json.Unmarshal(outputBytes, &output); err != nil {
t.Fatalf("failed to unmarshal output: %v", err)
}
// Verify output
if !output.Success {
t.Errorf("expected success, got error: %s", output.Error)
}
if output.TargetHash != "abc123def456" {
t.Errorf("expected target hash abc123def456, got %s", output.TargetHash)
}
if output.SupersessionType != "Invalidate" {
t.Errorf("expected supersession type Invalidate, got %s", output.SupersessionType)
}
// Verify captured params
if capturedParams.TargetHash != "abc123def456" {
t.Errorf("expected target hash abc123def456, got %s", capturedParams.TargetHash)
}
if capturedParams.SupersessionType != steme.SupersessionInvalidate {
t.Errorf("expected supersession type Invalidate, got %s", capturedParams.SupersessionType)
}
if capturedParams.Reason != "Proposal treated as approved. See incident INC-2024-001" {
t.Errorf("expected reason about proposal, got %s", capturedParams.Reason)
}
}
func TestParseSupersessionType(t *testing.T) {
tests := []struct {
input string
expected steme.SupersessionType
}{
{"Invalidate", steme.SupersessionInvalidate},
{"invalidate", steme.SupersessionInvalidate},
{"Temporal", steme.SupersessionTemporal},
{"temporal", steme.SupersessionTemporal},
{"Refinement", steme.SupersessionRefinement},
{"RequiresReview", steme.SupersessionRequiresReview},
{"requires_review", steme.SupersessionRequiresReview},
{"Additive", steme.SupersessionAdditive},
{"additive", steme.SupersessionAdditive},
{"unknown", steme.SupersessionInvalidate}, // Default
}
for _, tt := range tests {
result := parseSupersessionType(tt.input)
if result != tt.expected {
t.Errorf("parseSupersessionType(%s) = %v, expected %v", tt.input, result, tt.expected)
}
}
}
func TestToolNames(t *testing.T) {
client := &mockClient{}
tests := []struct {
tool Tool
expectedName string
}{
{NewQueryTool(client), "episteme_query"},
{NewAssertTool(client), "episteme_assert"},
{NewConstraintCheckTool(client), "episteme_constraint_check"},
{NewTraceTool(client), "episteme_trace"},
{NewSupersedeTool(client), "episteme_supersede"},
}
for _, tt := range tests {
if tt.tool.Name() != tt.expectedName {
t.Errorf("expected tool name %s, got %s", tt.expectedName, tt.tool.Name())
}
}
}
func TestAllTools(t *testing.T) {
client := &mockClient{}
tools := AllTools(client)
if len(tools) != 5 {
t.Errorf("expected 5 tools, got %d", len(tools))
}
// Verify all expected tools are present
expectedNames := map[string]bool{
"episteme_query": false,
"episteme_assert": false,
"episteme_constraint_check": false,
"episteme_trace": false,
"episteme_supersede": false,
}
for _, tool := range tools {
expectedNames[tool.Name()] = true
}
for name, found := range expectedNames {
if !found {
t.Errorf("expected to find tool %s", name)
}
}
}
func TestConfigCreation(t *testing.T) {
client := &mockClient{}
logFunc := func(format string, args ...any) {}
setState := func(key string, value any) {}
configs := AllConfigs(client, 0.8, setState, logFunc)
expectedAgents := []string{
"implementation",
"lead",
"research",
"supervisor",
"oncall",
}
for _, agentType := range expectedAgents {
config, ok := configs[agentType]
if !ok {
t.Errorf("expected config for %s", agentType)
continue
}
if config.Name == "" {
t.Errorf("config for %s has empty name", agentType)
}
if config.Description == "" {
t.Errorf("config for %s has empty description", agentType)
}
if config.Instruction == "" {
t.Errorf("config for %s has empty instruction", agentType)
}
if len(config.Tools) == 0 {
t.Errorf("config for %s has no tools", agentType)
}
}
}
func TestParseLifecycle(t *testing.T) {
tests := []struct {
input string
expected steme.LifecycleStage
}{
{"proposed", steme.LifecycleProposed},
{"under_review", steme.LifecycleUnderReview},
{"approved", steme.LifecycleApproved},
{"deprecated", steme.LifecycleDeprecated},
{"rejected", steme.LifecycleRejected},
{"invalid", steme.LifecycleProposed}, // Default
}
for _, tt := range tests {
result := parseLifecycle(tt.input)
if result != tt.expected {
t.Errorf("parseLifecycle(%s) = %v, expected %v", tt.input, result, tt.expected)
}
}
}
func TestParseLens(t *testing.T) {
tests := []struct {
input string
expected steme.Lens
}{
{"recency", steme.LensRecency},
{"consensus", steme.LensConsensus},
{"authority", steme.LensAuthority},
{"vote_aware_consensus", steme.LensVoteAwareConsensus},
{"trust_aware_authority", steme.LensTrustAwareAuthority},
{"invalid", steme.LensConsensus}, // Default
}
for _, tt := range tests {
result := parseLen(tt.input)
if result != tt.expected {
t.Errorf("parseLen(%s) = %v, expected %v", tt.input, result, tt.expected)
}
}
}