@claude-flow/plugin-agent-federation 1.0.0-alpha.8 → 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.
- package/README.md +4 -0
- package/dist/a2a/agent-card.d.ts +130 -0
- package/dist/a2a/agent-card.d.ts.map +1 -0
- package/dist/a2a/agent-card.js +239 -0
- package/dist/a2a/agent-card.js.map +1 -0
- package/dist/a2a/consume.d.ts +34 -0
- package/dist/a2a/consume.d.ts.map +1 -0
- package/dist/a2a/consume.js +69 -0
- package/dist/a2a/consume.js.map +1 -0
- package/dist/a2a/planner-descriptor.d.ts +40 -0
- package/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/dist/a2a/planner-descriptor.js +100 -0
- package/dist/a2a/planner-descriptor.js.map +1 -0
- package/dist/a2a/well-known.d.ts +46 -0
- package/dist/a2a/well-known.d.ts.map +1 -0
- package/dist/a2a/well-known.js +84 -0
- package/dist/a2a/well-known.js.map +1 -0
- package/dist/application/claim-checker.d.ts +27 -0
- package/dist/application/claim-checker.d.ts.map +1 -0
- package/dist/application/claim-checker.js +46 -0
- package/dist/application/claim-checker.js.map +1 -0
- package/dist/application/federation-breaker-service.d.ts +11 -1
- package/dist/application/federation-breaker-service.d.ts.map +1 -1
- package/dist/application/federation-breaker-service.js +12 -10
- package/dist/application/federation-breaker-service.js.map +1 -1
- package/dist/application/federation-coordinator.d.ts +48 -3
- package/dist/application/federation-coordinator.d.ts.map +1 -1
- package/dist/application/federation-coordinator.js +111 -22
- package/dist/application/federation-coordinator.js.map +1 -1
- package/dist/application/inbound-dispatcher.d.ts +114 -0
- package/dist/application/inbound-dispatcher.d.ts.map +1 -0
- package/dist/application/inbound-dispatcher.js +356 -0
- package/dist/application/inbound-dispatcher.js.map +1 -0
- package/dist/application/spend-reporter.d.ts +77 -0
- package/dist/application/spend-reporter.d.ts.map +1 -1
- package/dist/application/spend-reporter.js +76 -0
- package/dist/application/spend-reporter.js.map +1 -1
- package/dist/application/trust-evaluator.d.ts +32 -0
- package/dist/application/trust-evaluator.d.ts.map +1 -1
- package/dist/application/trust-evaluator.js +47 -0
- package/dist/application/trust-evaluator.js.map +1 -1
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +125 -6
- package/dist/cli-commands.js.map +1 -1
- package/dist/domain/services/discovery-service.d.ts +18 -1
- package/dist/domain/services/discovery-service.d.ts.map +1 -1
- package/dist/domain/services/discovery-service.js +21 -0
- package/dist/domain/services/discovery-service.js.map +1 -1
- package/dist/domain/services/handshake-service.d.ts +10 -0
- package/dist/domain/services/handshake-service.d.ts.map +1 -1
- package/dist/domain/services/handshake-service.js +28 -0
- package/dist/domain/services/handshake-service.js.map +1 -1
- package/dist/domain/services/wg-firewall-service.d.ts +71 -0
- package/dist/domain/services/wg-firewall-service.d.ts.map +1 -0
- package/dist/domain/services/wg-firewall-service.js +194 -0
- package/dist/domain/services/wg-firewall-service.js.map +1 -0
- package/dist/domain/services/wg-mesh-service.d.ts +156 -0
- package/dist/domain/services/wg-mesh-service.d.ts.map +1 -0
- package/dist/domain/services/wg-mesh-service.js +292 -0
- package/dist/domain/services/wg-mesh-service.js.map +1 -0
- package/dist/domain/services/wg-witness-service.d.ts +98 -0
- package/dist/domain/services/wg-witness-service.d.ts.map +1 -0
- package/dist/domain/services/wg-witness-service.js +122 -0
- package/dist/domain/services/wg-witness-service.js.map +1 -0
- package/dist/domain/value-objects/wg-config.d.ts +70 -0
- package/dist/domain/value-objects/wg-config.d.ts.map +1 -0
- package/dist/domain/value-objects/wg-config.js +104 -0
- package/dist/domain/value-objects/wg-config.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts +3 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +114 -18
- package/dist/mcp-tools.js.map +1 -1
- package/dist/plugin.d.ts +3 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +260 -4
- package/dist/plugin.js.map +1 -1
- package/dist/transport/midstream-aware-loader.d.ts +72 -0
- package/dist/transport/midstream-aware-loader.d.ts.map +1 -0
- package/dist/transport/midstream-aware-loader.js +269 -0
- package/dist/transport/midstream-aware-loader.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +22 -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) ?? [];
|