@claude-flow/plugin-agent-federation 1.0.4 → 1.0.6

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.
Files changed (40) hide show
  1. package/dist/a2a/agent-card.d.ts +130 -0
  2. package/dist/a2a/consume.d.ts +34 -0
  3. package/dist/a2a/planner-descriptor.d.ts +40 -0
  4. package/dist/a2a/well-known.d.ts +46 -0
  5. package/dist/application/claim-checker.d.ts +27 -0
  6. package/dist/application/federation-breaker-service.d.ts +192 -0
  7. package/dist/application/federation-coordinator.d.ts +211 -0
  8. package/dist/application/inbound-dispatcher.d.ts +113 -0
  9. package/dist/application/policy-engine.d.ts +42 -0
  10. package/dist/application/policy-engine.d.ts.map +1 -1
  11. package/dist/application/policy-engine.js +11 -1
  12. package/dist/application/policy-engine.js.map +1 -1
  13. package/dist/application/spend-reporter.d.ts +150 -0
  14. package/dist/application/trust-evaluator.d.ts +74 -0
  15. package/dist/bin.d.ts +13 -0
  16. package/dist/cli-commands.d.ts +7 -0
  17. package/dist/domain/entities/federation-envelope.d.ts +45 -0
  18. package/dist/domain/entities/federation-node.d.ts +104 -0
  19. package/dist/domain/entities/federation-session.d.ts +55 -0
  20. package/dist/domain/entities/trust-level.d.ts +17 -0
  21. package/dist/domain/entities/trust-level.d.ts.map +1 -1
  22. package/dist/domain/entities/trust-level.js +9 -2
  23. package/dist/domain/entities/trust-level.js.map +1 -1
  24. package/dist/domain/services/audit-service.d.ts +69 -0
  25. package/dist/domain/services/discovery-service.d.ts +76 -0
  26. package/dist/domain/services/handshake-service.d.ts +62 -0
  27. package/dist/domain/services/pii-pipeline-service.d.ts +65 -0
  28. package/dist/domain/services/routing-service.d.ts +54 -0
  29. package/dist/domain/services/wg-firewall-service.d.ts +71 -0
  30. package/dist/domain/services/wg-mesh-service.d.ts +156 -0
  31. package/dist/domain/services/wg-witness-service.d.ts +98 -0
  32. package/dist/domain/value-objects/federation-budget.d.ts +89 -0
  33. package/dist/domain/value-objects/federation-node-state.d.ts +92 -0
  34. package/dist/domain/value-objects/wg-config.d.ts +70 -0
  35. package/dist/index.d.ts +24 -0
  36. package/dist/mcp-tools.d.ts +10 -0
  37. package/dist/plugin.d.ts +19 -0
  38. package/dist/transport/midstream-aware-loader.d.ts +72 -0
  39. package/dist/version.d.ts +3 -0
  40. package/package.json +2 -2
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A2A (Agent2Agent, Linux Foundation) Agent Card adapter — cards only.
3
+ *
4
+ * Maps ruflo's bespoke federation identity (`FederationManifest` /
5
+ * `FederationNode`) to and from a spec-compliant A2A Agent Card so ruflo
6
+ * nodes are discoverable by A2A peers and A2A peers appear in federation
7
+ * discovery. This is an ADAPTER over the existing federation schema, not a
8
+ * rewrite — A2A Tasks/messaging are deliberately out of scope.
9
+ *
10
+ * Spec: A2A Protocol 1.0.0 — https://a2a-protocol.org/latest/specification/
11
+ * (schema source of truth: specification/a2a.proto in a2aproject/A2A).
12
+ * Required AgentCard fields per spec §4.4.1: name, description,
13
+ * supportedInterfaces, version, capabilities, defaultInputModes,
14
+ * defaultOutputModes, skills.
15
+ */
16
+ import { FederationNode } from '../domain/entities/federation-node.js';
17
+ import type { FederationManifest } from '../domain/services/discovery-service.js';
18
+ /** A2A protocol version this adapter targets. */
19
+ export declare const A2A_PROTOCOL_VERSION = "1.0";
20
+ /**
21
+ * Well-known URI for Agent Card discovery (A2A 1.0 §8.2 + IANA registration
22
+ * §14). NOTE: earlier drafts used `/.well-known/agent.json`; 0.3.0+ settled
23
+ * on `agent-card.json`.
24
+ */
25
+ export declare const A2A_WELL_KNOWN_PATH = "/.well-known/agent-card.json";
26
+ /**
27
+ * Open-form protocol binding string advertising ruflo's federation wire
28
+ * (Ed25519-signed envelopes over WebSocket/QUIC). Spec §4.4.6 explicitly
29
+ * allows non-core bindings: "This is an open form string, to be easily
30
+ * extended for other protocol bindings."
31
+ */
32
+ export declare const RUFLO_FEDERATION_BINDING = "RUFLO-FEDERATION";
33
+ /**
34
+ * Extension URI carrying the bespoke federation identity inside the card
35
+ * (spec §4.6 AgentExtension). Enables lossless round-trip in fromAgentCard.
36
+ */
37
+ export declare const RUFLO_FEDERATION_EXTENSION_URI = "urn:ruflo:federation:manifest:v1";
38
+ export interface A2AAgentInterface {
39
+ readonly url: string;
40
+ readonly protocolBinding: string;
41
+ readonly protocolVersion: string;
42
+ readonly tenant?: string;
43
+ }
44
+ export interface A2AAgentSkill {
45
+ readonly id: string;
46
+ readonly name: string;
47
+ readonly description: string;
48
+ readonly tags: readonly string[];
49
+ readonly examples?: readonly string[];
50
+ readonly inputModes?: readonly string[];
51
+ readonly outputModes?: readonly string[];
52
+ }
53
+ export interface A2AAgentExtension {
54
+ readonly uri: string;
55
+ readonly description?: string;
56
+ readonly required?: boolean;
57
+ readonly params?: Record<string, unknown>;
58
+ }
59
+ export interface A2AAgentCapabilities {
60
+ readonly streaming?: boolean;
61
+ readonly pushNotifications?: boolean;
62
+ readonly extensions?: readonly A2AAgentExtension[];
63
+ readonly extendedAgentCard?: boolean;
64
+ }
65
+ export interface A2AAgentProvider {
66
+ readonly url: string;
67
+ readonly organization: string;
68
+ }
69
+ export interface A2AAgentCard {
70
+ readonly name: string;
71
+ readonly description: string;
72
+ readonly supportedInterfaces: readonly A2AAgentInterface[];
73
+ readonly version: string;
74
+ readonly capabilities: A2AAgentCapabilities;
75
+ readonly defaultInputModes: readonly string[];
76
+ readonly defaultOutputModes: readonly string[];
77
+ readonly skills: readonly A2AAgentSkill[];
78
+ readonly provider?: A2AAgentProvider;
79
+ readonly documentationUrl?: string;
80
+ readonly iconUrl?: string;
81
+ readonly securitySchemes?: Record<string, unknown>;
82
+ readonly securityRequirements?: readonly Record<string, readonly string[]>[];
83
+ readonly signatures?: readonly {
84
+ protected: string;
85
+ signature: string;
86
+ }[];
87
+ }
88
+ export interface ToAgentCardOptions {
89
+ /** Human-readable agent name; defaults to `ruflo-federation/<nodeId>`. */
90
+ readonly name?: string;
91
+ /** Human-readable description override. */
92
+ readonly description?: string;
93
+ readonly provider?: A2AAgentProvider;
94
+ readonly documentationUrl?: string;
95
+ /**
96
+ * Extra spec-core interfaces to advertise ahead of the federation binding
97
+ * (e.g. a JSONRPC endpoint once A2A messaging lands). First entry is the
98
+ * preferred interface per spec.
99
+ */
100
+ readonly additionalInterfaces?: readonly A2AAgentInterface[];
101
+ }
102
+ /**
103
+ * Generate a spec-compliant A2A Agent Card from a ruflo federation manifest.
104
+ * Every agent type becomes an AgentSkill; the federation endpoint becomes a
105
+ * `RUFLO-FEDERATION` interface; the bespoke identity (nodeId, publicKey,
106
+ * complianceModes, manifest signature) rides in a spec §4.6 extension so
107
+ * `fromAgentCard` can restore it.
108
+ */
109
+ export declare function toAgentCard(manifest: FederationManifest, options?: ToAgentCardOptions): A2AAgentCard;
110
+ export interface AgentCardValidation {
111
+ readonly valid: boolean;
112
+ readonly errors: readonly string[];
113
+ }
114
+ /**
115
+ * Structural validation against the A2A 1.0 AgentCard REQUIRED fields.
116
+ * Intentionally strict on required shape, lenient on unknown extra fields
117
+ * (forward compatibility with future spec minors).
118
+ */
119
+ export declare function validateAgentCard(value: unknown): AgentCardValidation;
120
+ /**
121
+ * Map a validated remote A2A Agent Card into the bespoke federation registry
122
+ * shape (a `FederationNode`) so A2A peers appear in federation discovery.
123
+ *
124
+ * Trust posture: A2A cards are self-describing metadata, NOT authenticated
125
+ * federation manifests — consumed peers always enter at
126
+ * `TrustLevel.UNTRUSTED` regardless of embedded identity claims. Trust is
127
+ * earned afterwards through the normal handshake + TrustEvaluator path.
128
+ */
129
+ export declare function fromAgentCard(card: A2AAgentCard, sourceUrl?: string): FederationNode;
130
+ //# sourceMappingURL=agent-card.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A2A Agent Card consumption — fetch + validate a remote card and map it
3
+ * into the bespoke federation registry so A2A peers appear in federation
4
+ * discovery. Cards only (no A2A Tasks/messaging in this iteration).
5
+ */
6
+ import type { DiscoveryService } from '../domain/services/discovery-service.js';
7
+ import type { FederationNode } from '../domain/entities/federation-node.js';
8
+ import { type A2AAgentCard } from './agent-card.js';
9
+ export interface FetchAgentCardOptions {
10
+ /** Injectable fetch for tests / restricted environments. Default: globalThis.fetch. */
11
+ readonly fetchImpl?: typeof fetch;
12
+ readonly timeoutMs?: number;
13
+ /** Response size cap in bytes (defense against decompression floods). Default 256 KiB. */
14
+ readonly maxBytes?: number;
15
+ }
16
+ export interface FetchAgentCardResult {
17
+ readonly card: A2AAgentCard;
18
+ readonly sourceUrl: string;
19
+ }
20
+ /**
21
+ * Resolve the card URL: a bare base URL (`https://host[:port]` or a path-less
22
+ * URL) gets the A2A well-known path appended; anything already pointing at a
23
+ * JSON document is used as-is.
24
+ */
25
+ export declare function resolveAgentCardUrl(baseOrCardUrl: string): string;
26
+ /** Fetch and structurally validate a remote A2A Agent Card. */
27
+ export declare function fetchAgentCard(baseOrCardUrl: string, options?: FetchAgentCardOptions): Promise<FetchAgentCardResult>;
28
+ /**
29
+ * Fetch a remote Agent Card and register the peer in federation discovery.
30
+ * The peer enters at TrustLevel.UNTRUSTED (see fromAgentCard) — the card is
31
+ * self-asserted metadata, and trust is earned through the normal handshake.
32
+ */
33
+ export declare function consumeAgentCard(discovery: DiscoveryService, baseOrCardUrl: string, options?: FetchAgentCardOptions): Promise<FederationNode>;
34
+ //# sourceMappingURL=consume.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { type A2AAgentCard } from './agent-card.js';
2
+ /**
3
+ * Host-owned planner semantics for a registered capability identifier.
4
+ * Remote registration metadata may select an identifier, but never supplies
5
+ * these planner-visible strings directly.
6
+ */
7
+ export interface PlannerCapabilityDefinition {
8
+ readonly id: string;
9
+ readonly functionality: string;
10
+ readonly inputSpecification: string;
11
+ readonly outputSpecification: string;
12
+ readonly usageConstraints: readonly string[];
13
+ }
14
+ export interface PlannerDescriptorPolicy {
15
+ /** Resolve only identifiers the host has explicitly approved for planning. */
16
+ readonly resolveCapability: (canonicalSkillId: string) => PlannerCapabilityDefinition | undefined;
17
+ readonly maximumCapabilities?: number;
18
+ }
19
+ export interface PlannerSafeAgentDescriptor {
20
+ readonly schema: 'ruflo/planner-safe-agent-descriptor/v1';
21
+ readonly source: 'a2a-agent-card';
22
+ readonly capabilities: readonly PlannerCapabilityDefinition[];
23
+ readonly authority: 'none';
24
+ }
25
+ export interface PlannerDescriptorProjection {
26
+ readonly descriptor: PlannerSafeAgentDescriptor;
27
+ readonly rejectedSkillCount: number;
28
+ readonly duplicateSkillCount: number;
29
+ }
30
+ /**
31
+ * Project a third-party A2A Agent Card into planner-visible metadata.
32
+ *
33
+ * Security boundary: card descriptions, skill descriptions, examples, tags,
34
+ * provider text, documentation URLs, extension parameters, and all other
35
+ * remote prose are deliberately absent from the return type. Only a bounded
36
+ * canonical skill identifier may cross the registration boundary, and its
37
+ * planner semantics come from a host-controlled resolver.
38
+ */
39
+ export declare function projectPlannerSafeAgentCard(card: A2AAgentCard, policy: PlannerDescriptorPolicy): PlannerDescriptorProjection;
40
+ //# sourceMappingURL=planner-descriptor.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A2A well-known Agent Card endpoint (A2A 1.0 §8.2 discovery + §8.6 caching).
3
+ *
4
+ * Serves `GET /.well-known/agent-card.json` from a minimal `node:http`
5
+ * server. Security posture follows ADR-166 (mcp-bridge unauthenticated-RCE
6
+ * remediation): binds 127.0.0.1 by default and REFUSES a non-loopback bind
7
+ * unless the caller passes `allowNonLoopback: true` explicitly. The endpoint
8
+ * is read-only (GET/HEAD on one exact path), exposes only the public card,
9
+ * and is entirely opt-in — nothing here weakens the federation transport's
10
+ * existing bind or auth behavior.
11
+ */
12
+ import { type A2AAgentCard } from './agent-card.js';
13
+ export interface AgentCardServerOptions {
14
+ /** Card supplier — re-invoked per request so a late-published manifest is picked up. */
15
+ readonly getCard: () => A2AAgentCard | null;
16
+ readonly port: number;
17
+ /** Bind host. Default 127.0.0.1 (ADR-166 posture). */
18
+ readonly host?: string;
19
+ /** Serve path. Default A2A_WELL_KNOWN_PATH. */
20
+ readonly path?: string;
21
+ /**
22
+ * Explicit opt-in required to bind anything other than loopback. Without
23
+ * it a non-loopback host is rejected at start (fail-closed).
24
+ */
25
+ readonly allowNonLoopback?: boolean;
26
+ /** Cache-Control max-age seconds (spec §8.6.1). Default 300. */
27
+ readonly cacheMaxAgeSeconds?: number;
28
+ }
29
+ export interface AgentCardServerHandle {
30
+ readonly url: string;
31
+ readonly host: string;
32
+ readonly port: number;
33
+ close(): Promise<void>;
34
+ }
35
+ export declare function isLoopbackHost(host: string): boolean;
36
+ /**
37
+ * Start the well-known Agent Card HTTP endpoint.
38
+ *
39
+ * Responses:
40
+ * GET/HEAD <path> → 200 application/json (+ Cache-Control, ETag per §8.6)
41
+ * or 503 when no card is available yet
42
+ * other method → 405
43
+ * other path → 404
44
+ */
45
+ export declare function startAgentCardServer(options: AgentCardServerOptions): Promise<AgentCardServerHandle>;
46
+ //# sourceMappingURL=well-known.d.ts.map
@@ -0,0 +1,27 @@
1
+ import type { FederationClaimType, PolicyEngineDeps } from './policy-engine.js';
2
+ export type FederationAuthorizationMode = 'legacy' | 'observe' | 'enforce';
3
+ export interface FederationClaimCheckerConfig {
4
+ mode?: FederationAuthorizationMode;
5
+ grantedClaims?: readonly string[];
6
+ onObservation?: (claim: FederationClaimType, granted: boolean) => void;
7
+ }
8
+ export interface FederationClaimChecker {
9
+ mode: FederationAuthorizationMode;
10
+ grantedClaims: ReadonlySet<FederationClaimType>;
11
+ checkClaim: PolicyEngineDeps['checkClaim'];
12
+ }
13
+ /**
14
+ * Compatibility bridge for the legacy federation policy engine.
15
+ *
16
+ * This removes the anonymous `() => true` production stub and makes
17
+ * compatibility behavior explicit:
18
+ * - legacy: preserve pre-ADR-325 behavior;
19
+ * - observe: calculate and report missing grants without blocking;
20
+ * - enforce: default deny unless the exact claim is configured.
21
+ *
22
+ * ADR-324 policy adapters can supply the `grantedClaims` set after evaluating
23
+ * the request; ownership-changing federation messages remain disabled in the
24
+ * default message policy until the full ingress PEP is composed.
25
+ */
26
+ export declare function createFederationClaimChecker(config?: FederationClaimCheckerConfig): FederationClaimChecker;
27
+ //# sourceMappingURL=claim-checker.d.ts.map
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Federation circuit breaker service (ADR-097 Phase 2.b).
3
+ *
4
+ * Phase 2.a shipped the FederationNode state machine + canTransition table.
5
+ * This service is what drives transitions: it records per-peer send outcomes
6
+ * (success/failure + spend), evaluates thresholds against the configured
7
+ * policy, and calls suspend()/evict()/reactivate() on the entity.
8
+ *
9
+ * Architecture is two-layer to keep the policy testable in isolation:
10
+ *
11
+ * 1. **evaluatePolicy** (pure function) — given rolling samples for a peer
12
+ * + the policy + now, returns a BreakerDecision describing what should
13
+ * happen. No I/O, no clock, no entity mutation.
14
+ * 2. **FederationBreakerService** — owns the rolling-sample buffer,
15
+ * wraps evaluatePolicy with the entity mutation step, and exposes
16
+ * `recordOutcome` (called by the coordinator on each send completion)
17
+ * + `tick` (called periodically to drive cooldown/auto-evict).
18
+ *
19
+ * The cost-tracker integration (Phase 3 upstream — emits federation_spend
20
+ * events when sends complete) is *not* wired here; this service holds its
21
+ * own in-memory rolling buffer. When Phase 3 upstream lands, the consumer
22
+ * can call `recordOutcome` from the bus subscriber and the buffer becomes
23
+ * a transparent cache. This keeps Phase 2 deliverable without a hard
24
+ * cost-tracker dependency.
25
+ *
26
+ * Security invariants:
27
+ *
28
+ * 1. Negative tokens/usd are clamped to 0 — a malicious receive-side
29
+ * reporter cannot drive an "unsuspend" through credit inflation.
30
+ * 2. Sample buffer is bounded per-peer (DEFAULT_MAX_SAMPLES_PER_PEER) so
31
+ * a high-throughput peer cannot exhaust memory.
32
+ * 3. Policy evaluation is order-independent for SUSPEND triggers —
33
+ * cost OR failure-ratio fires the same SUSPEND. The reason field
34
+ * records which one tripped.
35
+ * 4. evaluatePolicy is pure & deterministic given (samples, policy, now)
36
+ * — no Date.now() calls inside, no random IDs, no hidden state.
37
+ * 5. Auto-eviction only runs from SUSPENDED state and only after the
38
+ * grace age. Cannot skip SUSPENDED for an ACTIVE → EVICTED jump.
39
+ */
40
+ import { FederationNode } from '../domain/entities/federation-node.js';
41
+ import { SuspensionReason } from '../domain/value-objects/federation-node-state.js';
42
+ /** A single send outcome recorded by the coordinator. */
43
+ export interface SendOutcome {
44
+ readonly nodeId: string;
45
+ readonly success: boolean;
46
+ /** Tokens consumed by this leg. Negative values are treated as 0. */
47
+ readonly tokensUsed?: number;
48
+ /** USD spent on this leg. Negative values are treated as 0. */
49
+ readonly usdSpent?: number;
50
+ /** Caller-supplied timestamp (for testability). Defaults to recordOutcome's `now`. */
51
+ readonly at?: Date;
52
+ }
53
+ /** Configurable thresholds. All defaults come from ADR-097 Part 2. */
54
+ export interface BreakerPolicy {
55
+ /** 24h spend cap per peer. Default $5.00. */
56
+ readonly costSuspensionUsd: number;
57
+ /** Window for the cost cap. Default 24h. */
58
+ readonly costWindowMs: number;
59
+ /** Failure ratio that trips the breaker. Default 0.5 (50%). */
60
+ readonly failureRatioThreshold: number;
61
+ /** Window for failure-ratio computation. Default 1h. */
62
+ readonly failureWindowMs: number;
63
+ /** Minimum samples in the failure window before evaluating (anti-noise). Default 10. */
64
+ readonly failureMinSamples: number;
65
+ /** Cooldown before SUSPENDED → reactivate-eligible. Default 30min. */
66
+ readonly cooldownMs: number;
67
+ /** Continuous suspension age before auto-evict. Default 24h. */
68
+ readonly autoEvictionAgeMs: number;
69
+ }
70
+ export declare const DEFAULT_BREAKER_POLICY: BreakerPolicy;
71
+ /** Per-peer sample buffer cap. ~24h of 1-Hz traffic. */
72
+ export declare const DEFAULT_MAX_SAMPLES_PER_PEER = 100000;
73
+ /** A normalized, time-stamped outcome (negatives clamped, `at` resolved). */
74
+ interface NormalizedSample {
75
+ readonly success: boolean;
76
+ readonly tokensUsed: number;
77
+ readonly usdSpent: number;
78
+ readonly at: Date;
79
+ }
80
+ /**
81
+ * What the breaker decided for a peer at evaluation time. The service then
82
+ * applies the action (calls node.suspend()/evict()/reactivate()) but the
83
+ * decision itself is a pure return so tests can assert it without checking
84
+ * mutated state.
85
+ */
86
+ export type BreakerDecision = {
87
+ readonly action: 'NONE';
88
+ readonly nodeId: string;
89
+ } | {
90
+ readonly action: 'SUSPEND';
91
+ readonly nodeId: string;
92
+ readonly reason: SuspensionReason;
93
+ } | {
94
+ readonly action: 'EVICT';
95
+ readonly nodeId: string;
96
+ readonly reason: SuspensionReason;
97
+ } | {
98
+ readonly action: 'REACTIVATE_ELIGIBLE';
99
+ readonly nodeId: string;
100
+ };
101
+ /**
102
+ * Pure policy evaluator. Returns the decision the breaker should apply.
103
+ * No I/O, no clock, no entity mutation — given the same inputs, returns the
104
+ * same output. The service layer is responsible for calling node.suspend()
105
+ * / evict() / reactivate() based on this.
106
+ *
107
+ * Decision rules:
108
+ *
109
+ * For ACTIVE peers:
110
+ * - 24h cost > policy.costSuspensionUsd → SUSPEND/COST_THRESHOLD_EXCEEDED
111
+ * - 1h failure ratio > policy.failureRatioThreshold (≥ failureMinSamples
112
+ * in window) → SUSPEND/FAILURE_RATIO_EXCEEDED
113
+ * - else NONE
114
+ *
115
+ * For SUSPENDED peers:
116
+ * - suspended ≥ autoEvictionAgeMs → EVICT/GRACE_PERIOD_EXPIRED
117
+ * - cooldown elapsed → REACTIVATE_ELIGIBLE (caller probes + reactivates)
118
+ * - else NONE
119
+ *
120
+ * For EVICTED peers: always NONE (terminal under breaker flow).
121
+ *
122
+ * Cost takes priority over failure-ratio when both trip, because cost is
123
+ * the more conservative signal (a malicious peer can manufacture failures
124
+ * by trickling errors but cannot manufacture spend without actually
125
+ * burning budget).
126
+ */
127
+ export declare function evaluatePolicy(samples: readonly NormalizedSample[], node: FederationNode, policy: BreakerPolicy, now: Date): BreakerDecision;
128
+ /**
129
+ * Stateful service: owns the per-peer sample buffer, applies decisions to
130
+ * entities, and exposes the breaker control surface to the coordinator.
131
+ *
132
+ * Construct once per federation coordinator. Inject the policy if you need
133
+ * non-default thresholds (Phase 4 doctor surface will let users tune them).
134
+ */
135
+ /**
136
+ * ADR-111 Phase 3 — callback invoked after the breaker applies a successful
137
+ * state transition. Used by the coordinator to propagate suspends to the
138
+ * WG mesh layer (clearing AllowedIPs). Not invoked for `apply=false` dry runs
139
+ * or `NONE` / `REACTIVATE_ELIGIBLE` decisions. Errors thrown from the callback
140
+ * are swallowed at the breaker — the entity transition has already happened,
141
+ * and side-effect failures shouldn't unwind the breaker's internal state.
142
+ */
143
+ export type BreakerTransitionListener = (node: FederationNode, decision: BreakerDecision) => void | Promise<void>;
144
+ export declare class FederationBreakerService {
145
+ private readonly policy;
146
+ private readonly samples;
147
+ private readonly maxSamplesPerPeer;
148
+ private readonly onTransition?;
149
+ constructor(policy?: BreakerPolicy, maxSamplesPerPeer?: number, onTransition?: BreakerTransitionListener);
150
+ /** Read the active policy (handy for tests + doctor surface). */
151
+ getPolicy(): BreakerPolicy;
152
+ /**
153
+ * Record a send outcome for a peer. Called by the coordinator after each
154
+ * `routing.send` completion. Returns a BreakerDecision describing what,
155
+ * if anything, the breaker would do for this peer right now — but does
156
+ * NOT apply it (the caller asks `evaluate(node)` if they want the action
157
+ * to actually happen). This split lets the coordinator decide whether to
158
+ * apply on every outcome or batch via tick.
159
+ *
160
+ * Negative tokens/usd are clamped to 0. A peer cannot inflate its own
161
+ * remaining cost ceiling by reporting credits.
162
+ */
163
+ recordOutcome(outcome: SendOutcome, now?: Date): void;
164
+ /**
165
+ * Evaluate the breaker for one peer and apply the resulting transition
166
+ * to the entity. Returns the decision so callers can observe the outcome.
167
+ *
168
+ * `apply` defaults to true — set to false to get the dry-run decision
169
+ * without mutating the entity (useful for the doctor surface).
170
+ */
171
+ evaluate(node: FederationNode, now?: Date, apply?: boolean): BreakerDecision;
172
+ /**
173
+ * Periodic tick — evaluate all known peers. Intended to be called from a
174
+ * timer or after each batch of outcomes. Returns the decisions for
175
+ * observability.
176
+ */
177
+ tick(nodes: readonly FederationNode[], now?: Date): BreakerDecision[];
178
+ /** Drop a peer's sample buffer (e.g. after reactivate or evict). */
179
+ forget(nodeId: string): void;
180
+ /**
181
+ * Snapshot of per-peer aggregates for the doctor surface. Pure read —
182
+ * does not mutate the buffer.
183
+ */
184
+ snapshot(nodeId: string, now?: Date): {
185
+ readonly nodeId: string;
186
+ readonly sampleCount: number;
187
+ readonly cumUsdInWindow: number;
188
+ readonly failureRatioInWindow: number | null;
189
+ };
190
+ }
191
+ export {};
192
+ //# sourceMappingURL=federation-breaker-service.d.ts.map