@claude-flow/plugin-agent-federation 1.0.0-alpha.9 → 1.0.0

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 (92) hide show
  1. package/README.md +4 -0
  2. package/dist/a2a/agent-card.d.ts +130 -0
  3. package/dist/a2a/agent-card.d.ts.map +1 -0
  4. package/dist/a2a/agent-card.js +239 -0
  5. package/dist/a2a/agent-card.js.map +1 -0
  6. package/dist/a2a/consume.d.ts +34 -0
  7. package/dist/a2a/consume.d.ts.map +1 -0
  8. package/dist/a2a/consume.js +69 -0
  9. package/dist/a2a/consume.js.map +1 -0
  10. package/dist/a2a/planner-descriptor.d.ts +40 -0
  11. package/dist/a2a/planner-descriptor.d.ts.map +1 -0
  12. package/dist/a2a/planner-descriptor.js +100 -0
  13. package/dist/a2a/planner-descriptor.js.map +1 -0
  14. package/dist/a2a/well-known.d.ts +46 -0
  15. package/dist/a2a/well-known.d.ts.map +1 -0
  16. package/dist/a2a/well-known.js +84 -0
  17. package/dist/a2a/well-known.js.map +1 -0
  18. package/dist/application/claim-checker.d.ts +27 -0
  19. package/dist/application/claim-checker.d.ts.map +1 -0
  20. package/dist/application/claim-checker.js +46 -0
  21. package/dist/application/claim-checker.js.map +1 -0
  22. package/dist/application/federation-breaker-service.d.ts +11 -1
  23. package/dist/application/federation-breaker-service.d.ts.map +1 -1
  24. package/dist/application/federation-breaker-service.js +12 -10
  25. package/dist/application/federation-breaker-service.js.map +1 -1
  26. package/dist/application/federation-coordinator.d.ts +48 -3
  27. package/dist/application/federation-coordinator.d.ts.map +1 -1
  28. package/dist/application/federation-coordinator.js +111 -22
  29. package/dist/application/federation-coordinator.js.map +1 -1
  30. package/dist/application/inbound-dispatcher.d.ts +114 -0
  31. package/dist/application/inbound-dispatcher.d.ts.map +1 -0
  32. package/dist/application/inbound-dispatcher.js +356 -0
  33. package/dist/application/inbound-dispatcher.js.map +1 -0
  34. package/dist/application/spend-reporter.d.ts +77 -0
  35. package/dist/application/spend-reporter.d.ts.map +1 -1
  36. package/dist/application/spend-reporter.js +76 -0
  37. package/dist/application/spend-reporter.js.map +1 -1
  38. package/dist/application/trust-evaluator.d.ts +32 -0
  39. package/dist/application/trust-evaluator.d.ts.map +1 -1
  40. package/dist/application/trust-evaluator.js +47 -0
  41. package/dist/application/trust-evaluator.js.map +1 -1
  42. package/dist/bin.d.ts +1 -1
  43. package/dist/bin.js +2 -2
  44. package/dist/bin.js.map +1 -1
  45. package/dist/cli-commands.d.ts.map +1 -1
  46. package/dist/cli-commands.js +125 -6
  47. package/dist/cli-commands.js.map +1 -1
  48. package/dist/domain/services/discovery-service.d.ts +18 -1
  49. package/dist/domain/services/discovery-service.d.ts.map +1 -1
  50. package/dist/domain/services/discovery-service.js +21 -0
  51. package/dist/domain/services/discovery-service.js.map +1 -1
  52. package/dist/domain/services/handshake-service.d.ts +10 -0
  53. package/dist/domain/services/handshake-service.d.ts.map +1 -1
  54. package/dist/domain/services/handshake-service.js +28 -0
  55. package/dist/domain/services/handshake-service.js.map +1 -1
  56. package/dist/domain/services/wg-firewall-service.d.ts +71 -0
  57. package/dist/domain/services/wg-firewall-service.d.ts.map +1 -0
  58. package/dist/domain/services/wg-firewall-service.js +194 -0
  59. package/dist/domain/services/wg-firewall-service.js.map +1 -0
  60. package/dist/domain/services/wg-mesh-service.d.ts +156 -0
  61. package/dist/domain/services/wg-mesh-service.d.ts.map +1 -0
  62. package/dist/domain/services/wg-mesh-service.js +292 -0
  63. package/dist/domain/services/wg-mesh-service.js.map +1 -0
  64. package/dist/domain/services/wg-witness-service.d.ts +98 -0
  65. package/dist/domain/services/wg-witness-service.d.ts.map +1 -0
  66. package/dist/domain/services/wg-witness-service.js +122 -0
  67. package/dist/domain/services/wg-witness-service.js.map +1 -0
  68. package/dist/domain/value-objects/wg-config.d.ts +70 -0
  69. package/dist/domain/value-objects/wg-config.d.ts.map +1 -0
  70. package/dist/domain/value-objects/wg-config.js +104 -0
  71. package/dist/domain/value-objects/wg-config.js.map +1 -0
  72. package/dist/index.d.ts +8 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +9 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/mcp-tools.d.ts +3 -1
  77. package/dist/mcp-tools.d.ts.map +1 -1
  78. package/dist/mcp-tools.js +114 -18
  79. package/dist/mcp-tools.js.map +1 -1
  80. package/dist/plugin.d.ts +2 -1
  81. package/dist/plugin.d.ts.map +1 -1
  82. package/dist/plugin.js +173 -15
  83. package/dist/plugin.js.map +1 -1
  84. package/dist/transport/midstream-aware-loader.d.ts +72 -0
  85. package/dist/transport/midstream-aware-loader.d.ts.map +1 -0
  86. package/dist/transport/midstream-aware-loader.js +269 -0
  87. package/dist/transport/midstream-aware-loader.js.map +1 -0
  88. package/dist/version.d.ts +3 -0
  89. package/dist/version.d.ts.map +1 -0
  90. package/dist/version.js +3 -0
  91. package/dist/version.js.map +1 -0
  92. package/package.json +21 -5
@@ -0,0 +1,356 @@
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 { FederationNodeState } from '../domain/value-objects/federation-node-state.js';
22
+ /** What gets emitted on the event bus per messageType. */
23
+ export const FEDERATION_INBOUND_EVENT_PREFIX = 'federation:inbound';
24
+ export const JCS_SIGNATURE_PROTOCOL = 'ruflo-signature-jcs-v1';
25
+ /**
26
+ * Updated peers advertise and negotiate recursively complete JCS signatures
27
+ * without operator configuration. Legacy mode remains explicit for low-risk
28
+ * heartbeat/status interoperability only.
29
+ */
30
+ export const DEFAULT_ENVELOPE_SIGNATURE_MODE = 'prefer-jcs';
31
+ /**
32
+ * The historical serializer does not authenticate nested payload fields.
33
+ * Keep it available only for messages whose payload cannot mutate federation
34
+ * state or transfer data/ownership. Unknown types are intentionally denied.
35
+ */
36
+ const LEGACY_V1_LOW_RISK_MESSAGE_TYPES = new Set([
37
+ 'heartbeat',
38
+ 'status-broadcast',
39
+ ]);
40
+ export function isLegacyEnvelopeTypeAllowed(messageType) {
41
+ return LEGACY_V1_LOW_RISK_MESSAGE_TYPES.has(messageType);
42
+ }
43
+ /**
44
+ * Canonical serialization of an envelope for signing. This is the
45
+ * RFC 8785/JCS-compatible subset used by federation v1 while v2 schema
46
+ * negotiation is introduced. It recursively sorts object keys and rejects
47
+ * values outside the I-JSON-safe subset instead of silently dropping them.
48
+ * The `signature` field itself is excluded because it is what we verify.
49
+ *
50
+ * Federation messages are wrapped as `AgentMessage{id, type, payload,
51
+ * metadata}` on the wire. The `payload` is the actual FederationEnvelope
52
+ * (per `plugin.ts sendToNode`); we canonicalize the payload + the
53
+ * metadata so the receiver verifies the same bytes the sender signed.
54
+ */
55
+ function canonicalizeJcsValue(value, ancestors) {
56
+ if (value === null)
57
+ return 'null';
58
+ switch (typeof value) {
59
+ case 'boolean':
60
+ return value ? 'true' : 'false';
61
+ case 'string':
62
+ return JSON.stringify(value);
63
+ case 'number':
64
+ if (!Number.isFinite(value) || Object.is(value, -0)) {
65
+ throw new TypeError('Federation canonicalization rejects non-canonical numbers');
66
+ }
67
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
68
+ throw new TypeError('Federation canonicalization rejects unsafe integers');
69
+ }
70
+ return JSON.stringify(value);
71
+ case 'bigint':
72
+ case 'function':
73
+ case 'symbol':
74
+ case 'undefined':
75
+ throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
76
+ case 'object':
77
+ break;
78
+ default:
79
+ throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
80
+ }
81
+ const object = value;
82
+ if (ancestors.has(object)) {
83
+ throw new TypeError('Federation canonicalization rejects cyclic values');
84
+ }
85
+ ancestors.add(object);
86
+ try {
87
+ if (Array.isArray(value)) {
88
+ const items = [];
89
+ for (let index = 0; index < value.length; index += 1) {
90
+ if (!Object.prototype.hasOwnProperty.call(value, index)) {
91
+ throw new TypeError('Federation canonicalization rejects sparse arrays');
92
+ }
93
+ items.push(canonicalizeJcsValue(value[index], ancestors));
94
+ }
95
+ return `[${items.join(',')}]`;
96
+ }
97
+ const prototype = Object.getPrototypeOf(value);
98
+ if (prototype !== Object.prototype && prototype !== null) {
99
+ throw new TypeError('Federation canonicalization accepts only plain objects');
100
+ }
101
+ const record = value;
102
+ const entries = Object.keys(record)
103
+ .sort()
104
+ .map((key) => `${JSON.stringify(key)}:${canonicalizeJcsValue(record[key], ancestors)}`);
105
+ return `{${entries.join(',')}}`;
106
+ }
107
+ finally {
108
+ ancestors.delete(object);
109
+ }
110
+ }
111
+ function envelopeSignatureVersion(message) {
112
+ const version = message.metadata?.signatureVersion;
113
+ if (version === undefined)
114
+ return 'legacy-v1';
115
+ if (version === 'jcs-v1')
116
+ return version;
117
+ throw new TypeError(`Unsupported federation signature version: ${String(version)}`);
118
+ }
119
+ export function selectEnvelopeSignatureVersion(mode, peerProtocols, messageType) {
120
+ const selected = mode === 'legacy'
121
+ ? 'legacy-v1'
122
+ : peerProtocols.includes(JCS_SIGNATURE_PROTOCOL)
123
+ ? 'jcs-v1'
124
+ : mode === 'prefer-jcs'
125
+ ? 'legacy-v1'
126
+ : null;
127
+ if (selected === null) {
128
+ throw new Error('PEER_SIGNATURE_PROTOCOL_UNSUPPORTED');
129
+ }
130
+ if (selected === 'legacy-v1'
131
+ && messageType !== undefined
132
+ && !isLegacyEnvelopeTypeAllowed(messageType)) {
133
+ throw new Error(`PEER_SIGNATURE_PROTOCOL_UNSUPPORTED_FOR_MESSAGE: ${messageType}`);
134
+ }
135
+ return selected;
136
+ }
137
+ export function canonicalizeEnvelopeForVerify(message, requestedVersion) {
138
+ const meta = (message.metadata ?? {});
139
+ // Strip signature from metadata if present (we verify the rest)
140
+ const { signature: _sig, ...metaForSig } = meta;
141
+ const canon = {
142
+ id: message.id,
143
+ type: message.type,
144
+ payload: message.payload,
145
+ metadata: metaForSig,
146
+ };
147
+ const version = requestedVersion ?? envelopeSignatureVersion(message);
148
+ if (version === 'legacy-v1') {
149
+ // Byte-for-byte compatibility with releases through 1.0.0-alpha.16.
150
+ // Its recursive array replacer omits nested fields, so legacy messages
151
+ // cannot become privileged ownership mutations.
152
+ return JSON.stringify(canon, Object.keys(canon).sort());
153
+ }
154
+ return canonicalizeJcsValue(canon, new Set());
155
+ }
156
+ /**
157
+ * Process one received message. Pure-ish: no side effects beyond audit
158
+ * + event emission, both injected.
159
+ *
160
+ * `address` is the wire-level remote (e.g. `192.168.1.42:54321`).
161
+ * `message.metadata.sourceNodeId` is the cryptographic identity claim.
162
+ * The two MAY differ (e.g. behind NAT) — we trust `sourceNodeId` for
163
+ * peer lookup since it's bound to the Ed25519 keypair.
164
+ */
165
+ export async function dispatchInbound(address, message, deps) {
166
+ const meta = (message.metadata ?? {});
167
+ const sourceNodeId = typeof meta.sourceNodeId === 'string' ? meta.sourceNodeId : null;
168
+ if (!sourceNodeId) {
169
+ await deps.audit.log('message_rejected', {
170
+ sourceNodeId: undefined,
171
+ metadata: { address, reason: 'MISSING_METADATA' },
172
+ });
173
+ deps.logger.warn(`Inbound rejected: no sourceNodeId in metadata (from ${address})`);
174
+ return { accepted: false, reason: 'MISSING_METADATA' };
175
+ }
176
+ const peer = deps.discovery.getPeer(sourceNodeId);
177
+ if (!peer) {
178
+ await deps.audit.log('message_rejected', {
179
+ sourceNodeId,
180
+ metadata: { address, reason: 'PEER_UNKNOWN' },
181
+ });
182
+ deps.logger.warn(`Inbound rejected: ${sourceNodeId} not in discovery (from ${address})`);
183
+ return { accepted: false, reason: 'PEER_UNKNOWN' };
184
+ }
185
+ if (peer.state === FederationNodeState.SUSPENDED) {
186
+ await deps.audit.log('message_rejected', {
187
+ sourceNodeId,
188
+ metadata: { address, reason: 'PEER_SUSPENDED' },
189
+ });
190
+ return { accepted: false, reason: 'PEER_SUSPENDED' };
191
+ }
192
+ if (peer.state === FederationNodeState.EVICTED) {
193
+ await deps.audit.log('message_rejected', {
194
+ sourceNodeId,
195
+ metadata: { address, reason: 'PEER_EVICTED' },
196
+ });
197
+ return { accepted: false, reason: 'PEER_EVICTED' };
198
+ }
199
+ let signatureVersion;
200
+ let canonicalEnvelope;
201
+ try {
202
+ signatureVersion = envelopeSignatureVersion(message);
203
+ const accepted = deps.acceptedSignatureVersions ?? ['legacy-v1', 'jcs-v1'];
204
+ if (!accepted.includes(signatureVersion)) {
205
+ throw new TypeError(`Signature version ${signatureVersion} is disabled`);
206
+ }
207
+ canonicalEnvelope = canonicalizeEnvelopeForVerify(message, signatureVersion);
208
+ }
209
+ catch {
210
+ await deps.audit.log('message_rejected', {
211
+ sourceNodeId,
212
+ metadata: { address, reason: 'INVALID_PAYLOAD' },
213
+ });
214
+ deps.logger.warn(`Inbound rejected: non-canonical payload from ${sourceNodeId} (addr=${address})`);
215
+ return { accepted: false, reason: 'INVALID_PAYLOAD' };
216
+ }
217
+ // Cryptographic signature verification (closes the trust gate that
218
+ // peer-state checks alone don't provide — without this, a malicious
219
+ // sender could just claim sourceNodeId='known-peer' in metadata and
220
+ // pass the previous gates).
221
+ if (deps.verifyEnvelope) {
222
+ const sig = typeof meta.signature === 'string' ? meta.signature : null;
223
+ let ok = false;
224
+ try {
225
+ ok = deps.verifyEnvelope(canonicalEnvelope, sig, peer.publicKey);
226
+ }
227
+ catch {
228
+ await deps.audit.log('message_rejected', {
229
+ sourceNodeId,
230
+ metadata: { address, reason: 'INVALID_PAYLOAD' },
231
+ });
232
+ deps.logger.warn(`Inbound rejected: non-canonical payload from ${sourceNodeId} (addr=${address})`);
233
+ return { accepted: false, reason: 'INVALID_PAYLOAD' };
234
+ }
235
+ if (!ok) {
236
+ await deps.audit.log('message_rejected', {
237
+ sourceNodeId,
238
+ metadata: { address, reason: 'INVALID_SIGNATURE' },
239
+ });
240
+ deps.logger.warn(`Inbound rejected: bad signature from ${sourceNodeId} (addr=${address})`);
241
+ return { accepted: false, reason: 'INVALID_SIGNATURE' };
242
+ }
243
+ }
244
+ // The legacy serializer omits nested fields. No policy grant can make those
245
+ // unauthenticated bytes safe for a consequential operation, so this gate is
246
+ // independent of authorization compatibility mode.
247
+ if (signatureVersion === 'legacy-v1' &&
248
+ !isLegacyEnvelopeTypeAllowed(message.type)) {
249
+ await deps.audit.log('message_rejected', {
250
+ sourceNodeId,
251
+ metadata: {
252
+ address,
253
+ reason: 'LEGACY_SIGNATURE_TYPE_REJECTED',
254
+ messageType: message.type,
255
+ },
256
+ });
257
+ deps.logger.warn(`Inbound rejected: legacy-v1 is not permitted for message type ${message.type} ` +
258
+ `from ${sourceNodeId}`);
259
+ return { accepted: false, reason: 'LEGACY_SIGNATURE_TYPE_REJECTED' };
260
+ }
261
+ const authorizationMode = deps.authorizationMode ?? 'legacy';
262
+ if (authorizationMode !== 'legacy' &&
263
+ authorizationMode !== 'observe' &&
264
+ authorizationMode !== 'enforce') {
265
+ await deps.audit.log('message_rejected', {
266
+ sourceNodeId,
267
+ metadata: { address, reason: 'AUTHORIZATION_ERROR' },
268
+ });
269
+ return { accepted: false, reason: 'AUTHORIZATION_ERROR' };
270
+ }
271
+ let authorizationDecision;
272
+ if (deps.authorizeInbound) {
273
+ try {
274
+ const decision = await deps.authorizeInbound({
275
+ address,
276
+ sourceNodeId,
277
+ messageType: message.type,
278
+ message,
279
+ peer,
280
+ signatureVersion,
281
+ messageSizeBytes: new TextEncoder().encode(canonicalEnvelope).byteLength,
282
+ });
283
+ if (!decision || typeof decision.allowed !== 'boolean') {
284
+ throw new TypeError('Inbound authorization evaluator returned an invalid decision');
285
+ }
286
+ authorizationDecision = decision;
287
+ }
288
+ catch {
289
+ if (authorizationMode === 'enforce') {
290
+ await deps.audit.log('message_rejected', {
291
+ sourceNodeId,
292
+ metadata: { address, reason: 'AUTHORIZATION_ERROR', messageType: message.type },
293
+ });
294
+ deps.logger.warn(`Inbound authorization failed for ${sourceNodeId} (addr=${address})`);
295
+ return { accepted: false, reason: 'AUTHORIZATION_ERROR' };
296
+ }
297
+ deps.logger.warn(`Inbound authorization observation failed for ${sourceNodeId} (mode=${authorizationMode})`);
298
+ }
299
+ }
300
+ else if (authorizationMode === 'enforce') {
301
+ await deps.audit.log('message_rejected', {
302
+ sourceNodeId,
303
+ metadata: { address, reason: 'AUTHORIZATION_ERROR', messageType: message.type },
304
+ });
305
+ deps.logger.warn('Inbound rejected: enforce mode has no authorization evaluator');
306
+ return { accepted: false, reason: 'AUTHORIZATION_ERROR' };
307
+ }
308
+ if (authorizationDecision && !authorizationDecision.allowed) {
309
+ if (authorizationMode === 'enforce') {
310
+ await deps.audit.log('message_rejected', {
311
+ sourceNodeId,
312
+ metadata: {
313
+ address,
314
+ reason: 'AUTHORIZATION_DENIED',
315
+ messageType: message.type,
316
+ policyReason: authorizationDecision.reason,
317
+ },
318
+ });
319
+ deps.logger.warn(`Inbound authorization denied for ${sourceNodeId} (addr=${address})`);
320
+ return { accepted: false, reason: 'AUTHORIZATION_DENIED' };
321
+ }
322
+ deps.logger.warn(`Inbound authorization would deny ${message.type} from ${sourceNodeId} ` +
323
+ `(mode=${authorizationMode})`);
324
+ }
325
+ // Touch lastSeen on every successful inbound — drives the
326
+ // discovery service's stale-peer detection.
327
+ peer.markSeen();
328
+ // Audit accepted delivery
329
+ await deps.audit.log('message_received', {
330
+ sourceNodeId,
331
+ metadata: {
332
+ address,
333
+ messageType: message.type,
334
+ messageId: message.id,
335
+ authorizationMode,
336
+ authorizationAllowed: authorizationDecision?.allowed,
337
+ },
338
+ });
339
+ // Emit typed event for the integrator
340
+ // Examples: federation:inbound:task, federation:inbound:memory-query
341
+ const eventName = `${FEDERATION_INBOUND_EVENT_PREFIX}:${message.type}`;
342
+ try {
343
+ deps.eventBus.emit(eventName, {
344
+ address,
345
+ sourceNodeId,
346
+ message,
347
+ peer,
348
+ });
349
+ }
350
+ catch (err) {
351
+ // EventBus throw should not crash the receive loop.
352
+ deps.logger.warn(`EventBus emit failed for ${eventName}: ${err instanceof Error ? err.message : err}`);
353
+ }
354
+ return { accepted: true, sourceNodeId, messageType: message.type };
355
+ }
356
+ //# sourceMappingURL=inbound-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-dispatcher.js","sourceRoot":"","sources":["../../src/application/inbound-dispatcher.ts"],"names":[],"mappings":"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"}
@@ -70,4 +70,81 @@ export declare class InMemorySpendReporter implements SpendReporter {
70
70
  /** Drop everything (test cleanup). */
71
71
  clear(): void;
72
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
+ }
73
150
  //# sourceMappingURL=spend-reporter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spend-reporter.d.ts","sourceRoot":"","sources":["../../src/application/spend-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IAE/C,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D,yDAAyD;IACzD,SAAS,IAAI,SAAS,oBAAoB,EAAE;IAI5C,sCAAsC;IACtC,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"spend-reporter.d.ts","sourceRoot":"","sources":["../../src/application/spend-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IAE/C,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D,yDAAyD;IACzD,SAAS,IAAI,SAAS,oBAAoB,EAAE;IAI5C,sCAAsC;IACtC,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,KAAK,CAAC,IAAI,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,4DAA4D;AAC5D,MAAM,WAAW,yBAAyB;IACxC,mCAAmC;IACnC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,qFAAqF;IACrF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,6HAA6H;IAC7H,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gGAAgG;AAChG,eAAO,MAAM,kCAAkC,qBAAqB,CAAC;AAErE,2EAA2E;AAC3E,eAAO,MAAM,oCAAoC,QAAmB,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,yBAAyB;IAMvC,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7D,gEAAgE;IAChE,YAAY,IAAI,MAAM;IAItB,sCAAsC;IACtC,aAAa,IAAI,MAAM;CAGxB"}
@@ -49,4 +49,80 @@ export class InMemorySpendReporter {
49
49
  this.buffer.length = 0;
50
50
  }
51
51
  }
52
+ /** Default namespace per cost-tracker's `plugins/ruflo-cost-tracker/scripts/federation.mjs`. */
53
+ export const DEFAULT_FEDERATION_SPEND_NAMESPACE = 'federation-spend';
54
+ /** Default TTL: 7 days, matching cost-tracker's longest rolling window. */
55
+ export const DEFAULT_FEDERATION_SPEND_TTL_SECONDS = 7 * 24 * 60 * 60;
56
+ /**
57
+ * Production reporter (ADR-110) that satisfies the cost-tracker
58
+ * consumer contract: writes to namespace `federation-spend`, key
59
+ * `fed-spend-<peerId>-<ts>`. The consumer
60
+ * (`plugins/ruflo-cost-tracker/scripts/federation.mjs`) `memory list`s
61
+ * the namespace, retrieves each key, and aggregates into rolling
62
+ * windows.
63
+ *
64
+ * Storage-agnostic: the integrator injects a `MemoryStore`
65
+ * implementation. Common wirings:
66
+ *
67
+ * // ruflo memory MCP
68
+ * new MemorySpendReporter({
69
+ * memoryStore: {
70
+ * store: async (a) => mcpClient.call('memory_store', a),
71
+ * },
72
+ * });
73
+ *
74
+ * // ruflo memory CLI shell-out
75
+ * new MemorySpendReporter({
76
+ * memoryStore: {
77
+ * store: async ({namespace, key, value, ttl}) => {
78
+ * await execFile('npx', ['ruflo', 'memory', 'store',
79
+ * '--namespace', namespace, '--key', key, '--value', value]);
80
+ * },
81
+ * },
82
+ * });
83
+ *
84
+ * Errors from the memory backend BUBBLE UP — the reporter does not
85
+ * retry/swallow. Wrap with the integrator's preferred retry strategy
86
+ * if needed.
87
+ *
88
+ * Negative `tokensUsed` / `usdSpent` are persisted as-is. Clamping is
89
+ * the breaker's responsibility (see federation-breaker-service.ts);
90
+ * the audit/spend log is an honest mirror of what the integrator
91
+ * reported.
92
+ */
93
+ export class MemorySpendReporter {
94
+ memoryStore;
95
+ namespace;
96
+ ttlSeconds;
97
+ constructor(config) {
98
+ this.memoryStore = config.memoryStore;
99
+ this.namespace = config.namespace ?? DEFAULT_FEDERATION_SPEND_NAMESPACE;
100
+ this.ttlSeconds = config.ttlSeconds ?? DEFAULT_FEDERATION_SPEND_TTL_SECONDS;
101
+ }
102
+ async reportSpend(event) {
103
+ const key = `fed-spend-${event.peerId}-${event.ts}`;
104
+ const value = JSON.stringify({
105
+ peerId: event.peerId,
106
+ taskId: event.taskId ?? null,
107
+ tokensUsed: event.tokensUsed,
108
+ usdSpent: event.usdSpent,
109
+ success: event.success,
110
+ ts: event.ts,
111
+ });
112
+ await this.memoryStore.store({
113
+ namespace: this.namespace,
114
+ key,
115
+ value,
116
+ ttl: this.ttlSeconds,
117
+ });
118
+ }
119
+ /** Read configured namespace (for the doctor/debug surface). */
120
+ getNamespace() {
121
+ return this.namespace;
122
+ }
123
+ /** Read configured TTL in seconds. */
124
+ getTtlSeconds() {
125
+ return this.ttlSeconds;
126
+ }
127
+ }
52
128
  //# sourceMappingURL=spend-reporter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spend-reporter.js","sourceRoot":"","sources":["../../src/application/spend-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+BH;;;;;;;GAOG;AACH,MAAM,OAAO,qBAAqB;IACf,MAAM,GAA2B,EAAE,CAAC;IAErD,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,yDAAyD;IACzD,SAAS;QACP,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,sCAAsC;IACtC,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
1
+ {"version":3,"file":"spend-reporter.js","sourceRoot":"","sources":["../../src/application/spend-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+BH;;;;;;;GAOG;AACH,MAAM,OAAO,qBAAqB;IACf,MAAM,GAA2B,EAAE,CAAC;IAErD,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,yDAAyD;IACzD,SAAS;QACP,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,sCAAsC;IACtC,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;CACF;AA6BD,gGAAgG;AAChG,MAAM,CAAC,MAAM,kCAAkC,GAAG,kBAAkB,CAAC;AAErE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,OAAO,mBAAmB;IACb,WAAW,CAAc;IACzB,SAAS,CAAS;IAClB,UAAU,CAAS;IAEpC,YAAY,MAAiC;QAC3C,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACtC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,kCAAkC,CAAC;QACxE,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,oCAAoC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,MAAM,GAAG,GAAG,aAAa,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;YAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;YAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,EAAE,EAAE,KAAK,CAAC,EAAE;SACb,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;YAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,GAAG;YACH,KAAK;YACL,GAAG,EAAE,IAAI,CAAC,UAAU;SACrB,CAAC,CAAC;IACL,CAAC;IAED,gEAAgE;IAChE,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,sCAAsC;IACtC,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF"}
@@ -19,6 +19,21 @@ export type ImmediateDowngradeReason = 'repeated-threat-detection' | 'hmac-verif
19
19
  export interface TrustEvaluatorDeps {
20
20
  onTrustChange?: (nodeId: string, result: TrustTransitionResult) => void;
21
21
  }
22
+ /**
23
+ * Audit record emitted by `bootstrapElevate`. Captures the bypass intent,
24
+ * the operator-supplied reason, and the before/after trust levels so that
25
+ * downstream audit consumers can flag bootstrap elevations distinctly from
26
+ * organic trust transitions. See ADR-164 §3.5.4.
27
+ */
28
+ export interface BootstrapElevationAuditEntry {
29
+ readonly tag: 'bootstrap_elevation';
30
+ readonly nodeId: string;
31
+ readonly previousLevel: TrustLevel;
32
+ readonly newLevel: TrustLevel;
33
+ readonly reason: string;
34
+ readonly timestamp: string;
35
+ readonly operatorBypass: true;
36
+ }
22
37
  export interface ThreatWindow {
23
38
  readonly detections: Date[];
24
39
  readonly windowMs: number;
@@ -34,6 +49,23 @@ export declare class TrustEvaluator {
34
49
  };
35
50
  evaluateTransition(node: FederationNode, metrics: SessionMetrics, uptimeRatio: number, hasInstitutionalAttestation?: boolean): TrustTransitionResult | null;
36
51
  downgrade(node: FederationNode, reason: ImmediateDowngradeReason): TrustTransitionResult;
52
+ /**
53
+ * Founder-bootstrap trust elevation (ADR-164 §3.5.4).
54
+ *
55
+ * Bypasses the organic trust-accrual thresholds (minInteractions: 500 for
56
+ * 2→3, etc.) so a freshly-joined BBS peer can be hand-promoted to
57
+ * TRUSTED on Day 1, before its interaction count has accrued. This is an
58
+ * operator escape hatch — every invocation MUST be recorded as a special
59
+ * `bootstrap_elevation` audit entry so it is distinguishable from organic
60
+ * upgrades. `reason` MUST be a non-empty operator-supplied string.
61
+ *
62
+ * The caller is responsible for refusing to invoke this method when the
63
+ * target node is not a registered federation peer; this function trusts
64
+ * its inputs and only performs the elevation + audit construction.
65
+ *
66
+ * @returns audit entry tagged `bootstrap_elevation` ready to be persisted.
67
+ */
68
+ bootstrapElevate(node: FederationNode, newLevel: TrustLevel, reason: string): BootstrapElevationAuditEntry;
37
69
  recordThreatDetection(nodeId: string): boolean;
38
70
  private checkUpgrade;
39
71
  private checkDowngrade;
@@ -1 +1 @@
1
- {"version":3,"file":"trust-evaluator.d.ts","sourceRoot":"","sources":["../../src/application/trust-evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA+B,MAAM,mCAAmC,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAE/E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;CACzC;AAED,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,2BAA2B,GAC3B,wBAAwB,CAAC;AAE7B,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACzE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,IAAI,GAAE,kBAAuB;IAKzC,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,oBAAoB,CAAA;KAAE;IA8B/G,kBAAkB,CAChB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,2BAA2B,GAAE,OAAe,GAC3C,qBAAqB,GAAG,IAAI;IA4C/B,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,wBAAwB,GAAG,qBAAqB;IAmBxF,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAY9C,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,oBAAoB;CAK7B"}
1
+ {"version":3,"file":"trust-evaluator.d.ts","sourceRoot":"","sources":["../../src/application/trust-evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA+B,MAAM,mCAAmC,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAE/E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;CACzC;AAED,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,2BAA2B,GAC3B,wBAAwB,CAAC;AAE7B,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACzE;AAED;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,EAAE,qBAAqB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,IAAI,GAAE,kBAAuB;IAKzC,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,oBAAoB,CAAA;KAAE;IA8B/G,kBAAkB,CAChB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,2BAA2B,GAAE,OAAe,GAC3C,qBAAqB,GAAG,IAAI;IA4C/B,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,wBAAwB,GAAG,qBAAqB;IAmBxF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CACd,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,MAAM,GACb,4BAA4B;IAoC/B,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAY9C,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,oBAAoB;CAK7B"}
@@ -84,6 +84,53 @@ export class TrustEvaluator {
84
84
  this.deps.onTrustChange?.(node.nodeId, result);
85
85
  return result;
86
86
  }
87
+ /**
88
+ * Founder-bootstrap trust elevation (ADR-164 §3.5.4).
89
+ *
90
+ * Bypasses the organic trust-accrual thresholds (minInteractions: 500 for
91
+ * 2→3, etc.) so a freshly-joined BBS peer can be hand-promoted to
92
+ * TRUSTED on Day 1, before its interaction count has accrued. This is an
93
+ * operator escape hatch — every invocation MUST be recorded as a special
94
+ * `bootstrap_elevation` audit entry so it is distinguishable from organic
95
+ * upgrades. `reason` MUST be a non-empty operator-supplied string.
96
+ *
97
+ * The caller is responsible for refusing to invoke this method when the
98
+ * target node is not a registered federation peer; this function trusts
99
+ * its inputs and only performs the elevation + audit construction.
100
+ *
101
+ * @returns audit entry tagged `bootstrap_elevation` ready to be persisted.
102
+ */
103
+ bootstrapElevate(node, newLevel, reason) {
104
+ if (typeof reason !== 'string' || reason.trim().length === 0) {
105
+ throw new Error('bootstrapElevate requires a non-empty reason');
106
+ }
107
+ if (newLevel !== TrustLevel.VERIFIED &&
108
+ newLevel !== TrustLevel.ATTESTED &&
109
+ newLevel !== TrustLevel.TRUSTED &&
110
+ newLevel !== TrustLevel.PRIVILEGED) {
111
+ throw new Error(`bootstrapElevate target level must be VERIFIED..PRIVILEGED, got ${newLevel}`);
112
+ }
113
+ const previousLevel = node.trustLevel;
114
+ node.updateTrustLevel(newLevel);
115
+ const result = {
116
+ previousLevel,
117
+ newLevel,
118
+ score: 0,
119
+ components: { successRate: 0, uptime: 0, threatPenalty: 0, dataIntegrityScore: 0 },
120
+ reason: `Bootstrap elevation: ${reason}`,
121
+ requiresHumanApproval: false,
122
+ };
123
+ this.deps.onTrustChange?.(node.nodeId, result);
124
+ return {
125
+ tag: 'bootstrap_elevation',
126
+ nodeId: node.nodeId,
127
+ previousLevel,
128
+ newLevel,
129
+ reason: reason.trim(),
130
+ timestamp: new Date().toISOString(),
131
+ operatorBypass: true,
132
+ };
133
+ }
87
134
  recordThreatDetection(nodeId) {
88
135
  const now = new Date();
89
136
  const detections = this.threatWindows.get(nodeId) ?? [];