Files
proffesor-for-testing__agen…/docs/implementation/specs/SPEC-035-A-causal-weight-matrix.md
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

6.8 KiB

SPEC-035-A: Causal Weight Matrix

Field Value
Specification ID SPEC-035-A
Parent ADR ADR-035
Version 1.0
Status Draft
Last Updated 2026-01-20
Author Architecture Team

Overview

This specification defines the Causal Weight Matrix that learns causal relationships between test events using asymmetric STDP (Spike-Timing Dependent Plasticity) rules. The matrix tracks timing relationships to infer which events cause others.


Configuration

export interface CausalDiscoveryConfig {
  /** Number of event types to track */
  numEventTypes: number;

  /** Threshold for significant causal relationship */
  causalThreshold: number;

  /** Time window for causality detection (ms) */
  timeWindow: number;

  /** Learning rate for causal weight updates */
  learningRate: number;

  /** Decay rate for causal weights */
  decayRate: number;
}

export const DEFAULT_CAUSAL_CONFIG: CausalDiscoveryConfig = {
  numEventTypes: 100,
  causalThreshold: 0.1,
  timeWindow: 50,
  learningRate: 0.01,
  decayRate: 0.001,
};

Event Types

export type TestEventType =
  | 'test_started'
  | 'test_passed'
  | 'test_failed'
  | 'test_flaky'
  | 'assertion_failed'
  | 'timeout'
  | 'exception'
  | 'resource_exhausted'
  | 'dependency_failed'
  | 'coverage_changed'
  | 'build_failed'
  | 'deploy_started'
  | 'deploy_failed'
  | 'deploy_succeeded'
  | 'rollback_triggered'
  | 'alert_fired';

export interface TestEvent {
  /** Type of event */
  type: TestEventType;

  /** Timestamp in milliseconds */
  timestamp: number;

  /** Associated test ID (if applicable) */
  testId?: string;

  /** Associated file path (if applicable) */
  file?: string;

  /** Event metadata */
  data?: Record<string, unknown>;
}

Causal Relationship Types

export type CausalRelation = 'causes' | 'prevents' | 'none';

export interface CausalEdge {
  source: TestEventType;
  target: TestEventType;
  strength: number;
  relation: CausalRelation;
}

export interface CausalGraph {
  nodes: TestEventType[];
  edges: CausalEdge[];

  /** Get all edges from a source event */
  edgesFrom(source: TestEventType): CausalEdge[];

  /** Get all edges to a target event */
  edgesTo(target: TestEventType): CausalEdge[];

  /** Find reachable nodes from a source */
  reachableFrom(source: TestEventType): Set<TestEventType>;

  /** Compute transitive closure */
  transitiveClosure(): CausalGraph;
}

CausalWeightMatrix Implementation

export class CausalWeightMatrix {
  private weights: Map<string, number> = new Map();
  private lastSpikeTime: Map<TestEventType, number> = new Map();

  constructor(private config: CausalDiscoveryConfig) {}

  /** Get causal weight from source to target */
  getWeight(source: TestEventType, target: TestEventType): number {
    return this.weights.get(`${source}->${target}`) || 0;
  }

  /** Set causal weight */
  setWeight(source: TestEventType, target: TestEventType, weight: number): void {
    this.weights.set(`${source}->${target}`, weight);
  }

  /** Update weights using asymmetric STDP rule */
  updateWeights(event: TestEvent): void {
    const currentTime = event.timestamp;
    const currentType = event.type;

    // Update weights based on timing relative to all other event types
    for (const [otherType, lastTime] of this.lastSpikeTime.entries()) {
      const dt = currentTime - lastTime;

      if (dt > 0 && dt < this.config.timeWindow) {
        // Other event preceded this one -> otherType may cause currentType
        const weightChange = this.stdpPositive(dt);
        const currentWeight = this.getWeight(otherType, currentType);
        this.setWeight(otherType, currentType, currentWeight + weightChange);
      } else if (dt < 0 && Math.abs(dt) < this.config.timeWindow) {
        // This event preceded other -> currentType may cause otherType
        const weightChange = this.stdpNegative(Math.abs(dt));
        const currentWeight = this.getWeight(currentType, otherType);
        this.setWeight(currentType, otherType, currentWeight + weightChange);
      }
    }

    // Record this event's time
    this.lastSpikeTime.set(currentType, currentTime);
  }

  /** STDP: positive timing (pre before post) strengthens connection */
  private stdpPositive(dt: number): number {
    const tau = this.config.timeWindow / 3;
    return this.config.learningRate * Math.exp(-dt / tau);
  }

  /** STDP: negative timing (post before pre) weakens connection */
  private stdpNegative(dt: number): number {
    const tau = this.config.timeWindow / 3;
    return -0.5 * this.config.learningRate * Math.exp(-dt / tau);
  }

  /** Apply decay to all weights */
  decay(): void {
    for (const [key, weight] of this.weights.entries()) {
      const decayedWeight = weight * (1 - this.config.decayRate);
      if (Math.abs(decayedWeight) < 0.001) {
        this.weights.delete(key);
      } else {
        this.weights.set(key, decayedWeight);
      }
    }
  }

  /** Extract causal graph from learned weights */
  extractCausalGraph(): CausalGraph {
    const nodes = new Set<TestEventType>();
    const edges: CausalEdge[] = [];

    for (const [key, weight] of this.weights.entries()) {
      const [source, target] = key.split('->') as [TestEventType, TestEventType];

      if (Math.abs(weight) > this.config.causalThreshold) {
        nodes.add(source);
        nodes.add(target);

        edges.push({
          source,
          target,
          strength: Math.abs(weight),
          relation: weight > 0 ? 'causes' : 'prevents',
        });
      }
    }

    return new CausalGraphImpl(Array.from(nodes), edges);
  }
}

Validation Rules

Rule ID Description Severity
SPEC-035-A-001 timeWindow must be > 0 Error
SPEC-035-A-002 learningRate must be 0-1 Warning
SPEC-035-A-003 decayRate must be 0-1 Warning
SPEC-035-A-004 causalThreshold must be >= 0 Error

Spec ID Title Relationship
SPEC-035-B Causal Graph Uses weight matrix
SPEC-035-C Discovery Engine Orchestrates learning

Changelog

Version Date Author Changes
1.0 2026-01-20 Architecture Team Initial specification

References