@claude-flow/plugin-agent-federation 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/agent-card.d.ts +130 -0
- package/dist/a2a/consume.d.ts +34 -0
- package/dist/a2a/planner-descriptor.d.ts +40 -0
- package/dist/a2a/well-known.d.ts +46 -0
- package/dist/application/claim-checker.d.ts +27 -0
- package/dist/application/federation-breaker-service.d.ts +192 -0
- package/dist/application/federation-coordinator.d.ts +211 -0
- package/dist/application/inbound-dispatcher.d.ts +113 -0
- package/dist/application/policy-engine.d.ts +42 -0
- package/dist/application/policy-engine.d.ts.map +1 -1
- package/dist/application/policy-engine.js +11 -1
- package/dist/application/policy-engine.js.map +1 -1
- package/dist/application/spend-reporter.d.ts +150 -0
- package/dist/application/trust-evaluator.d.ts +74 -0
- package/dist/bin.d.ts +13 -0
- package/dist/cli-commands.d.ts +7 -0
- package/dist/domain/entities/federation-envelope.d.ts +45 -0
- package/dist/domain/entities/federation-node.d.ts +104 -0
- package/dist/domain/entities/federation-session.d.ts +55 -0
- package/dist/domain/entities/trust-level.d.ts +17 -0
- package/dist/domain/entities/trust-level.d.ts.map +1 -1
- package/dist/domain/entities/trust-level.js +9 -2
- package/dist/domain/entities/trust-level.js.map +1 -1
- package/dist/domain/services/audit-service.d.ts +69 -0
- package/dist/domain/services/discovery-service.d.ts +76 -0
- package/dist/domain/services/handshake-service.d.ts +62 -0
- package/dist/domain/services/pii-pipeline-service.d.ts +65 -0
- package/dist/domain/services/routing-service.d.ts +54 -0
- package/dist/domain/services/wg-firewall-service.d.ts +71 -0
- package/dist/domain/services/wg-mesh-service.d.ts +156 -0
- package/dist/domain/services/wg-witness-service.d.ts +98 -0
- package/dist/domain/value-objects/federation-budget.d.ts +89 -0
- package/dist/domain/value-objects/federation-node-state.d.ts +92 -0
- package/dist/domain/value-objects/wg-config.d.ts +70 -0
- package/dist/index.d.ts +24 -0
- package/dist/mcp-tools.d.ts +10 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/transport/midstream-aware-loader.d.ts +72 -0
- package/dist/version.d.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { FederationNode, type FederationNodeStateRecord } from '../domain/entities/federation-node.js';
|
|
2
|
+
import { FederationSession } from '../domain/entities/federation-session.js';
|
|
3
|
+
import { type FederationMessageType } from '../domain/entities/federation-envelope.js';
|
|
4
|
+
import { TrustLevel } from '../domain/entities/trust-level.js';
|
|
5
|
+
import { type SuspensionReason } from '../domain/value-objects/federation-node-state.js';
|
|
6
|
+
import { DiscoveryService, type FederationManifest } from '../domain/services/discovery-service.js';
|
|
7
|
+
import { HandshakeService } from '../domain/services/handshake-service.js';
|
|
8
|
+
import { RoutingService, type RoutingResult } from '../domain/services/routing-service.js';
|
|
9
|
+
import { AuditService } from '../domain/services/audit-service.js';
|
|
10
|
+
import { PIIPipelineService } from '../domain/services/pii-pipeline-service.js';
|
|
11
|
+
import { TrustEvaluator, type BootstrapElevationAuditEntry } from './trust-evaluator.js';
|
|
12
|
+
import { PolicyEngine } from './policy-engine.js';
|
|
13
|
+
import { enforceBudget, validateBudget } from '../domain/value-objects/federation-budget.js';
|
|
14
|
+
import type { FederationBreakerService } from './federation-breaker-service.js';
|
|
15
|
+
import type { SpendReporter } from './spend-reporter.js';
|
|
16
|
+
import type { WgMeshService, WgCommand } from '../domain/services/wg-mesh-service.js';
|
|
17
|
+
/**
|
|
18
|
+
* Optional per-call budget controls (ADR-097 Phase 1). All fields are
|
|
19
|
+
* optional; omitted means "no limit on that axis" (the default unbounded
|
|
20
|
+
* budget still has maxHops = 8 to defang recursive delegation loops).
|
|
21
|
+
*/
|
|
22
|
+
export interface SendOptions {
|
|
23
|
+
readonly budget?: {
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
maxUsd?: number;
|
|
26
|
+
};
|
|
27
|
+
readonly maxHops?: number;
|
|
28
|
+
/**
|
|
29
|
+
* How many hops this message has already traveled (0 on the originator).
|
|
30
|
+
* Phase 1 enforces at the send side because there is no inbound dispatcher
|
|
31
|
+
* yet; callers re-forwarding a received message should pass the original
|
|
32
|
+
* envelope's hopCount here.
|
|
33
|
+
*/
|
|
34
|
+
readonly hopCount?: number;
|
|
35
|
+
/** Caller-reported usage from the previous leg, for cumulative checks. */
|
|
36
|
+
readonly spent?: {
|
|
37
|
+
tokens?: number;
|
|
38
|
+
usd?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface FederationCoordinatorConfig {
|
|
42
|
+
readonly nodeId: string;
|
|
43
|
+
readonly publicKey: string;
|
|
44
|
+
readonly endpoint: string;
|
|
45
|
+
readonly capabilities: readonly string[];
|
|
46
|
+
/** Wire protocols this runtime is prepared to advertise and negotiate. */
|
|
47
|
+
readonly supportedProtocols?: readonly string[];
|
|
48
|
+
readonly version?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface FederationStatus {
|
|
51
|
+
readonly nodeId: string;
|
|
52
|
+
readonly activeSessions: number;
|
|
53
|
+
readonly knownPeers: number;
|
|
54
|
+
readonly trustLevels: Record<string, TrustLevel>;
|
|
55
|
+
readonly healthy: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Optional integrations (ADR-097 Phase 3 upstream + Phase 2.b breaker
|
|
59
|
+
* wiring). Both are constructor-injected; both default to no-op.
|
|
60
|
+
*
|
|
61
|
+
* - `spendReporter` — invoked by reportSpend() when present; persists
|
|
62
|
+
* the FederationSpendEvent to whatever backend the integrator wired
|
|
63
|
+
* (cost-tracker bus, ruflo memory federation-spend namespace, etc.)
|
|
64
|
+
* - `breakerService` — invoked by reportSpend() when present; calls
|
|
65
|
+
* recordOutcome() so the breaker's in-memory rolling buffer is fed
|
|
66
|
+
* without requiring the integrator to wire two parallel pipelines
|
|
67
|
+
*/
|
|
68
|
+
export interface FederationCoordinatorIntegrations {
|
|
69
|
+
readonly spendReporter?: SpendReporter;
|
|
70
|
+
readonly breakerService?: FederationBreakerService;
|
|
71
|
+
/**
|
|
72
|
+
* ADR-111 Phase 3 — optional WG mesh service. When present, peer state
|
|
73
|
+
* transitions (evict/reactivate/breaker-suspend) emit `wg set` commands
|
|
74
|
+
* to a sink supplied by `wgCommandSink`. No-op for peers without a `wg`
|
|
75
|
+
* manifest block, so wiring the service is safe even in mixed deployments.
|
|
76
|
+
*/
|
|
77
|
+
readonly wgMesh?: WgMeshService;
|
|
78
|
+
/**
|
|
79
|
+
* Where emitted WG commands go. Defaults to the audit log only. Integrators
|
|
80
|
+
* can plug in a shell executor here (after operator approval — bringing up
|
|
81
|
+
* a network interface is destructive). Commands include shell-ready
|
|
82
|
+
* `wg set <iface> ...` strings; the sink should still validate before exec.
|
|
83
|
+
*/
|
|
84
|
+
readonly wgCommandSink?: (cmd: WgCommand) => void | Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
export declare class FederationCoordinator {
|
|
87
|
+
private readonly config;
|
|
88
|
+
private readonly discovery;
|
|
89
|
+
private readonly handshake;
|
|
90
|
+
private readonly routing;
|
|
91
|
+
private readonly audit;
|
|
92
|
+
private readonly piiPipeline;
|
|
93
|
+
private readonly trustEvaluator;
|
|
94
|
+
private readonly policyEngine;
|
|
95
|
+
private readonly sessions;
|
|
96
|
+
private initialized;
|
|
97
|
+
private readonly spendReporter?;
|
|
98
|
+
private readonly breakerService?;
|
|
99
|
+
private readonly wgMesh?;
|
|
100
|
+
private readonly wgCommandSink?;
|
|
101
|
+
constructor(config: FederationCoordinatorConfig, discovery: DiscoveryService, handshake: HandshakeService, routing: RoutingService, audit: AuditService, piiPipeline: PIIPipelineService, trustEvaluator: TrustEvaluator, policyEngine: PolicyEngine, integrations?: FederationCoordinatorIntegrations);
|
|
102
|
+
/**
|
|
103
|
+
* ADR-111 Phase 3 — emit a WG command for an audited peer transition.
|
|
104
|
+
* No-op if no wgMesh is configured or the peer lacks a `wg` manifest
|
|
105
|
+
* block. Commands flow to `wgCommandSink` (if any) and always to the
|
|
106
|
+
* audit log so reactivation is fully traceable.
|
|
107
|
+
*/
|
|
108
|
+
private emitWgCommand;
|
|
109
|
+
initialize(manifest: Omit<FederationManifest, 'signature'>): Promise<void>;
|
|
110
|
+
shutdown(): Promise<void>;
|
|
111
|
+
joinPeer(endpoint: string, manifest?: FederationManifest): Promise<FederationSession>;
|
|
112
|
+
leavePeer(nodeId: string): Promise<void>;
|
|
113
|
+
sendMessage<T>(targetNodeId: string, messageType: FederationMessageType, payload: T, options?: SendOptions): Promise<RoutingResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Re-export the budget primitives so external callers (e.g. follow-up
|
|
116
|
+
* iterations adding receive-side decrement) can reuse them without
|
|
117
|
+
* dipping into domain/value-objects directly.
|
|
118
|
+
*/
|
|
119
|
+
static readonly budget: {
|
|
120
|
+
validate: typeof validateBudget;
|
|
121
|
+
enforce: typeof enforceBudget;
|
|
122
|
+
};
|
|
123
|
+
broadcastMessage<T>(messageType: FederationMessageType, payload: T): Promise<RoutingResult[]>;
|
|
124
|
+
handleThreatDetection(nodeId: string): void;
|
|
125
|
+
handleHmacFailure(nodeId: string): void;
|
|
126
|
+
getStatus(): FederationStatus;
|
|
127
|
+
/**
|
|
128
|
+
* ADR-097 Phase 4: per-peer breaker state snapshot for the doctor
|
|
129
|
+
* surface and `federation_breaker_status` MCP tool.
|
|
130
|
+
*
|
|
131
|
+
* Returns one entry per known peer with the entity's stateRecord —
|
|
132
|
+
* what state, when it changed, why, and which caller's correlation
|
|
133
|
+
* key triggered it. Pure read; does not mutate.
|
|
134
|
+
*/
|
|
135
|
+
getPeerStates(): readonly (FederationNodeStateRecord & {
|
|
136
|
+
readonly nodeId: string;
|
|
137
|
+
})[];
|
|
138
|
+
/**
|
|
139
|
+
* ADR-111 Phase 6: public read of the discovery peer list. Used by
|
|
140
|
+
* federation_wg_status MCP tool + WG firewall projection. Returns the
|
|
141
|
+
* full known-peer set (active + suspended + evicted) — filter via
|
|
142
|
+
* .isActive / state if you only want the live mesh members.
|
|
143
|
+
*/
|
|
144
|
+
listPeers(): readonly FederationNode[];
|
|
145
|
+
/**
|
|
146
|
+
* Aggregated counts for the doctor surface — `{ active: N, suspended: M,
|
|
147
|
+
* evicted: K }`. Cheap O(peers) sweep; safe to call from a status line.
|
|
148
|
+
*/
|
|
149
|
+
getPeerStateCounts(): {
|
|
150
|
+
readonly active: number;
|
|
151
|
+
readonly suspended: number;
|
|
152
|
+
readonly evicted: number;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Operator-initiated evict. Returns true on transition, false if the
|
|
156
|
+
* peer was already EVICTED or unknown. Logs to audit either way.
|
|
157
|
+
*
|
|
158
|
+
* Does NOT remove the peer from the discovery registry — `leavePeer`
|
|
159
|
+
* is the registry-removal API. Eviction is the breaker layer; the peer
|
|
160
|
+
* remains queryable so the operator can later reactivate.
|
|
161
|
+
*/
|
|
162
|
+
evictPeer(nodeId: string, reason?: SuspensionReason, correlationId?: string): Promise<boolean>;
|
|
163
|
+
/**
|
|
164
|
+
* ADR-097 Phase 3 upstream: report the actual cost of a federated
|
|
165
|
+
* call. Federation doesn't own model pricing, so the integrator calls
|
|
166
|
+
* this after the downstream agent completes.
|
|
167
|
+
*
|
|
168
|
+
* Fans out to:
|
|
169
|
+
* - spendReporter (if injected) — persists to integrator's backend
|
|
170
|
+
* - breakerService.recordOutcome (if injected) — feeds the breaker's
|
|
171
|
+
* in-memory rolling buffer for cost/failure-ratio thresholds
|
|
172
|
+
*
|
|
173
|
+
* Both are no-ops if the corresponding integration isn't wired, so
|
|
174
|
+
* callers don't need to branch on configuration. Negative tokens/usd
|
|
175
|
+
* are clamped to 0 at the breaker layer (anti-credit-inflation); the
|
|
176
|
+
* spend reporter receives the raw values so backends can audit them.
|
|
177
|
+
*
|
|
178
|
+
* Auto-fills `ts` if the caller omits it.
|
|
179
|
+
*/
|
|
180
|
+
reportSpend(input: {
|
|
181
|
+
readonly peerId: string;
|
|
182
|
+
readonly taskId?: string;
|
|
183
|
+
readonly tokensUsed: number;
|
|
184
|
+
readonly usdSpent: number;
|
|
185
|
+
readonly success: boolean;
|
|
186
|
+
readonly ts?: string;
|
|
187
|
+
}): Promise<void>;
|
|
188
|
+
/**
|
|
189
|
+
* Founder-bootstrap trust elevation (ADR-164 §3.5.4 — operator escape hatch
|
|
190
|
+
* used in the autopilot Day-1 scenario where a freshly-joined BBS peer
|
|
191
|
+
* needs TRUSTED before organic minInteractions thresholds can accrue).
|
|
192
|
+
*
|
|
193
|
+
* Refuses if the target node is not a registered federation peer (returns
|
|
194
|
+
* null). On success, writes a `trust_level_changed` audit entry tagged
|
|
195
|
+
* `bootstrap_elevation` with the operator-supplied reason, and returns
|
|
196
|
+
* the audit entry so the CLI / caller can print it to stdout.
|
|
197
|
+
*/
|
|
198
|
+
bootstrapElevatePeer(nodeId: string, newLevel: TrustLevel, reason: string): Promise<BootstrapElevationAuditEntry | null>;
|
|
199
|
+
/**
|
|
200
|
+
* Operator-initiated reactivate. Used after an integrator-supplied
|
|
201
|
+
* health probe confirms a SUSPENDED peer is healthy, OR as an
|
|
202
|
+
* operator-override escape from EVICTED. Returns true on transition.
|
|
203
|
+
*/
|
|
204
|
+
reactivatePeer(nodeId: string, correlationId?: string): Promise<boolean>;
|
|
205
|
+
getSession(sessionId: string): FederationSession | undefined;
|
|
206
|
+
getActiveSessions(): FederationSession[];
|
|
207
|
+
private establishSession;
|
|
208
|
+
private findSessionByNodeId;
|
|
209
|
+
private ensureInitialized;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=federation-coordinator.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound message dispatcher (ADR-109).
|
|
3
|
+
*
|
|
4
|
+
* The federation plugin's `transport.listen()` accepts inbound bytes
|
|
5
|
+
* but until this module landed, the bytes had no consumer — they sat
|
|
6
|
+
* in the transport's per-address message queue with no one polling.
|
|
7
|
+
*
|
|
8
|
+
* `dispatchInbound()` is what the plugin registers as the
|
|
9
|
+
* transport.onMessage handler. For each received message it:
|
|
10
|
+
*
|
|
11
|
+
* 1. Resolves the sender via `sourceNodeId` in metadata
|
|
12
|
+
* 2. Verifies the peer is in the discovery registry (unknown → reject)
|
|
13
|
+
* 3. Verifies peer is ACTIVE (SUSPENDED/EVICTED → reject defense-in-depth)
|
|
14
|
+
* 4. Audits success or rejection
|
|
15
|
+
* 5. Emits a typed event on the eventBus for the integrator to handle
|
|
16
|
+
*
|
|
17
|
+
* Anti-coupling: dispatcher does NOT actually execute inbound tasks.
|
|
18
|
+
* That's the integrator's job. The dispatcher's contract is "deliver
|
|
19
|
+
* an envelope to the integrator's handler IFF it passes safety gates."
|
|
20
|
+
*/
|
|
21
|
+
import type { AgentMessage } from '../transport/midstream-aware-loader.js';
|
|
22
|
+
import type { DiscoveryService } from '../domain/services/discovery-service.js';
|
|
23
|
+
import type { AuditService } from '../domain/services/audit-service.js';
|
|
24
|
+
import type { FederationNode } from '../domain/entities/federation-node.js';
|
|
25
|
+
export declare const FEDERATION_INBOUND_EVENT_PREFIX = "federation:inbound";
|
|
26
|
+
/** Reasons we reject an inbound message — constant strings, no oracle leak. */
|
|
27
|
+
export type InboundRejectionReason = 'PEER_UNKNOWN' | 'PEER_SUSPENDED' | 'PEER_EVICTED' | 'MISSING_METADATA' | 'INVALID_PAYLOAD' | 'INVALID_SIGNATURE' | 'LEGACY_SIGNATURE_TYPE_REJECTED' | 'AUTHORIZATION_DENIED' | 'AUTHORIZATION_ERROR';
|
|
28
|
+
/**
|
|
29
|
+
* Verifier function for inbound envelopes. Given the canonical bytes of
|
|
30
|
+
* the message + the claimed signature + the peer's published public
|
|
31
|
+
* key, returns true iff the signature is valid. Plugin wires this with
|
|
32
|
+
* `@noble/ed25519`; tests inject a mock.
|
|
33
|
+
*
|
|
34
|
+
* Returning `null` means "no signature provided" — handled by the
|
|
35
|
+
* dispatcher as INVALID_SIGNATURE (defense: unsigned messages from
|
|
36
|
+
* known peers are still rejected).
|
|
37
|
+
*/
|
|
38
|
+
export type EnvelopeVerifier = (canonicalBytes: string, signatureHex: string | null, peerPublicKeyHex: string) => boolean;
|
|
39
|
+
export declare const JCS_SIGNATURE_PROTOCOL: "ruflo-signature-jcs-v1";
|
|
40
|
+
export type EnvelopeSignatureVersion = 'legacy-v1' | 'jcs-v1';
|
|
41
|
+
export type EnvelopeSignatureMode = 'legacy' | 'prefer-jcs' | 'require-jcs';
|
|
42
|
+
/**
|
|
43
|
+
* Updated peers advertise and negotiate recursively complete JCS signatures
|
|
44
|
+
* without operator configuration. Legacy mode remains explicit for low-risk
|
|
45
|
+
* heartbeat/status interoperability only.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DEFAULT_ENVELOPE_SIGNATURE_MODE: EnvelopeSignatureMode;
|
|
48
|
+
export type InboundAuthorizationMode = 'legacy' | 'observe' | 'enforce';
|
|
49
|
+
export declare function isLegacyEnvelopeTypeAllowed(messageType: string): boolean;
|
|
50
|
+
export interface InboundAuthorizationRequest {
|
|
51
|
+
readonly address: string;
|
|
52
|
+
readonly sourceNodeId: string;
|
|
53
|
+
readonly messageType: string;
|
|
54
|
+
readonly message: AgentMessage;
|
|
55
|
+
readonly peer: FederationNode;
|
|
56
|
+
readonly signatureVersion: EnvelopeSignatureVersion;
|
|
57
|
+
readonly messageSizeBytes: number;
|
|
58
|
+
}
|
|
59
|
+
export interface InboundAuthorizationDecision {
|
|
60
|
+
readonly allowed: boolean;
|
|
61
|
+
readonly reason?: string;
|
|
62
|
+
}
|
|
63
|
+
export type InboundAuthorizationEvaluator = (request: InboundAuthorizationRequest) => InboundAuthorizationDecision | Promise<InboundAuthorizationDecision>;
|
|
64
|
+
/** Dispatch dependencies (kept narrow for testability). */
|
|
65
|
+
export interface InboundDispatchDeps {
|
|
66
|
+
readonly discovery: Pick<DiscoveryService, 'getPeer'>;
|
|
67
|
+
readonly audit: Pick<AuditService, 'log'>;
|
|
68
|
+
readonly eventBus: {
|
|
69
|
+
emit: (event: string, data: unknown) => void;
|
|
70
|
+
};
|
|
71
|
+
readonly logger: {
|
|
72
|
+
debug: (m: string) => void;
|
|
73
|
+
warn: (m: string) => void;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Optional Ed25519 envelope verifier. When PROVIDED, every accepted
|
|
77
|
+
* message MUST pass verification — `null` signature or false-returning
|
|
78
|
+
* verifier rejects the message as INVALID_SIGNATURE.
|
|
79
|
+
*
|
|
80
|
+
* When OMITTED, the dispatcher operates in legacy "trust the metadata"
|
|
81
|
+
* mode (backward compat for tests that inject minimal deps). Production
|
|
82
|
+
* MUST inject this — see the plugin.ts wiring.
|
|
83
|
+
*/
|
|
84
|
+
readonly verifyEnvelope?: EnvelopeVerifier;
|
|
85
|
+
/** Defaults to both versions; enforce-only deployments pass `['jcs-v1']`. */
|
|
86
|
+
readonly acceptedSignatureVersions?: readonly EnvelopeSignatureVersion[];
|
|
87
|
+
/** Compatibility mode defaults to legacy; enforce is fail-closed. */
|
|
88
|
+
readonly authorizationMode?: InboundAuthorizationMode;
|
|
89
|
+
/** Injected policy decision point. It is always called before event emission. */
|
|
90
|
+
readonly authorizeInbound?: InboundAuthorizationEvaluator;
|
|
91
|
+
}
|
|
92
|
+
export declare function selectEnvelopeSignatureVersion(mode: EnvelopeSignatureMode, peerProtocols: readonly string[], messageType?: string): EnvelopeSignatureVersion;
|
|
93
|
+
export declare function canonicalizeEnvelopeForVerify(message: AgentMessage, requestedVersion?: EnvelopeSignatureVersion): string;
|
|
94
|
+
/** Outcome reported to the caller (mostly for tests + observability). */
|
|
95
|
+
export type InboundDispatchOutcome = {
|
|
96
|
+
readonly accepted: true;
|
|
97
|
+
readonly sourceNodeId: string;
|
|
98
|
+
readonly messageType: string;
|
|
99
|
+
} | {
|
|
100
|
+
readonly accepted: false;
|
|
101
|
+
readonly reason: InboundRejectionReason;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Process one received message. Pure-ish: no side effects beyond audit
|
|
105
|
+
* + event emission, both injected.
|
|
106
|
+
*
|
|
107
|
+
* `address` is the wire-level remote (e.g. `192.168.1.42:54321`).
|
|
108
|
+
* `message.metadata.sourceNodeId` is the cryptographic identity claim.
|
|
109
|
+
* The two MAY differ (e.g. behind NAT) — we trust `sourceNodeId` for
|
|
110
|
+
* peer lookup since it's bound to the Ed25519 keypair.
|
|
111
|
+
*/
|
|
112
|
+
export declare function dispatchInbound(address: string, message: AgentMessage, deps: InboundDispatchDeps): Promise<InboundDispatchOutcome>;
|
|
113
|
+
//# sourceMappingURL=inbound-dispatcher.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TrustLevel } from '../domain/entities/trust-level.js';
|
|
2
|
+
import { type PIIAction, type PIIDetection, type PIIPolicyConfig } from '../domain/services/pii-pipeline-service.js';
|
|
3
|
+
import { type FederationMessageType } from '../domain/entities/federation-envelope.js';
|
|
4
|
+
export type FederationClaimType = 'federation:discover' | 'federation:connect' | 'federation:read' | 'federation:write' | 'federation:admin' | 'federation:memory' | 'federation:spawn';
|
|
5
|
+
export interface SecurityPolicy {
|
|
6
|
+
readonly maxMessageSizeBytes: number;
|
|
7
|
+
readonly maxMessagesPerMinute: number;
|
|
8
|
+
readonly allowedMessageTypes: readonly FederationMessageType[];
|
|
9
|
+
readonly requirePiiScan: boolean;
|
|
10
|
+
readonly requireAiDefenceScan: boolean;
|
|
11
|
+
readonly minTrustLevelForWrite: TrustLevel;
|
|
12
|
+
readonly minTrustLevelForMemory: TrustLevel;
|
|
13
|
+
readonly minTrustLevelForSpawn: TrustLevel;
|
|
14
|
+
readonly minTrustLevelForAdmin: TrustLevel;
|
|
15
|
+
}
|
|
16
|
+
export interface PolicyEvaluationResult {
|
|
17
|
+
readonly allowed: boolean;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
readonly requiredClaims: readonly FederationClaimType[];
|
|
20
|
+
readonly trustLevelSufficient: boolean;
|
|
21
|
+
readonly piiAction?: PIIAction;
|
|
22
|
+
}
|
|
23
|
+
export interface PolicyEngineDeps {
|
|
24
|
+
checkClaim: (claim: FederationClaimType) => boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare class PolicyEngine {
|
|
27
|
+
private readonly deps;
|
|
28
|
+
private readonly securityPolicy;
|
|
29
|
+
private readonly piiPolicy;
|
|
30
|
+
private readonly rateLimitCounters;
|
|
31
|
+
constructor(deps: PolicyEngineDeps, securityPolicy?: Partial<SecurityPolicy>, piiPolicy?: PIIPolicyConfig);
|
|
32
|
+
evaluateMessage(messageType: FederationMessageType, trustLevel: TrustLevel, messageSizeBytes: number, sourceNodeId: string): PolicyEvaluationResult;
|
|
33
|
+
evaluatePiiDetection(detection: PIIDetection, trustLevel: TrustLevel): PIIAction;
|
|
34
|
+
evaluateTrustChange(currentLevel: TrustLevel, proposedLevel: TrustLevel): {
|
|
35
|
+
allowed: boolean;
|
|
36
|
+
requiresHumanApproval: boolean;
|
|
37
|
+
reason: string;
|
|
38
|
+
};
|
|
39
|
+
private checkRateLimit;
|
|
40
|
+
private getOperationForMessageType;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=policy-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-engine.d.ts","sourceRoot":"","sources":["../../src/application/policy-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,4CAA4C,CAAC;AACnI,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAEvF,MAAM,MAAM,mBAAmB,GAC3B,qBAAqB,GACrB,oBAAoB,GACpB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,mBAAmB,GACnB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC/D,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxD,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"policy-engine.d.ts","sourceRoot":"","sources":["../../src/application/policy-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,4CAA4C,CAAC;AACnI,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAEvF,MAAM,MAAM,mBAAmB,GAC3B,qBAAqB,GACrB,oBAAoB,GACpB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,mBAAmB,GACnB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC/D,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxD,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAuFD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC;CACrD;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsD;gBAGtF,IAAI,EAAE,gBAAgB,EACtB,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,EACxC,SAAS,CAAC,EAAE,eAAe;IAc7B,eAAe,CACb,WAAW,EAAE,qBAAqB,EAClC,UAAU,EAAE,UAAU,EACtB,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,GACnB,sBAAsB;IAsEzB,oBAAoB,CAAC,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS;IAWhF,mBAAmB,CACjB,YAAY,EAAE,UAAU,EACxB,aAAa,EAAE,UAAU,GACxB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,qBAAqB,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAgCvE,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,0BAA0B;CA4BnC"}
|
|
@@ -12,6 +12,7 @@ const DEFAULT_SECURITY_POLICY = {
|
|
|
12
12
|
minTrustLevelForWrite: TrustLevel.ATTESTED,
|
|
13
13
|
minTrustLevelForMemory: TrustLevel.TRUSTED,
|
|
14
14
|
minTrustLevelForSpawn: TrustLevel.PRIVILEGED,
|
|
15
|
+
minTrustLevelForAdmin: TrustLevel.TRUSTED,
|
|
15
16
|
};
|
|
16
17
|
const POLICY_CATEGORY_FOR_MESSAGE_TYPE = {
|
|
17
18
|
'task-assignment': 'write',
|
|
@@ -20,7 +21,9 @@ const POLICY_CATEGORY_FOR_MESSAGE_TYPE = {
|
|
|
20
21
|
'memory-query': 'memory',
|
|
21
22
|
'memory-response': 'memory',
|
|
22
23
|
'agent-spawn': 'spawn',
|
|
23
|
-
'agent-handoff': 'spawn',
|
|
24
|
+
'agent-handoff': 'write+spawn',
|
|
25
|
+
'trust-change': 'admin',
|
|
26
|
+
'topology-change': 'admin',
|
|
24
27
|
};
|
|
25
28
|
function policyThresholdForCategory(policy, category) {
|
|
26
29
|
switch (category) {
|
|
@@ -30,6 +33,10 @@ function policyThresholdForCategory(policy, category) {
|
|
|
30
33
|
return policy.minTrustLevelForMemory;
|
|
31
34
|
case 'spawn':
|
|
32
35
|
return policy.minTrustLevelForSpawn;
|
|
36
|
+
case 'admin':
|
|
37
|
+
return policy.minTrustLevelForAdmin;
|
|
38
|
+
case 'write+spawn':
|
|
39
|
+
return Math.max(policy.minTrustLevelForWrite, policy.minTrustLevelForSpawn);
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
const CLAIMS_FOR_MESSAGE_TYPE = {
|
|
@@ -197,6 +204,9 @@ export class PolicyEngine {
|
|
|
197
204
|
return 'send';
|
|
198
205
|
case 'agent-spawn':
|
|
199
206
|
return 'remote-spawn';
|
|
207
|
+
case 'trust-change':
|
|
208
|
+
case 'topology-change':
|
|
209
|
+
return 'admin-change';
|
|
200
210
|
case 'status-broadcast':
|
|
201
211
|
return 'status';
|
|
202
212
|
case 'heartbeat':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-engine.js","sourceRoot":"","sources":["../../src/application/policy-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"policy-engine.js","sourceRoot":"","sources":["../../src/application/policy-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation spend-reporting interface (ADR-097 Phase 3 upstream).
|
|
3
|
+
*
|
|
4
|
+
* The federation layer doesn't own model pricing — it can't know how much
|
|
5
|
+
* a downstream agent's work cost until the integrator tells it. So the
|
|
6
|
+
* coordinator exposes a `reportSpend()` method that callers invoke after
|
|
7
|
+
* their downstream completes, and that method fans out to:
|
|
8
|
+
*
|
|
9
|
+
* 1. The injected SpendReporter (if any) — typically wires to the
|
|
10
|
+
* cost-tracker bus / `federation-spend` memory namespace
|
|
11
|
+
* 2. The injected FederationBreakerService (if any) — its in-memory
|
|
12
|
+
* buffer becomes a transparent cache when the cost-tracker
|
|
13
|
+
* subscriber lands
|
|
14
|
+
*
|
|
15
|
+
* Both deps are constructor-optional. A coordinator with neither still
|
|
16
|
+
* accepts reportSpend() calls (silent no-op) so callers don't need to
|
|
17
|
+
* branch on which integrations are wired.
|
|
18
|
+
*
|
|
19
|
+
* Event shape matches the consumer contract pinned by
|
|
20
|
+
* plugins/ruflo-cost-tracker/scripts/federation.mjs:
|
|
21
|
+
*
|
|
22
|
+
* { peerId, taskId, tokensUsed, usdSpent, ts }
|
|
23
|
+
*
|
|
24
|
+
* Storage layout (consumer convention): namespace `federation-spend`,
|
|
25
|
+
* key `fed-spend-<peerId>-<ts>`. The interface is storage-agnostic; the
|
|
26
|
+
* default in-memory reporter included here is for tests + a reference
|
|
27
|
+
* implementation. Production integrators write a thin adapter that
|
|
28
|
+
* persists to ruflo memory / Redis / Datadog / their accounting system.
|
|
29
|
+
*/
|
|
30
|
+
/** A single per-send cost report from the integrator. */
|
|
31
|
+
export interface FederationSpendEvent {
|
|
32
|
+
/** Peer this cost was incurred against. */
|
|
33
|
+
readonly peerId: string;
|
|
34
|
+
/** Optional task correlation key — not all callers will have one. */
|
|
35
|
+
readonly taskId?: string;
|
|
36
|
+
/** Tokens consumed (input + output). Negative values clamped to 0 by sink. */
|
|
37
|
+
readonly tokensUsed: number;
|
|
38
|
+
/** USD spent. Negative values clamped to 0 by sink. */
|
|
39
|
+
readonly usdSpent: number;
|
|
40
|
+
/** ISO 8601 timestamp. Caller-supplied for testability; auto-filled if omitted by reportSpend. */
|
|
41
|
+
readonly ts: string;
|
|
42
|
+
/** Whether the underlying send succeeded (drives breaker failure-ratio). */
|
|
43
|
+
readonly success: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Strategy interface the coordinator calls when reportSpend() fires.
|
|
47
|
+
* Integrators implement this to push the event to whatever backend they
|
|
48
|
+
* want — cost-tracker bus, Datadog, accounting DB, etc.
|
|
49
|
+
*
|
|
50
|
+
* Implementations must be tolerant: dropping or persisting later is
|
|
51
|
+
* acceptable, but throwing here will surface to the integrator's caller.
|
|
52
|
+
* Buffer and retry inside the implementation.
|
|
53
|
+
*/
|
|
54
|
+
export interface SpendReporter {
|
|
55
|
+
reportSpend(event: FederationSpendEvent): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* In-memory reporter for tests + a reference implementation. Production
|
|
59
|
+
* code should wire a real SpendReporter that persists to durable storage.
|
|
60
|
+
*
|
|
61
|
+
* Buffer is unbounded — fine for tests, NOT fine for long-running
|
|
62
|
+
* production. The cost-tracker consumer reads from durable storage, not
|
|
63
|
+
* from this buffer.
|
|
64
|
+
*/
|
|
65
|
+
export declare class InMemorySpendReporter implements SpendReporter {
|
|
66
|
+
private readonly buffer;
|
|
67
|
+
reportSpend(event: FederationSpendEvent): Promise<void>;
|
|
68
|
+
/** Snapshot of all reported events (test inspection). */
|
|
69
|
+
getEvents(): readonly FederationSpendEvent[];
|
|
70
|
+
/** Drop everything (test cleanup). */
|
|
71
|
+
clear(): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Minimal memory-backend interface (ADR-110). The integrator wires this
|
|
75
|
+
* to whatever store satisfies their durability/consistency needs:
|
|
76
|
+
* ruflo memory, Redis, DynamoDB, file-backed JSON, etc. The federation
|
|
77
|
+
* plugin doesn't import any specific memory client — pluggable by
|
|
78
|
+
* design.
|
|
79
|
+
*/
|
|
80
|
+
export interface MemoryStore {
|
|
81
|
+
/** Persist a value under (namespace, key). Optional TTL in seconds. */
|
|
82
|
+
store(args: {
|
|
83
|
+
namespace: string;
|
|
84
|
+
key: string;
|
|
85
|
+
value: string;
|
|
86
|
+
ttl?: number;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/** Configuration for `MemorySpendReporter`. See ADR-110. */
|
|
90
|
+
export interface MemorySpendReporterConfig {
|
|
91
|
+
/** Memory store impl. Required. */
|
|
92
|
+
readonly memoryStore: MemoryStore;
|
|
93
|
+
/** Namespace per the cost-tracker consumer contract. Default: `federation-spend`. */
|
|
94
|
+
readonly namespace?: string;
|
|
95
|
+
/** Optional TTL in seconds. Default: 7 days. Cost-tracker rolling windows are 1h/24h/7d, so anything older is irrelevant. */
|
|
96
|
+
readonly ttlSeconds?: number;
|
|
97
|
+
}
|
|
98
|
+
/** Default namespace per cost-tracker's `plugins/ruflo-cost-tracker/scripts/federation.mjs`. */
|
|
99
|
+
export declare const DEFAULT_FEDERATION_SPEND_NAMESPACE = "federation-spend";
|
|
100
|
+
/** Default TTL: 7 days, matching cost-tracker's longest rolling window. */
|
|
101
|
+
export declare const DEFAULT_FEDERATION_SPEND_TTL_SECONDS: number;
|
|
102
|
+
/**
|
|
103
|
+
* Production reporter (ADR-110) that satisfies the cost-tracker
|
|
104
|
+
* consumer contract: writes to namespace `federation-spend`, key
|
|
105
|
+
* `fed-spend-<peerId>-<ts>`. The consumer
|
|
106
|
+
* (`plugins/ruflo-cost-tracker/scripts/federation.mjs`) `memory list`s
|
|
107
|
+
* the namespace, retrieves each key, and aggregates into rolling
|
|
108
|
+
* windows.
|
|
109
|
+
*
|
|
110
|
+
* Storage-agnostic: the integrator injects a `MemoryStore`
|
|
111
|
+
* implementation. Common wirings:
|
|
112
|
+
*
|
|
113
|
+
* // ruflo memory MCP
|
|
114
|
+
* new MemorySpendReporter({
|
|
115
|
+
* memoryStore: {
|
|
116
|
+
* store: async (a) => mcpClient.call('memory_store', a),
|
|
117
|
+
* },
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* // ruflo memory CLI shell-out
|
|
121
|
+
* new MemorySpendReporter({
|
|
122
|
+
* memoryStore: {
|
|
123
|
+
* store: async ({namespace, key, value, ttl}) => {
|
|
124
|
+
* await execFile('npx', ['ruflo', 'memory', 'store',
|
|
125
|
+
* '--namespace', namespace, '--key', key, '--value', value]);
|
|
126
|
+
* },
|
|
127
|
+
* },
|
|
128
|
+
* });
|
|
129
|
+
*
|
|
130
|
+
* Errors from the memory backend BUBBLE UP — the reporter does not
|
|
131
|
+
* retry/swallow. Wrap with the integrator's preferred retry strategy
|
|
132
|
+
* if needed.
|
|
133
|
+
*
|
|
134
|
+
* Negative `tokensUsed` / `usdSpent` are persisted as-is. Clamping is
|
|
135
|
+
* the breaker's responsibility (see federation-breaker-service.ts);
|
|
136
|
+
* the audit/spend log is an honest mirror of what the integrator
|
|
137
|
+
* reported.
|
|
138
|
+
*/
|
|
139
|
+
export declare class MemorySpendReporter implements SpendReporter {
|
|
140
|
+
private readonly memoryStore;
|
|
141
|
+
private readonly namespace;
|
|
142
|
+
private readonly ttlSeconds;
|
|
143
|
+
constructor(config: MemorySpendReporterConfig);
|
|
144
|
+
reportSpend(event: FederationSpendEvent): Promise<void>;
|
|
145
|
+
/** Read configured namespace (for the doctor/debug surface). */
|
|
146
|
+
getNamespace(): string;
|
|
147
|
+
/** Read configured TTL in seconds. */
|
|
148
|
+
getTtlSeconds(): number;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=spend-reporter.d.ts.map
|