Files
ruvnet__ruflo/plugins/ruflo-graph-intelligence/tests/phase8-federation.test.ts
ruv 3cec3bf94a feat(graph-intelligence): #2044 ADR-123 Phase 8 — federation distribution of signed PR vectors
Phase 8 (BEYOND-SOTA MOAT):
  domain/federation-protocol.ts — 4 wire message types over ADR-104:
    pr_artifact_request   — peer asks for a PR over (graphId, queryNode)
                            with lastKnownGraphHash + lastKnownResultHash
                            for stale-detection
    pr_artifact_response  — peer ships a Phase 7 signed envelope back
    pr_artifact_delta     — sparse delta over a known base resultHash
    pr_artifact_stale     — graph changed; consumer must recompute
    All Zod-validated, discriminated-union routable.
  application/federation-server.ts — receives requests, rate-limits per
    peer (default 60/min), computes via Phase 2-6 adapters + runPageRank,
    seals via Phase 7 sealArtifact. Returns stale rejects when graphHash
    mismatches or graphId unknown.
  application/federation-client.ts — sends requests via FederationTransport;
    verifies inbound envelopes against trusted-public-keys list; falls back
    to local compute (origin: 'peer' / 'stale-fallback' /
    'untrusted-fallback' / 'local-fallback') with reason.
  inProcessTransport() helper stitches a client to a server for in-process
    testing without spinning up real ADR-104 wiring.

104/104 tests passing (+6 for Phase 8):
  - phase8-federation.test.ts: 6 (verifiable peer artifact, untrusted-key
    fallback, stale graphHash, unknown graphId, rate limit per peer,
    unreachable-peer local fallback)

All 8 phases of ADR-123 are now feature-complete. Plugin builds clean,
ships at 0.1.0-alpha.1 with 104 passing tests across the eleven wedges
(W1-W11) + Wedge 12 streaming + the beyond-SOTA Phase 7+8 federation
artifact pipeline.

Next: publish ruflo-graph-intelligence to npm, open + merge PR, create
announcement gist with intro/features/usages (practical to exotic).

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

195 lines
6.5 KiB
TypeScript

/**
* Phase 8 Tests — Federation Distribution of Signed PR Vectors (beyond-SOTA)
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { FederationServer } from '../src/application/federation-server.js';
import {
FederationClient,
inProcessTransport,
} from '../src/application/federation-client.js';
import { generateWitnessKey } from '../src/infrastructure/witness-signer.js';
import { resetRegistry, getRegistry, type SublinearAdapter } from '../src/domain/adapter.js';
import type { SparseMatrix } from '../src/domain/types.js';
import { createHash } from 'node:crypto';
function ddAdapter(graphId: string, n = 6): SublinearAdapter {
const entries = [];
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 contentHash = createHash('sha256')
.update(JSON.stringify({ graphId, n }))
.digest('hex');
const matrix: SparseMatrix = {
graphId,
size: n,
entries,
nodeIndex,
indexNode,
capturedAt: '2026-05-19T00:00:00Z',
contentHash,
};
return {
graphId,
ownerPlugin: 'test',
async exportAsSparseMatrix() {
return matrix;
},
};
}
describe('Federation round-trip', () => {
beforeEach(() => resetRegistry());
it('client receives a verifiable peer artifact', async () => {
const adapter = ddAdapter('fed:test', 6);
getRegistry().register(adapter);
const key = generateWitnessKey();
const server = new FederationServer({
installationId: 'inst-A',
witnessKey: key,
witnessKeyId: 'key-v1',
transport: { async send() { return null; }, onMessage() { return () => {}; } },
});
const client = new FederationClient({
installationId: 'inst-B',
transport: inProcessTransport(server),
trustedPublicKeys: [key.publicKeyHex],
});
const result = await client.fetchPageRank({
peer: 'inst-A',
graphId: 'fed:test',
nodeId: 'n2',
});
expect(result.origin).toBe('peer');
expect(result.verification?.valid).toBe(true);
expect(result.result.score).toBeGreaterThanOrEqual(0);
});
it('client falls back to local on untrusted signer', async () => {
const adapter = ddAdapter('fed:test', 6);
getRegistry().register(adapter);
const strangerKey = generateWitnessKey();
const server = new FederationServer({
installationId: 'inst-A',
witnessKey: strangerKey,
witnessKeyId: 'key-v1',
transport: { async send() { return null; }, onMessage() { return () => {}; } },
});
const client = new FederationClient({
installationId: 'inst-B',
transport: inProcessTransport(server),
trustedPublicKeys: ['0'.repeat(64)], // stranger NOT in trust list
});
const result = await client.fetchPageRank({
peer: 'inst-A',
graphId: 'fed:test',
nodeId: 'n2',
});
expect(result.origin).toBe('untrusted-fallback');
expect(result.fallbackReason).toMatch(/trusted list/i);
expect(result.result.score).toBeGreaterThanOrEqual(0);
});
it('server sends pr_artifact_stale when graphHash differs', async () => {
const adapter = ddAdapter('fed:test', 6);
getRegistry().register(adapter);
const key = generateWitnessKey();
const server = new FederationServer({
installationId: 'inst-A',
witnessKey: key,
witnessKeyId: 'key-v1',
transport: { async send() { return null; }, onMessage() { return () => {}; } },
});
const client = new FederationClient({
installationId: 'inst-B',
transport: inProcessTransport(server),
trustedPublicKeys: [key.publicKeyHex],
});
const result = await client.fetchPageRank({
peer: 'inst-A',
graphId: 'fed:test',
nodeId: 'n2',
lastKnownGraphHash: '0'.repeat(64), // doesn't match what server has
});
expect(result.origin).toBe('stale-fallback');
expect(result.fallbackReason).toMatch(/changed/);
});
it('server returns stale-reject for an unknown graphId', async () => {
const key = generateWitnessKey();
const server = new FederationServer({
installationId: 'inst-A',
witnessKey: key,
witnessKeyId: 'key-v1',
transport: { async send() { return null; }, onMessage() { return () => {}; } },
});
const client = new FederationClient({
installationId: 'inst-B',
transport: inProcessTransport(server),
});
// No adapter registered on client OR server side
try {
await client.fetchPageRank({
peer: 'inst-A',
graphId: 'fed:nonexistent',
nodeId: 'n2',
});
throw new Error('expected localCompute to throw');
} catch (err) {
expect((err as Error).message).toMatch(/no adapter/);
}
});
it('server rate-limits per peer', async () => {
const adapter = ddAdapter('fed:test', 6);
getRegistry().register(adapter);
const key = generateWitnessKey();
const server = new FederationServer({
installationId: 'inst-A',
witnessKey: key,
witnessKeyId: 'key-v1',
transport: { async send() { return null; }, onMessage() { return () => {}; } },
rateLimitPerPeerPerMinute: 2,
});
const client = new FederationClient({
installationId: 'inst-B',
transport: inProcessTransport(server),
trustedPublicKeys: [key.publicKeyHex],
});
const r1 = await client.fetchPageRank({ peer: 'inst-A', graphId: 'fed:test', nodeId: 'n2' });
const r2 = await client.fetchPageRank({ peer: 'inst-A', graphId: 'fed:test', nodeId: 'n3' });
const r3 = await client.fetchPageRank({ peer: 'inst-A', graphId: 'fed:test', nodeId: 'n4' });
expect(r1.origin).toBe('peer');
expect(r2.origin).toBe('peer');
expect(r3.origin).toBe('stale-fallback');
expect(r3.fallbackReason).toMatch(/rate limit/i);
});
it('client falls back to local compute when no usable response', async () => {
const adapter = ddAdapter('fed:test', 6);
getRegistry().register(adapter);
const client = new FederationClient({
installationId: 'inst-B',
transport: {
async send() { return null; }, // server unreachable
onMessage() { return () => {}; },
},
});
const result = await client.fetchPageRank({
peer: 'inst-A',
graphId: 'fed:test',
nodeId: 'n2',
});
expect(result.origin).toBe('local-fallback');
expect(result.result.score).toBeGreaterThanOrEqual(0);
});
});