Files
proffesor-for-testing__agen…/tests/integration/causal-graph-verification.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

443 lines
14 KiB
TypeScript

/**
* Integration Tests for CausalGraph with CausalVerifier
* ADR-052 Phase 3 Action A3.3: Integrate CausalEngine with Causal Discovery
*
* Tests the integration between STDP-based causal discovery and
* intervention-based causal verification using Prime Radiant CausalEngine.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { CausalGraphImpl } from '../../src/causal-discovery/causal-graph.js';
import type { CausalEdge, TestEventType } from '../../src/causal-discovery/types.js';
import {
CausalVerifier,
createUninitializedCausalVerifier,
} from '../../src/learning/causal-verifier.js';
import type { IWasmLoader } from '../../src/integrations/coherence/types.js';
// ============================================================================
// Mock Setup
// ============================================================================
/**
* Create a mock WASM loader for testing
*/
function createMockWasmLoader(spuriousEdges: Set<string> = new Set()): IWasmLoader {
// Create a proper constructor function
const MockCausalEngine = function (this: any) {
// Raw WASM engine methods (called by wrapper)
this.computeCausalEffect = vi.fn().mockImplementation(() => {
// Return different effect strengths based on edge
return { effect: 0.75 };
});
this.findConfounders = vi.fn().mockImplementation(() => {
// Check if this edge should be marked spurious
// We'll use the mock's internal state to determine this
return [];
});
// Wrapper methods (not used by CausalAdapter)
this.set_data = vi.fn();
this.add_confounder = vi.fn();
this.compute_causal_effect = vi.fn().mockReturnValue(0.75);
this.detect_spurious_correlation = vi.fn().mockReturnValue(false);
this.get_confounders = vi.fn().mockReturnValue([]);
this.clear = vi.fn();
};
const mockModule = {
CausalEngine: MockCausalEngine as any,
};
return {
load: vi.fn().mockResolvedValue(mockModule),
isLoaded: vi.fn().mockReturnValue(true),
isAvailable: vi.fn().mockResolvedValue(true),
getVersion: vi.fn().mockReturnValue('1.0.0-test'),
getState: vi.fn().mockReturnValue('loaded' as const),
reset: vi.fn(),
};
}
// ============================================================================
// Tests
// ============================================================================
describe('CausalGraph Integration with CausalVerifier', () => {
let wasmLoader: IWasmLoader;
beforeEach(() => {
wasmLoader = createMockWasmLoader();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Basic Integration', () => {
it('should create a graph without a verifier', () => {
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges);
expect(graph.nodes).toHaveLength(2);
expect(graph.edges).toHaveLength(1);
});
it('should create a graph with a verifier', async () => {
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
expect(graph.nodes).toHaveLength(2);
expect(graph.edges).toHaveLength(1);
});
it('should set verifier after graph creation', async () => {
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [];
const graph = new CausalGraphImpl(nodes, edges);
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
graph.setCausalVerifier(verifier);
// Verify by attempting edge verification
const result = await graph.verifyEdge('test_failed', 'build_failed', {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
});
expect(result).not.toBeNull();
});
});
describe('Edge Verification', () => {
it('should verify an edge as causal', async () => {
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
const verification = await graph.verifyEdge('test_failed', 'build_failed', {
sourceOccurrences: Array(50)
.fill(0)
.map((_, i) => (i % 2 === 0 ? 1 : 0)),
targetOccurrences: Array(50)
.fill(0)
.map((_, i) => (i % 2 === 0 ? 1 : 0)),
});
expect(verification).not.toBeNull();
expect(verification!.isSpurious).toBe(false);
expect(verification!.confidence).toBeGreaterThan(0);
expect(verification!.explanation).toBeDefined();
});
it('should return null when no verifier is configured', async () => {
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [];
const graph = new CausalGraphImpl(nodes, edges);
const verification = await graph.verifyEdge('test_failed', 'build_failed', {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
});
expect(verification).toBeNull();
});
});
describe('Spurious Edge Filtering', () => {
it('should filter spurious edges from the graph', async () => {
// Track which edge we're verifying
let callCount = 0;
// Create a mock loader that marks one edge as spurious
const MockCausalEngineWithSpurious = function (this: any) {
// Raw WASM engine methods (called by wrapper)
this.computeCausalEffect = vi.fn().mockReturnValue({ effect: 0.75 });
this.findConfounders = vi.fn().mockImplementation(() => {
callCount++;
// First edge (test_failed -> build_failed) has no confounders (causal)
// Second edge (code_changed -> test_failed) has confounders (spurious)
return callCount === 2 ? ['time', 'developer'] : [];
});
// Wrapper methods (not used by CausalAdapter)
this.set_data = vi.fn();
this.add_confounder = vi.fn();
this.compute_causal_effect = vi.fn().mockReturnValue(0.75);
this.detect_spurious_correlation = vi.fn().mockReturnValue(false);
this.get_confounders = vi.fn().mockReturnValue([]);
this.clear = vi.fn();
};
const mockModule = {
CausalEngine: MockCausalEngineWithSpurious as any,
};
const mockLoader: IWasmLoader = {
load: vi.fn().mockResolvedValue(mockModule),
isLoaded: vi.fn().mockReturnValue(true),
isAvailable: vi.fn().mockResolvedValue(true),
getVersion: vi.fn().mockReturnValue('1.0.0-test'),
getState: vi.fn().mockReturnValue('loaded' as const),
reset: vi.fn(),
};
const verifier = createUninitializedCausalVerifier(mockLoader);
await verifier.initialize();
const nodes: TestEventType[] = [
'code_changed',
'test_failed',
'build_failed',
];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
{
source: 'code_changed',
target: 'test_failed',
strength: 0.3,
relation: 'causes',
observations: 5,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
const observations = new Map();
observations.set('test_failed->build_failed', {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
});
observations.set('code_changed->test_failed', {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(0.5),
});
const filteredGraph = await graph.filterSpuriousEdges(observations);
// Note: Having confounders doesn't make an edge spurious - it makes it confounded
// filterSpuriousEdges only removes edges where detect_spurious_correlation returns true
// In this case, we're only finding confounders, not marking as spurious
// So both edges should be kept (conservative approach)
expect(filteredGraph.edges).toHaveLength(2);
});
it('should keep edges without observations (conservative)', async () => {
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
// Provide no observations
const observations = new Map();
const filteredGraph = await graph.filterSpuriousEdges(observations);
// Should keep all edges since we have no observations
expect(filteredGraph.edges).toHaveLength(1);
});
it('should return unchanged graph when no verifier is configured', async () => {
const nodes: TestEventType[] = ['test_failed', 'build_failed'];
const edges: CausalEdge[] = [
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges);
const observations = new Map();
observations.set('test_failed->build_failed', {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
});
const filteredGraph = await graph.filterSpuriousEdges(observations);
// Should be the same graph
expect(filteredGraph).toBe(graph);
expect(filteredGraph.edges).toHaveLength(1);
});
});
describe('Integration with Graph Operations', () => {
it('should verify edges after finding intervention points', async () => {
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
const nodes: TestEventType[] = [
'code_changed',
'test_failed',
'build_failed',
'deploy_failed',
];
const edges: CausalEdge[] = [
{
source: 'code_changed',
target: 'test_failed',
strength: 0.7,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 15,
lastObserved: Date.now(),
},
{
source: 'build_failed',
target: 'deploy_failed',
strength: 0.9,
relation: 'causes',
observations: 20,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
// Find intervention points
const interventionPoints = graph.findInterventionPoints('deploy_failed', 3);
expect(interventionPoints.length).toBeGreaterThan(0);
// Verify the most critical intervention point
if (interventionPoints.length > 0) {
const criticalPoint = interventionPoints[0];
const verification = await graph.verifyEdge(
'test_failed',
'build_failed',
{
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
}
);
expect(verification).not.toBeNull();
}
});
it('should combine reachability analysis with verification', async () => {
const verifier = createUninitializedCausalVerifier(wasmLoader);
await verifier.initialize();
const nodes: TestEventType[] = [
'code_changed',
'test_failed',
'build_failed',
];
const edges: CausalEdge[] = [
{
source: 'code_changed',
target: 'test_failed',
strength: 0.7,
relation: 'causes',
observations: 10,
lastObserved: Date.now(),
},
{
source: 'test_failed',
target: 'build_failed',
strength: 0.8,
relation: 'causes',
observations: 15,
lastObserved: Date.now(),
},
];
const graph = new CausalGraphImpl(nodes, edges, verifier);
// Find all nodes reachable from code_changed
const reachable = graph.reachableFrom('code_changed');
expect(reachable.has('test_failed')).toBe(true);
expect(reachable.has('build_failed')).toBe(true);
// Verify each edge in the reachable subgraph
const subgraph = graph.getSubgraphFrom('code_changed');
for (const edge of subgraph.edges) {
const verification = await graph.verifyEdge(edge.source, edge.target, {
sourceOccurrences: Array(50).fill(1),
targetOccurrences: Array(50).fill(1),
});
expect(verification).not.toBeNull();
}
});
});
});