mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
ed81567cdb
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>
136 lines
4.3 KiB
TypeScript
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();
|
|
});
|
|
});
|