Files
ruvnet__ruflo/plugins/ruflo-graph-intelligence/tests/phase6_5-streaming.test.ts
ruv ed81567cdb feat(graph-intelligence): #2044 ADR-123 Phases 6.5+7 — streaming bridge + signed PR artifacts (beyond-SOTA)
Phase 6.5 — Wedge 12: StreamingBridge
  application/streaming-bridge.ts — couples a registered SublinearAdapter
  with solve_on_change so event-driven graphs pay only O(nnz(delta) · log N)
  per event. Maintains prev_solution in memory; pushDelta applies the
  delta or falls back to full re-solve when the delta ratio exceeds the
  threshold (default 0.05) or refresh-cap (default 50) hits. Cold-start
  produces a full-solve baseline. Reset clears cached state.

Phase 7 — Witness-signed PR artifacts (BEYOND-SOTA WEDGE)
  domain/signed-artifact.ts — SignedPageRankPayload + Envelope schema.
  Embeds complexity_class + coherence_score + resultHash echoes so
  verification covers provenance + budget compliance + input stability.
  Solver version pinned to sublinear-time-solver@1.7.0.
  infrastructure/witness-signer.ts — Ed25519 sign/verify via node:crypto
  (no new deps). canonicalJSON skips undefined keys for round-trip
  determinism. Trust-list filtering. Three integrity checks before
  signature verify: resultHash echo, coherenceScore echo,
  complexityClass echo — all three must match the embedded PageRankResult
  or the artifact is rejected as tampered.

98/98 tests passing (+18 since Phase 6):
  - phase6_5-streaming.test.ts: 6 (cold-start, sparse delta, full-resolve
    fallback, refresh-cap, getCurrentSolution, reset)
  - phase7-signed-artifact.test.ts: 12 (canonicalJSON determinism +
    undefined-key skip, round-trip, 4 tamper-detection scenarios, trust
    list accept/reject, byte-deterministic signing, signature changes
    with content)

No competing memory framework (LangGraph, AutoGen, Letta, MemGPT, Mem0,
HippoRAG) ships signed graph-reasoning artifacts. The combination of
ADR-103 witness chain + ADR-104 federation transport + sublinear's
deterministic forward-push composes into a primitive structurally
unavailable to systems without these three substrate primitives.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-18 22:01:09 -04:00

136 lines
4.3 KiB
TypeScript

/**
* Phase 6.5 Tests — Streaming Bridge (Wedge 12)
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { StreamingBridge } from '../src/application/streaming-bridge.js';
import { resetRegistry, getRegistry, type SublinearAdapter } from '../src/domain/adapter.js';
import type { SparseEntry, SparseMatrix } from '../src/domain/types.js';
function staticDdAdapter(n: number, graphId = 'streaming:test'): SublinearAdapter {
const entries: SparseEntry[] = [];
const nodeIndex: Record<string, number> = {};
const indexNode: string[] = [];
for (let i = 0; i < n; i++) {
nodeIndex[`n${i}`] = i;
indexNode.push(`n${i}`);
entries.push({ row: i, col: i, value: 5 });
if (i > 0) entries.push({ row: i, col: i - 1, value: -1 });
if (i < n - 1) entries.push({ row: i, col: i + 1, value: -1 });
}
const matrix: SparseMatrix = {
graphId,
size: n,
entries,
nodeIndex,
indexNode,
capturedAt: '2026-05-19T00:00:00Z',
};
return {
graphId,
ownerPlugin: 'test',
async exportAsSparseMatrix() {
return matrix;
},
};
}
describe('StreamingBridge', () => {
beforeEach(() => resetRegistry());
it('cold-start produces a full-solve baseline', async () => {
const adapter = staticDdAdapter(8);
const bridge = new StreamingBridge({
adapter,
initialRhs: Array.from({ length: 8 }, () => 1),
algorithm: 'cg',
maxComplexityClass: 'polynomial',
});
const u = await bridge.coldStart();
expect(u.mode).toBe('cold-start');
expect(u.x).toHaveLength(8);
expect(u.residualNorm).toBeLessThan(1e-3);
});
it('applies a sparse delta via solve_on_change', async () => {
const adapter = staticDdAdapter(8);
const bridge = new StreamingBridge({
adapter,
initialRhs: Array.from({ length: 8 }, () => 1),
algorithm: 'cg',
maxComplexityClass: 'polynomial',
deltaRatioThreshold: 0.5, // generous so single-element delta uses solve_on_change
});
await bridge.coldStart();
const u = await bridge.pushDelta({ indices: [3], values: [0.5] });
expect(u.mode).toBe('delta');
expect(u.deltaNnz).toBe(1);
expect(u.x).toHaveLength(8);
});
it('falls back to full re-solve when delta ratio exceeds threshold', async () => {
const adapter = staticDdAdapter(4);
const bridge = new StreamingBridge({
adapter,
initialRhs: [1, 1, 1, 1],
algorithm: 'cg',
maxComplexityClass: 'polynomial',
deltaRatioThreshold: 0.1, // very strict
});
await bridge.coldStart();
const u = await bridge.pushDelta({
indices: [0, 1, 2, 3],
values: [0.1, 0.1, 0.1, 0.1],
});
expect(u.mode).toBe('full-resolve');
});
it('refresh-cap forces full re-solve after N deltas', async () => {
const adapter = staticDdAdapter(8);
const bridge = new StreamingBridge({
adapter,
initialRhs: Array.from({ length: 8 }, () => 1),
algorithm: 'cg',
maxComplexityClass: 'polynomial',
deltaRatioThreshold: 0.5,
refreshEvery: 3,
});
await bridge.coldStart();
// 3 deltas → 4th should be full-resolve via refresh cap
for (let i = 0; i < 3; i++) {
await bridge.pushDelta({ indices: [i], values: [0.1] });
}
const u = await bridge.pushDelta({ indices: [4], values: [0.1] });
expect(u.mode).toBe('full-resolve');
});
it('getCurrentSolution returns the latest known x', async () => {
const adapter = staticDdAdapter(8);
const bridge = new StreamingBridge({
adapter,
initialRhs: Array.from({ length: 8 }, () => 1),
algorithm: 'cg',
maxComplexityClass: 'polynomial',
});
await bridge.coldStart();
expect(bridge.getCurrentSolution()).toBeDefined();
expect(bridge.getCurrentSolution()).toHaveLength(8);
});
it('reset clears cached state', async () => {
const adapter = staticDdAdapter(8);
const bridge = new StreamingBridge({
adapter,
initialRhs: Array.from({ length: 8 }, () => 1),
algorithm: 'cg',
maxComplexityClass: 'polynomial',
});
await bridge.coldStart();
bridge.reset();
expect(bridge.getCurrentSolution()).toBeUndefined();
// After reset, a delta push should cold-start first
const u = await bridge.pushDelta({ indices: [3], values: [0.5] });
expect(u).toBeDefined();
});
});