@highway1/core 0.1.62 → 0.1.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +420 -234
- package/dist/index.js +1011 -624
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,8 +3,6 @@ import { base58btc } from 'multiformats/bases/base58';
|
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { encode, decode } from 'cbor-x';
|
|
5
5
|
import Ajv from 'ajv';
|
|
6
|
-
import lunr from 'lunr';
|
|
7
|
-
import Fuse from 'fuse.js';
|
|
8
6
|
import { Level } from 'level';
|
|
9
7
|
import { sha256 } from '@noble/hashes/sha256';
|
|
10
8
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
@@ -146,6 +144,112 @@ async function verifyMessage(signedMessage, expectedPublicKey) {
|
|
|
146
144
|
}
|
|
147
145
|
}
|
|
148
146
|
|
|
147
|
+
// src/identity/aliases.ts
|
|
148
|
+
function validateAliasName(alias) {
|
|
149
|
+
if (!alias || alias.length === 0) {
|
|
150
|
+
return { valid: false, error: "Alias cannot be empty" };
|
|
151
|
+
}
|
|
152
|
+
if (alias.length > 32) {
|
|
153
|
+
return { valid: false, error: "Alias must be 32 characters or less" };
|
|
154
|
+
}
|
|
155
|
+
if (alias.startsWith("did:")) {
|
|
156
|
+
return { valid: false, error: 'Alias cannot start with "did:" (reserved for DID prefix)' };
|
|
157
|
+
}
|
|
158
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(alias)) {
|
|
159
|
+
return { valid: false, error: "Alias must be lowercase alphanumeric with hyphens, starting with alphanumeric" };
|
|
160
|
+
}
|
|
161
|
+
return { valid: true };
|
|
162
|
+
}
|
|
163
|
+
function resolveDid(input, aliases) {
|
|
164
|
+
if (!input) {
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
if (input.startsWith("did:")) {
|
|
168
|
+
return input;
|
|
169
|
+
}
|
|
170
|
+
const did = aliases[input];
|
|
171
|
+
if (did) {
|
|
172
|
+
return did;
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function reverseAlias(did, aliases) {
|
|
177
|
+
for (const [alias, aliasDid] of Object.entries(aliases)) {
|
|
178
|
+
if (aliasDid === did) {
|
|
179
|
+
return alias;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
function formatDidWithAlias(did, aliases) {
|
|
185
|
+
const alias = reverseAlias(did, aliases);
|
|
186
|
+
return alias || did;
|
|
187
|
+
}
|
|
188
|
+
function formatDidWithAliasDetailed(did, aliases, truncate = false) {
|
|
189
|
+
const alias = reverseAlias(did, aliases);
|
|
190
|
+
if (alias) {
|
|
191
|
+
if (truncate && did.length > 40) {
|
|
192
|
+
const truncated = did.substring(0, 20) + "..." + did.substring(did.length - 10);
|
|
193
|
+
return `${alias} (${truncated})`;
|
|
194
|
+
}
|
|
195
|
+
return `${alias} (${did})`;
|
|
196
|
+
}
|
|
197
|
+
if (truncate && did.length > 40) {
|
|
198
|
+
return did.substring(0, 20) + "..." + did.substring(did.length - 10);
|
|
199
|
+
}
|
|
200
|
+
return did;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/identity/document.ts
|
|
204
|
+
function createDIDDocument(publicKey, services) {
|
|
205
|
+
const did = deriveDID(publicKey);
|
|
206
|
+
const keyId = `${did}#key-1`;
|
|
207
|
+
const publicKeyMultibase = `z${Buffer.from(publicKey).toString("hex")}`;
|
|
208
|
+
return {
|
|
209
|
+
"@context": [
|
|
210
|
+
"https://www.w3.org/ns/did/v1",
|
|
211
|
+
"https://w3id.org/security/suites/ed25519-2020/v1"
|
|
212
|
+
],
|
|
213
|
+
id: did,
|
|
214
|
+
verificationMethod: [
|
|
215
|
+
{
|
|
216
|
+
id: keyId,
|
|
217
|
+
type: "Ed25519VerificationKey2020",
|
|
218
|
+
controller: did,
|
|
219
|
+
publicKeyMultibase
|
|
220
|
+
}
|
|
221
|
+
],
|
|
222
|
+
authentication: [keyId],
|
|
223
|
+
assertionMethod: [keyId],
|
|
224
|
+
service: services
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function validateDIDDocument(doc) {
|
|
228
|
+
if (typeof doc !== "object" || doc === null) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const d = doc;
|
|
232
|
+
return Array.isArray(d["@context"]) && typeof d.id === "string" && d.id.startsWith("did:clawiverse:") && Array.isArray(d.verificationMethod) && d.verificationMethod.length > 0 && Array.isArray(d.authentication) && Array.isArray(d.assertionMethod);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/identity/index.ts
|
|
236
|
+
async function generateAnonymousIdentity() {
|
|
237
|
+
const keyPair = await generateKeyPair();
|
|
238
|
+
const exported = exportKeyPair(keyPair);
|
|
239
|
+
const did = deriveDID(keyPair.publicKey);
|
|
240
|
+
const didSuffix = did.split(":").pop().slice(-8);
|
|
241
|
+
return {
|
|
242
|
+
did,
|
|
243
|
+
publicKey: exported.publicKey,
|
|
244
|
+
privateKey: exported.privateKey,
|
|
245
|
+
agentCard: {
|
|
246
|
+
name: `Agent-${didSuffix}`,
|
|
247
|
+
description: `Anonymous agent ${didSuffix}`,
|
|
248
|
+
capabilities: []
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
149
253
|
// src/utils/logger.ts
|
|
150
254
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
151
255
|
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
@@ -198,8 +302,9 @@ var DEFAULT_RECONNECT = {
|
|
|
198
302
|
stableAfterMs: 6e4
|
|
199
303
|
};
|
|
200
304
|
function createRelayClient(config) {
|
|
201
|
-
const { relayUrls, did, keyPair
|
|
305
|
+
const { relayUrls, did, keyPair } = config;
|
|
202
306
|
const reconnectConfig = { ...DEFAULT_RECONNECT, ...config.reconnect };
|
|
307
|
+
let currentCard = config.card;
|
|
203
308
|
const connections = relayUrls.map((url) => ({
|
|
204
309
|
url,
|
|
205
310
|
ws: null,
|
|
@@ -228,13 +333,13 @@ function createRelayClient(config) {
|
|
|
228
333
|
logger.info("WebSocket connected", { url: conn.url });
|
|
229
334
|
try {
|
|
230
335
|
const timestamp = Date.now();
|
|
231
|
-
const helloData = encode({ did, card, timestamp });
|
|
336
|
+
const helloData = encode({ did, card: currentCard, timestamp });
|
|
232
337
|
const signature = await sign(helloData, keyPair.privateKey);
|
|
233
338
|
const hello = {
|
|
234
339
|
type: "HELLO",
|
|
235
340
|
protocolVersion: 1,
|
|
236
341
|
did,
|
|
237
|
-
card,
|
|
342
|
+
card: currentCard,
|
|
238
343
|
timestamp,
|
|
239
344
|
signature
|
|
240
345
|
};
|
|
@@ -445,7 +550,7 @@ function createRelayClient(config) {
|
|
|
445
550
|
ws.off("message", handler);
|
|
446
551
|
resolve(msg.agents);
|
|
447
552
|
}
|
|
448
|
-
} catch
|
|
553
|
+
} catch {
|
|
449
554
|
}
|
|
450
555
|
};
|
|
451
556
|
ws.on("message", handler);
|
|
@@ -470,13 +575,49 @@ function createRelayClient(config) {
|
|
|
470
575
|
ws.off("message", handler);
|
|
471
576
|
resolve(msg.card);
|
|
472
577
|
}
|
|
473
|
-
} catch
|
|
578
|
+
} catch {
|
|
474
579
|
}
|
|
475
580
|
};
|
|
476
581
|
ws.on("message", handler);
|
|
477
582
|
ws.send(encode({ type: "FETCH_CARD", did: did2 }));
|
|
478
583
|
});
|
|
479
584
|
},
|
|
585
|
+
async queryTrust(target, domain, since, cursor) {
|
|
586
|
+
const conn = getConnectedConnection();
|
|
587
|
+
const ws = conn?.ws;
|
|
588
|
+
if (!conn || !ws) {
|
|
589
|
+
throw new TransportError("No connected relay");
|
|
590
|
+
}
|
|
591
|
+
return new Promise((resolve, reject) => {
|
|
592
|
+
const timeout = setTimeout(() => {
|
|
593
|
+
reject(new TransportError("Trust query timeout"));
|
|
594
|
+
}, 1e4);
|
|
595
|
+
const handler = (data) => {
|
|
596
|
+
try {
|
|
597
|
+
const msg = decode(data);
|
|
598
|
+
if (msg.type === "TRUST_RESULT" && msg.target === target) {
|
|
599
|
+
clearTimeout(timeout);
|
|
600
|
+
ws.off("message", handler);
|
|
601
|
+
resolve(msg);
|
|
602
|
+
}
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
ws.on("message", handler);
|
|
607
|
+
ws.send(encode({ type: "TRUST_QUERY", target, domain, since, cursor }));
|
|
608
|
+
});
|
|
609
|
+
},
|
|
610
|
+
async publishCard(card) {
|
|
611
|
+
if (card) {
|
|
612
|
+
currentCard = card;
|
|
613
|
+
}
|
|
614
|
+
await sendToRelay({ type: "PUBLISH_CARD", card });
|
|
615
|
+
logger.debug("Sent PUBLISH_CARD");
|
|
616
|
+
},
|
|
617
|
+
async unpublishCard() {
|
|
618
|
+
await sendToRelay({ type: "UNPUBLISH_CARD" });
|
|
619
|
+
logger.debug("Sent UNPUBLISH_CARD");
|
|
620
|
+
},
|
|
480
621
|
onDeliver(handler) {
|
|
481
622
|
deliveryHandler = handler;
|
|
482
623
|
},
|
|
@@ -818,490 +959,63 @@ function createRelayIndexOperations(client) {
|
|
|
818
959
|
}
|
|
819
960
|
};
|
|
820
961
|
}
|
|
821
|
-
var logger3 = createLogger("search-index");
|
|
822
|
-
var SearchIndex = class {
|
|
823
|
-
cards = /* @__PURE__ */ new Map();
|
|
824
|
-
lunrIndex;
|
|
825
|
-
fuse;
|
|
826
|
-
needsRebuild = false;
|
|
827
|
-
/**
|
|
828
|
-
* Add or update an Agent Card in the index
|
|
829
|
-
*/
|
|
830
|
-
indexAgentCard(card) {
|
|
831
|
-
this.cards.set(card.did, card);
|
|
832
|
-
this.needsRebuild = true;
|
|
833
|
-
logger3.debug("Indexed Agent Card", { did: card.did, capabilities: card.capabilities.length });
|
|
834
|
-
}
|
|
835
|
-
/**
|
|
836
|
-
* Remove an Agent Card from the index
|
|
837
|
-
*/
|
|
838
|
-
removeAgentCard(did) {
|
|
839
|
-
this.cards.delete(did);
|
|
840
|
-
this.needsRebuild = true;
|
|
841
|
-
logger3.debug("Removed Agent Card from index", { did });
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Search for agents matching a query
|
|
845
|
-
*/
|
|
846
|
-
search(query) {
|
|
847
|
-
if (this.needsRebuild) {
|
|
848
|
-
this.rebuild();
|
|
849
|
-
}
|
|
850
|
-
let results = [];
|
|
851
|
-
if (query.text && this.lunrIndex) {
|
|
852
|
-
results = this.searchByText(query.text);
|
|
853
|
-
} else if (query.capability && this.fuse) {
|
|
854
|
-
results = this.searchByCapability(query.capability);
|
|
855
|
-
} else {
|
|
856
|
-
results = Array.from(this.cards.values()).map((card) => ({
|
|
857
|
-
card,
|
|
858
|
-
score: 1
|
|
859
|
-
}));
|
|
860
|
-
}
|
|
861
|
-
if (query.filters) {
|
|
862
|
-
results = this.applyFilters(results, query.filters);
|
|
863
|
-
}
|
|
864
|
-
results.sort((a, b) => b.score - a.score);
|
|
865
|
-
if (query.limit) {
|
|
866
|
-
results = results.slice(0, query.limit);
|
|
867
|
-
}
|
|
868
|
-
logger3.debug("Search completed", { query, results: results.length });
|
|
869
|
-
return results;
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Get all indexed cards
|
|
873
|
-
*/
|
|
874
|
-
getAllCards() {
|
|
875
|
-
return Array.from(this.cards.values());
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* Clear the index
|
|
879
|
-
*/
|
|
880
|
-
clear() {
|
|
881
|
-
this.cards.clear();
|
|
882
|
-
this.lunrIndex = void 0;
|
|
883
|
-
this.fuse = void 0;
|
|
884
|
-
this.needsRebuild = false;
|
|
885
|
-
logger3.info("Search index cleared");
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Get index size
|
|
889
|
-
*/
|
|
890
|
-
size() {
|
|
891
|
-
return this.cards.size;
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Rebuild search indexes
|
|
895
|
-
*/
|
|
896
|
-
rebuild() {
|
|
897
|
-
logger3.info("Rebuilding search indexes", { cards: this.cards.size });
|
|
898
|
-
const cards = Array.from(this.cards.values());
|
|
899
|
-
this.lunrIndex = lunr(function() {
|
|
900
|
-
this.ref("did");
|
|
901
|
-
this.field("name", { boost: 10 });
|
|
902
|
-
this.field("description", { boost: 5 });
|
|
903
|
-
this.field("capabilities");
|
|
904
|
-
for (const card of cards) {
|
|
905
|
-
this.add({
|
|
906
|
-
did: card.did,
|
|
907
|
-
name: card.name,
|
|
908
|
-
description: card.description,
|
|
909
|
-
capabilities: card.capabilities.map((cap) => `${cap.name} ${cap.description}`).join(" ")
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
this.fuse = new Fuse(Array.from(this.cards.values()), {
|
|
914
|
-
keys: [
|
|
915
|
-
{ name: "name", weight: 0.3 },
|
|
916
|
-
{ name: "description", weight: 0.2 },
|
|
917
|
-
{ name: "capabilities.name", weight: 0.3 },
|
|
918
|
-
{ name: "capabilities.description", weight: 0.2 }
|
|
919
|
-
],
|
|
920
|
-
threshold: 0.4,
|
|
921
|
-
includeScore: true
|
|
922
|
-
});
|
|
923
|
-
this.needsRebuild = false;
|
|
924
|
-
logger3.info("Search indexes rebuilt");
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Search by text using Lunr
|
|
928
|
-
*/
|
|
929
|
-
searchByText(text) {
|
|
930
|
-
if (!this.lunrIndex) return [];
|
|
931
|
-
const lunrResults = this.lunrIndex.search(text);
|
|
932
|
-
return lunrResults.map((result) => ({
|
|
933
|
-
card: this.cards.get(result.ref),
|
|
934
|
-
score: result.score
|
|
935
|
-
}));
|
|
936
|
-
}
|
|
937
|
-
/**
|
|
938
|
-
* Search by capability using Fuse
|
|
939
|
-
*/
|
|
940
|
-
searchByCapability(capability) {
|
|
941
|
-
if (!this.fuse) return [];
|
|
942
|
-
const fuseResults = this.fuse.search(capability);
|
|
943
|
-
return fuseResults.map((result) => ({
|
|
944
|
-
card: result.item,
|
|
945
|
-
score: 1 - (result.score || 0)
|
|
946
|
-
// Fuse score is distance, convert to similarity
|
|
947
|
-
}));
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Apply filters to search results
|
|
951
|
-
*/
|
|
952
|
-
applyFilters(results, filters) {
|
|
953
|
-
return results.filter((result) => {
|
|
954
|
-
const { card } = result;
|
|
955
|
-
if (filters.minTrustScore !== void 0 && card.trust) {
|
|
956
|
-
const overallTrust = card.trust.interactionScore * 0.4 + Math.min(card.trust.endorsements / 10, 1) * 0.2 + card.trust.completionRate * 0.2 + card.trust.uptime * 0.2;
|
|
957
|
-
if (overallTrust < filters.minTrustScore) {
|
|
958
|
-
return false;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
if (filters.language) {
|
|
962
|
-
const hasLanguage = card.capabilities.some(
|
|
963
|
-
(cap) => {
|
|
964
|
-
const metadata = cap.metadata;
|
|
965
|
-
if (!metadata) return false;
|
|
966
|
-
return metadata.language === filters.language || Array.isArray(metadata.languages) && metadata.languages.includes(filters.language);
|
|
967
|
-
}
|
|
968
|
-
);
|
|
969
|
-
if (!hasLanguage) return false;
|
|
970
|
-
}
|
|
971
|
-
if (filters.tags && filters.tags.length > 0) {
|
|
972
|
-
const cardTags = Array.isArray(card.metadata?.tags) ? card.metadata.tags : [];
|
|
973
|
-
const hasAllTags = filters.tags.every((tag) => cardTags.includes(tag));
|
|
974
|
-
if (!hasAllTags) return false;
|
|
975
|
-
}
|
|
976
|
-
return true;
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
962
|
|
|
981
|
-
// src/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
* Returns a score between 0 and 1
|
|
986
|
-
*/
|
|
987
|
-
match(query, capability) {
|
|
988
|
-
let score = 0;
|
|
989
|
-
let weights = 0;
|
|
990
|
-
if (query.capability && capability.id === query.capability) {
|
|
991
|
-
return 1;
|
|
992
|
-
}
|
|
993
|
-
if (query.capability) {
|
|
994
|
-
const nameScore = this.fuzzyMatch(query.capability, capability.name);
|
|
995
|
-
score += nameScore * 0.4;
|
|
996
|
-
weights += 0.4;
|
|
997
|
-
}
|
|
998
|
-
if (query.text) {
|
|
999
|
-
const keywords = this.extractKeywords(query.text);
|
|
1000
|
-
const keywordScore = this.matchKeywords(keywords, capability);
|
|
1001
|
-
score += keywordScore * 0.4;
|
|
1002
|
-
weights += 0.4;
|
|
1003
|
-
}
|
|
1004
|
-
if (query.capability && capability["@type"]) {
|
|
1005
|
-
const typeScore = this.matchesType(query.capability, capability["@type"]) ? 0.6 : 0;
|
|
1006
|
-
score += typeScore * 0.2;
|
|
1007
|
-
weights += 0.2;
|
|
1008
|
-
}
|
|
1009
|
-
return weights > 0 ? score / weights : 0;
|
|
1010
|
-
}
|
|
1011
|
-
/**
|
|
1012
|
-
* Extract keywords from natural language text
|
|
1013
|
-
*/
|
|
1014
|
-
extractKeywords(text) {
|
|
1015
|
-
const stopWords = /* @__PURE__ */ new Set([
|
|
1016
|
-
"a",
|
|
1017
|
-
"an",
|
|
1018
|
-
"the",
|
|
1019
|
-
"is",
|
|
1020
|
-
"are",
|
|
1021
|
-
"was",
|
|
1022
|
-
"were",
|
|
1023
|
-
"be",
|
|
1024
|
-
"been",
|
|
1025
|
-
"to",
|
|
1026
|
-
"from",
|
|
1027
|
-
"in",
|
|
1028
|
-
"on",
|
|
1029
|
-
"at",
|
|
1030
|
-
"by",
|
|
1031
|
-
"for",
|
|
1032
|
-
"with",
|
|
1033
|
-
"about",
|
|
1034
|
-
"can",
|
|
1035
|
-
"could",
|
|
1036
|
-
"should",
|
|
1037
|
-
"would",
|
|
1038
|
-
"will",
|
|
1039
|
-
"do",
|
|
1040
|
-
"does",
|
|
1041
|
-
"did",
|
|
1042
|
-
"i",
|
|
1043
|
-
"you",
|
|
1044
|
-
"he",
|
|
1045
|
-
"she",
|
|
1046
|
-
"it",
|
|
1047
|
-
"we",
|
|
1048
|
-
"they",
|
|
1049
|
-
"me",
|
|
1050
|
-
"him",
|
|
1051
|
-
"her"
|
|
1052
|
-
]);
|
|
1053
|
-
return text.toLowerCase().split(/\W+/).filter((word) => word.length > 2 && !stopWords.has(word));
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Match keywords against capability
|
|
1057
|
-
*/
|
|
1058
|
-
matchKeywords(keywords, capability) {
|
|
1059
|
-
if (keywords.length === 0) return 0;
|
|
1060
|
-
const capText = `${capability.name} ${capability.description}`.toLowerCase();
|
|
1061
|
-
const matches = keywords.filter((keyword) => capText.includes(keyword));
|
|
1062
|
-
return matches.length / keywords.length;
|
|
1063
|
-
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Check if query matches capability type hierarchy
|
|
1066
|
-
*/
|
|
1067
|
-
matchesType(query, type) {
|
|
1068
|
-
const queryLower = query.toLowerCase();
|
|
1069
|
-
const typeLower = type.toLowerCase();
|
|
1070
|
-
if (typeLower.includes(queryLower)) return true;
|
|
1071
|
-
const typeMap = {
|
|
1072
|
-
translate: ["translation", "translationservice"],
|
|
1073
|
-
review: ["codereview", "reviewservice"],
|
|
1074
|
-
analyze: ["analysis", "dataanalysis"],
|
|
1075
|
-
generate: ["generation", "textgeneration", "imagegeneration"],
|
|
1076
|
-
search: ["searchservice", "query"],
|
|
1077
|
-
compute: ["computation", "computationservice"],
|
|
1078
|
-
store: ["storage", "storageservice"],
|
|
1079
|
-
message: ["messaging", "messagingservice"],
|
|
1080
|
-
auth: ["authentication", "authenticationservice"]
|
|
1081
|
-
};
|
|
1082
|
-
for (const [key, values] of Object.entries(typeMap)) {
|
|
1083
|
-
if (queryLower.includes(key) && values.some((v) => typeLower.includes(v))) {
|
|
1084
|
-
return true;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
return false;
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* Fuzzy string matching using Levenshtein distance
|
|
1091
|
-
*/
|
|
1092
|
-
fuzzyMatch(query, target) {
|
|
1093
|
-
const queryLower = query.toLowerCase();
|
|
1094
|
-
const targetLower = target.toLowerCase();
|
|
1095
|
-
if (queryLower === targetLower) return 1;
|
|
1096
|
-
if (targetLower.includes(queryLower)) return 0.8;
|
|
1097
|
-
if (queryLower.includes(targetLower)) return 0.7;
|
|
1098
|
-
const distance = this.levenshteinDistance(queryLower, targetLower);
|
|
1099
|
-
const maxLen = Math.max(queryLower.length, targetLower.length);
|
|
1100
|
-
const similarity = 1 - distance / maxLen;
|
|
1101
|
-
return similarity > 0.5 ? similarity * 0.6 : 0;
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Calculate Levenshtein distance between two strings
|
|
1105
|
-
*/
|
|
1106
|
-
levenshteinDistance(a, b) {
|
|
1107
|
-
const matrix = [];
|
|
1108
|
-
for (let i = 0; i <= b.length; i++) {
|
|
1109
|
-
matrix[i] = [i];
|
|
1110
|
-
}
|
|
1111
|
-
for (let j = 0; j <= a.length; j++) {
|
|
1112
|
-
matrix[0][j] = j;
|
|
1113
|
-
}
|
|
1114
|
-
for (let i = 1; i <= b.length; i++) {
|
|
1115
|
-
for (let j = 1; j <= a.length; j++) {
|
|
1116
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1117
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1118
|
-
} else {
|
|
1119
|
-
matrix[i][j] = Math.min(
|
|
1120
|
-
matrix[i - 1][j - 1] + 1,
|
|
1121
|
-
// substitution
|
|
1122
|
-
matrix[i][j - 1] + 1,
|
|
1123
|
-
// insertion
|
|
1124
|
-
matrix[i - 1][j] + 1
|
|
1125
|
-
// deletion
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
return matrix[b.length][a.length];
|
|
1131
|
-
}
|
|
1132
|
-
};
|
|
1133
|
-
|
|
1134
|
-
// src/discovery/semantic-search.ts
|
|
1135
|
-
var logger4 = createLogger("semantic-search");
|
|
1136
|
-
var SemanticSearchEngine = class {
|
|
1137
|
-
constructor(dht) {
|
|
1138
|
-
this.dht = dht;
|
|
1139
|
-
this.index = new SearchIndex();
|
|
1140
|
-
this.matcher = new CapabilityMatcher();
|
|
1141
|
-
}
|
|
1142
|
-
index;
|
|
1143
|
-
matcher;
|
|
1144
|
-
/**
|
|
1145
|
-
* Main search interface
|
|
1146
|
-
* Local-first with network fallback
|
|
1147
|
-
*/
|
|
1148
|
-
async search(query) {
|
|
1149
|
-
logger4.info("Searching for agents", { query });
|
|
1150
|
-
const localResults = this.index.search(query);
|
|
1151
|
-
logger4.debug("Local search results", { count: localResults.length });
|
|
1152
|
-
const limit = query.limit || 10;
|
|
1153
|
-
if (localResults.length < limit && this.dht) {
|
|
1154
|
-
logger4.debug("Insufficient local results, querying network");
|
|
1155
|
-
const networkResults = await this.searchNetwork(query);
|
|
1156
|
-
return this.mergeResults(localResults, networkResults, limit);
|
|
1157
|
-
}
|
|
1158
|
-
return localResults.map((r) => r.card);
|
|
963
|
+
// src/messaging/envelope.ts
|
|
964
|
+
function normalizeEnvelopeType(type) {
|
|
965
|
+
if (type === "message" || type === "notification" || type === "request") {
|
|
966
|
+
return "message";
|
|
1159
967
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
*/
|
|
1163
|
-
indexAgentCard(card) {
|
|
1164
|
-
this.index.indexAgentCard(card);
|
|
968
|
+
if (type === "reply" || type === "response") {
|
|
969
|
+
return "reply";
|
|
1165
970
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
/**
|
|
1173
|
-
* Get all indexed cards
|
|
1174
|
-
*/
|
|
1175
|
-
getAllIndexedCards() {
|
|
1176
|
-
return this.index.getAllCards();
|
|
971
|
+
return void 0;
|
|
972
|
+
}
|
|
973
|
+
function normalizeEnvelope(msg) {
|
|
974
|
+
if (typeof msg !== "object" || msg === null) {
|
|
975
|
+
return void 0;
|
|
1177
976
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
this.index.clear();
|
|
977
|
+
const envelope = msg;
|
|
978
|
+
const normalizedType = envelope.type ? normalizeEnvelopeType(envelope.type) : void 0;
|
|
979
|
+
if (typeof envelope.id !== "string" || typeof envelope.from !== "string" || !envelope.from.startsWith("did:clawiverse:") || typeof envelope.to !== "string" || !envelope.to.startsWith("did:clawiverse:") || !normalizedType || typeof envelope.protocol !== "string" || envelope.payload === void 0 || typeof envelope.timestamp !== "number" || typeof envelope.signature !== "string") {
|
|
980
|
+
return void 0;
|
|
1183
981
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
*/
|
|
1187
|
-
getIndexSize() {
|
|
1188
|
-
return this.index.size();
|
|
982
|
+
if (normalizedType === "reply" && typeof envelope.replyTo !== "string") {
|
|
983
|
+
return void 0;
|
|
1189
984
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
*/
|
|
1193
|
-
async searchNetwork(query) {
|
|
1194
|
-
if (!this.dht) {
|
|
1195
|
-
return [];
|
|
1196
|
-
}
|
|
1197
|
-
try {
|
|
1198
|
-
const capability = query.capability || this.extractPrimaryCapability(query.text);
|
|
1199
|
-
if (!capability) {
|
|
1200
|
-
logger4.debug("No capability extracted from query, skipping network search");
|
|
1201
|
-
return [];
|
|
1202
|
-
}
|
|
1203
|
-
const cards = await this.dht.queryByCapability(capability);
|
|
1204
|
-
logger4.debug("Network search results", { count: cards.length });
|
|
1205
|
-
return cards.map((card) => {
|
|
1206
|
-
const score = this.scoreCard(card, query);
|
|
1207
|
-
return { card, score };
|
|
1208
|
-
});
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
logger4.error("Network search failed", { error });
|
|
1211
|
-
return [];
|
|
1212
|
-
}
|
|
985
|
+
if (normalizedType === "message" && envelope.replyTo !== void 0) {
|
|
986
|
+
return void 0;
|
|
1213
987
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
merged.sort((a, b) => b.score - a.score);
|
|
1234
|
-
return merged.slice(0, limit).map((r) => r.card);
|
|
988
|
+
return {
|
|
989
|
+
id: envelope.id,
|
|
990
|
+
from: envelope.from,
|
|
991
|
+
to: envelope.to,
|
|
992
|
+
type: normalizedType,
|
|
993
|
+
protocol: envelope.protocol,
|
|
994
|
+
payload: envelope.payload,
|
|
995
|
+
timestamp: envelope.timestamp,
|
|
996
|
+
signature: envelope.signature,
|
|
997
|
+
replyTo: normalizedType === "reply" ? envelope.replyTo : void 0,
|
|
998
|
+
threadId: typeof envelope.threadId === "string" ? envelope.threadId : void 0
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function createEnvelope(from, to, type, protocol, payload, replyTo, threadId) {
|
|
1002
|
+
const normalizedType = normalizeEnvelopeType(type);
|
|
1003
|
+
if (!normalizedType) {
|
|
1004
|
+
throw new MessagingError(`Invalid message type: ${type}`);
|
|
1235
1005
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
*/
|
|
1239
|
-
scoreCard(card, query) {
|
|
1240
|
-
let totalScore = 0;
|
|
1241
|
-
let count = 0;
|
|
1242
|
-
for (const capability of card.capabilities) {
|
|
1243
|
-
const score = this.matcher.match(query, capability);
|
|
1244
|
-
if (score > 0) {
|
|
1245
|
-
totalScore += score;
|
|
1246
|
-
count++;
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
const avgScore = count > 0 ? totalScore / count : 0;
|
|
1250
|
-
if (card.trust) {
|
|
1251
|
-
const trustBoost = card.trust.interactionScore * 0.2;
|
|
1252
|
-
return Math.min(avgScore + trustBoost, 1);
|
|
1253
|
-
}
|
|
1254
|
-
return avgScore;
|
|
1255
|
-
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Extract primary capability from natural language text
|
|
1258
|
-
*/
|
|
1259
|
-
extractPrimaryCapability(text) {
|
|
1260
|
-
if (!text) return void 0;
|
|
1261
|
-
const keywords = this.matcher.extractKeywords(text);
|
|
1262
|
-
const capabilityKeywords = [
|
|
1263
|
-
"translate",
|
|
1264
|
-
"translation",
|
|
1265
|
-
"review",
|
|
1266
|
-
"code",
|
|
1267
|
-
"analyze",
|
|
1268
|
-
"analysis",
|
|
1269
|
-
"generate",
|
|
1270
|
-
"generation",
|
|
1271
|
-
"search",
|
|
1272
|
-
"query",
|
|
1273
|
-
"compute",
|
|
1274
|
-
"calculation",
|
|
1275
|
-
"store",
|
|
1276
|
-
"storage",
|
|
1277
|
-
"message",
|
|
1278
|
-
"messaging",
|
|
1279
|
-
"auth",
|
|
1280
|
-
"authentication"
|
|
1281
|
-
];
|
|
1282
|
-
for (const keyword of keywords) {
|
|
1283
|
-
if (capabilityKeywords.includes(keyword)) {
|
|
1284
|
-
return keyword;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
return keywords[0];
|
|
1006
|
+
if (normalizedType === "reply" && !replyTo) {
|
|
1007
|
+
throw new MessagingError("Reply envelopes must include replyTo");
|
|
1288
1008
|
}
|
|
1289
|
-
};
|
|
1290
|
-
function createSemanticSearch(dht) {
|
|
1291
|
-
return new SemanticSearchEngine(dht);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// src/messaging/envelope.ts
|
|
1295
|
-
function createEnvelope(from, to, type, protocol, payload, replyTo) {
|
|
1296
1009
|
return {
|
|
1297
1010
|
id: generateMessageId(),
|
|
1298
1011
|
from,
|
|
1299
1012
|
to,
|
|
1300
|
-
type,
|
|
1013
|
+
type: normalizedType,
|
|
1301
1014
|
protocol,
|
|
1302
1015
|
payload,
|
|
1303
1016
|
timestamp: Date.now(),
|
|
1304
|
-
replyTo
|
|
1017
|
+
...normalizedType === "reply" ? { replyTo } : {},
|
|
1018
|
+
...threadId ? { threadId } : {}
|
|
1305
1019
|
};
|
|
1306
1020
|
}
|
|
1307
1021
|
async function signEnvelope(envelope, signFn) {
|
|
@@ -1319,7 +1033,11 @@ async function signEnvelope(envelope, signFn) {
|
|
|
1319
1033
|
}
|
|
1320
1034
|
async function verifyEnvelope(envelope, verifyFn) {
|
|
1321
1035
|
try {
|
|
1322
|
-
const
|
|
1036
|
+
const normalized = normalizeEnvelope(envelope);
|
|
1037
|
+
if (!normalized) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
const { signature, ...envelopeWithoutSig } = normalized;
|
|
1323
1041
|
const envelopeJson = JSON.stringify(envelopeWithoutSig);
|
|
1324
1042
|
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
1325
1043
|
const signatureBytes = Buffer.from(signature, "hex");
|
|
@@ -1329,15 +1047,15 @@ async function verifyEnvelope(envelope, verifyFn) {
|
|
|
1329
1047
|
}
|
|
1330
1048
|
}
|
|
1331
1049
|
function validateEnvelope(msg) {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
}
|
|
1335
|
-
const m = msg;
|
|
1336
|
-
return typeof m.id === "string" && typeof m.from === "string" && m.from.startsWith("did:clawiverse:") && typeof m.to === "string" && m.to.startsWith("did:clawiverse:") && (m.type === "request" || m.type === "response" || m.type === "notification") && typeof m.protocol === "string" && m.payload !== void 0 && typeof m.timestamp === "number" && typeof m.signature === "string";
|
|
1050
|
+
const normalized = normalizeEnvelope(msg);
|
|
1051
|
+
return normalized !== void 0;
|
|
1337
1052
|
}
|
|
1338
1053
|
function generateMessageId() {
|
|
1339
1054
|
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
1340
1055
|
}
|
|
1056
|
+
function generateThreadId() {
|
|
1057
|
+
return `thread_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
1058
|
+
}
|
|
1341
1059
|
function encodeMessage(envelope) {
|
|
1342
1060
|
try {
|
|
1343
1061
|
return encode(envelope);
|
|
@@ -1347,8 +1065,16 @@ function encodeMessage(envelope) {
|
|
|
1347
1065
|
}
|
|
1348
1066
|
function decodeMessage(data) {
|
|
1349
1067
|
try {
|
|
1350
|
-
|
|
1068
|
+
const decoded = decode(data);
|
|
1069
|
+
const normalized = normalizeEnvelope(decoded);
|
|
1070
|
+
if (!normalized) {
|
|
1071
|
+
throw new MessagingError("Decoded message envelope is invalid");
|
|
1072
|
+
}
|
|
1073
|
+
return normalized;
|
|
1351
1074
|
} catch (error) {
|
|
1075
|
+
if (error instanceof MessagingError) {
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1352
1078
|
throw new MessagingError("Failed to decode message", error);
|
|
1353
1079
|
}
|
|
1354
1080
|
}
|
|
@@ -1361,30 +1087,37 @@ function encodeMessageJSON(envelope) {
|
|
|
1361
1087
|
}
|
|
1362
1088
|
function decodeMessageJSON(json) {
|
|
1363
1089
|
try {
|
|
1364
|
-
|
|
1090
|
+
const decoded = JSON.parse(json);
|
|
1091
|
+
const normalized = normalizeEnvelope(decoded);
|
|
1092
|
+
if (!normalized) {
|
|
1093
|
+
throw new MessagingError("Decoded JSON envelope is invalid");
|
|
1094
|
+
}
|
|
1095
|
+
return normalized;
|
|
1365
1096
|
} catch (error) {
|
|
1097
|
+
if (error instanceof MessagingError) {
|
|
1098
|
+
throw error;
|
|
1099
|
+
}
|
|
1366
1100
|
throw new MessagingError("Failed to decode message from JSON", error);
|
|
1367
1101
|
}
|
|
1368
1102
|
}
|
|
1369
1103
|
|
|
1370
1104
|
// src/messaging/router.ts
|
|
1371
|
-
var
|
|
1105
|
+
var logger3 = createLogger("router");
|
|
1372
1106
|
function createMessageRouter(relayClient, verifyFn) {
|
|
1373
1107
|
const handlers = /* @__PURE__ */ new Map();
|
|
1374
1108
|
let catchAllHandler;
|
|
1375
|
-
const pendingRequests = /* @__PURE__ */ new Map();
|
|
1376
1109
|
return {
|
|
1377
1110
|
registerHandler: (protocol, handler) => {
|
|
1378
1111
|
handlers.set(protocol, handler);
|
|
1379
|
-
|
|
1112
|
+
logger3.info("Registered message handler", { protocol });
|
|
1380
1113
|
},
|
|
1381
1114
|
unregisterHandler: (protocol) => {
|
|
1382
1115
|
handlers.delete(protocol);
|
|
1383
|
-
|
|
1116
|
+
logger3.info("Unregistered message handler", { protocol });
|
|
1384
1117
|
},
|
|
1385
1118
|
registerCatchAllHandler: (handler) => {
|
|
1386
1119
|
catchAllHandler = handler;
|
|
1387
|
-
|
|
1120
|
+
logger3.info("Registered catch-all message handler");
|
|
1388
1121
|
},
|
|
1389
1122
|
sendMessage: async (envelope) => {
|
|
1390
1123
|
try {
|
|
@@ -1393,25 +1126,13 @@ function createMessageRouter(relayClient, verifyFn) {
|
|
|
1393
1126
|
}
|
|
1394
1127
|
const encoded = encodeMessage(envelope);
|
|
1395
1128
|
await relayClient.sendEnvelope(envelope.to, encoded);
|
|
1396
|
-
|
|
1129
|
+
logger3.info("Message sent via relay", {
|
|
1397
1130
|
id: envelope.id,
|
|
1398
1131
|
from: envelope.from,
|
|
1399
1132
|
to: envelope.to,
|
|
1400
|
-
protocol: envelope.protocol
|
|
1133
|
+
protocol: envelope.protocol,
|
|
1134
|
+
type: envelope.type
|
|
1401
1135
|
});
|
|
1402
|
-
if (envelope.type === "request") {
|
|
1403
|
-
logger5.debug("Waiting for response to request", { id: envelope.id });
|
|
1404
|
-
const RESPONSE_TIMEOUT = 3e4;
|
|
1405
|
-
return new Promise((resolve) => {
|
|
1406
|
-
const timeout = setTimeout(() => {
|
|
1407
|
-
pendingRequests.delete(envelope.id);
|
|
1408
|
-
logger5.warn("Response timeout", { id: envelope.id, timeout: RESPONSE_TIMEOUT });
|
|
1409
|
-
resolve(void 0);
|
|
1410
|
-
}, RESPONSE_TIMEOUT);
|
|
1411
|
-
pendingRequests.set(envelope.id, { resolve, timeout });
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
return void 0;
|
|
1415
1136
|
} catch (error) {
|
|
1416
1137
|
if (error instanceof MessagingError) throw error;
|
|
1417
1138
|
throw new MessagingError("Failed to send message", error);
|
|
@@ -1422,7 +1143,7 @@ function createMessageRouter(relayClient, verifyFn) {
|
|
|
1422
1143
|
try {
|
|
1423
1144
|
const envelope = decodeMessage(deliverMsg.envelope);
|
|
1424
1145
|
if (!validateEnvelope(envelope)) {
|
|
1425
|
-
|
|
1146
|
+
logger3.warn("Received invalid message envelope");
|
|
1426
1147
|
return;
|
|
1427
1148
|
}
|
|
1428
1149
|
const isValidSignature = await verifyEnvelope(envelope, async (signature, data) => {
|
|
@@ -1430,7 +1151,7 @@ function createMessageRouter(relayClient, verifyFn) {
|
|
|
1430
1151
|
return verify(signature, data, senderPublicKey);
|
|
1431
1152
|
});
|
|
1432
1153
|
if (!isValidSignature) {
|
|
1433
|
-
|
|
1154
|
+
logger3.warn("Received message with invalid signature", {
|
|
1434
1155
|
id: envelope.id,
|
|
1435
1156
|
from: envelope.from
|
|
1436
1157
|
});
|
|
@@ -1442,77 +1163,59 @@ function createMessageRouter(relayClient, verifyFn) {
|
|
|
1442
1163
|
const signatureBytes = Buffer.from(signature, "hex");
|
|
1443
1164
|
const hookValid = await verifyFn(signatureBytes, dataBytes);
|
|
1444
1165
|
if (!hookValid) {
|
|
1445
|
-
|
|
1166
|
+
logger3.warn("Message rejected by custom verifier", {
|
|
1446
1167
|
id: envelope.id,
|
|
1447
1168
|
from: envelope.from
|
|
1448
1169
|
});
|
|
1449
1170
|
return;
|
|
1450
1171
|
}
|
|
1451
1172
|
} catch (error) {
|
|
1452
|
-
|
|
1173
|
+
logger3.warn("Custom verification hook failed", {
|
|
1453
1174
|
id: envelope.id,
|
|
1454
1175
|
from: envelope.from,
|
|
1455
1176
|
error: error.message
|
|
1456
1177
|
});
|
|
1457
1178
|
return;
|
|
1458
1179
|
}
|
|
1459
|
-
|
|
1180
|
+
logger3.info("Received message", {
|
|
1460
1181
|
id: envelope.id,
|
|
1461
1182
|
from: envelope.from,
|
|
1462
1183
|
to: envelope.to,
|
|
1463
1184
|
protocol: envelope.protocol,
|
|
1464
1185
|
type: envelope.type
|
|
1465
1186
|
});
|
|
1466
|
-
if (envelope.type === "response" && envelope.replyTo) {
|
|
1467
|
-
const pending = pendingRequests.get(envelope.replyTo);
|
|
1468
|
-
if (pending) {
|
|
1469
|
-
clearTimeout(pending.timeout);
|
|
1470
|
-
pendingRequests.delete(envelope.replyTo);
|
|
1471
|
-
pending.resolve(envelope);
|
|
1472
|
-
logger5.info("Matched response to pending request", {
|
|
1473
|
-
requestId: envelope.replyTo,
|
|
1474
|
-
responseId: envelope.id
|
|
1475
|
-
});
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
1187
|
const handler = handlers.get(envelope.protocol);
|
|
1480
1188
|
let response = void 0;
|
|
1481
1189
|
if (handler) {
|
|
1482
1190
|
response = await handler(envelope);
|
|
1483
1191
|
} else if (catchAllHandler) {
|
|
1484
|
-
|
|
1192
|
+
logger3.debug("Using catch-all handler for protocol", { protocol: envelope.protocol });
|
|
1485
1193
|
response = await catchAllHandler(envelope);
|
|
1486
1194
|
} else {
|
|
1487
|
-
|
|
1195
|
+
logger3.warn("No handler for protocol", { protocol: envelope.protocol });
|
|
1488
1196
|
}
|
|
1489
1197
|
if (response) {
|
|
1490
1198
|
const encoded = encodeMessage(response);
|
|
1491
1199
|
await relayClient.sendEnvelope(response.to, encoded);
|
|
1492
|
-
|
|
1200
|
+
logger3.info("Sent reply back to sender", {
|
|
1493
1201
|
responseId: response.id,
|
|
1494
1202
|
replyTo: response.replyTo
|
|
1495
1203
|
});
|
|
1496
1204
|
}
|
|
1497
1205
|
} catch (error) {
|
|
1498
|
-
|
|
1206
|
+
logger3.error("Error handling incoming message", error);
|
|
1499
1207
|
}
|
|
1500
1208
|
});
|
|
1501
|
-
|
|
1209
|
+
logger3.info("Message router started");
|
|
1502
1210
|
},
|
|
1503
1211
|
stop: async () => {
|
|
1504
|
-
for (const [, pending] of pendingRequests.entries()) {
|
|
1505
|
-
clearTimeout(pending.timeout);
|
|
1506
|
-
pending.resolve(void 0);
|
|
1507
|
-
}
|
|
1508
|
-
pendingRequests.clear();
|
|
1509
1212
|
handlers.clear();
|
|
1510
1213
|
catchAllHandler = void 0;
|
|
1511
|
-
|
|
1214
|
+
logger3.info("Message router stopped");
|
|
1512
1215
|
}
|
|
1513
1216
|
};
|
|
1514
1217
|
}
|
|
1515
|
-
var
|
|
1218
|
+
var logger4 = createLogger("message-storage");
|
|
1516
1219
|
var MessageStorage = class {
|
|
1517
1220
|
db;
|
|
1518
1221
|
ready = false;
|
|
@@ -1522,22 +1225,28 @@ var MessageStorage = class {
|
|
|
1522
1225
|
async open() {
|
|
1523
1226
|
await this.db.open();
|
|
1524
1227
|
this.ready = true;
|
|
1525
|
-
|
|
1228
|
+
logger4.info("Message storage opened");
|
|
1526
1229
|
}
|
|
1527
1230
|
async close() {
|
|
1528
1231
|
if (this.ready) {
|
|
1529
1232
|
await this.db.close();
|
|
1530
1233
|
this.ready = false;
|
|
1531
|
-
|
|
1234
|
+
logger4.info("Message storage closed");
|
|
1532
1235
|
}
|
|
1533
1236
|
}
|
|
1534
1237
|
// ─── Message Operations ───────────────────────────────────────────────────
|
|
1535
1238
|
async putMessage(msg) {
|
|
1536
|
-
const
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1239
|
+
const normalized = this.requireStoredMessage(msg);
|
|
1240
|
+
const ts = String(normalized.receivedAt ?? normalized.sentAt ?? Date.now()).padStart(16, "0");
|
|
1241
|
+
const key = `msg:${normalized.direction}:${ts}:${normalized.envelope.id}`;
|
|
1242
|
+
await this.db.put(key, normalized);
|
|
1243
|
+
const idxKey = `idx:from:${normalized.envelope.from}:${ts}:${normalized.envelope.id}`;
|
|
1540
1244
|
await this.db.put(idxKey, "1");
|
|
1245
|
+
if (normalized.envelope.threadId) {
|
|
1246
|
+
const threadIdxKey = `idx:thread:${normalized.envelope.threadId}:${ts}:${normalized.envelope.id}`;
|
|
1247
|
+
await this.db.put(threadIdxKey, "1");
|
|
1248
|
+
await this.updateSessionMeta(normalized);
|
|
1249
|
+
}
|
|
1541
1250
|
}
|
|
1542
1251
|
async getMessage(id) {
|
|
1543
1252
|
for (const direction of ["inbound", "outbound"]) {
|
|
@@ -1547,7 +1256,9 @@ var MessageStorage = class {
|
|
|
1547
1256
|
lte: prefix + "\xFF",
|
|
1548
1257
|
valueEncoding: "json"
|
|
1549
1258
|
})) {
|
|
1550
|
-
if (value.envelope.id
|
|
1259
|
+
if (value.envelope.id !== id) continue;
|
|
1260
|
+
const normalized = this.normalizeStoredMessage(value);
|
|
1261
|
+
if (normalized) return normalized;
|
|
1551
1262
|
}
|
|
1552
1263
|
}
|
|
1553
1264
|
return null;
|
|
@@ -1583,13 +1294,14 @@ var MessageStorage = class {
|
|
|
1583
1294
|
// newest first
|
|
1584
1295
|
valueEncoding: "json"
|
|
1585
1296
|
})) {
|
|
1586
|
-
|
|
1297
|
+
const normalized = this.normalizeStoredMessage(value);
|
|
1298
|
+
if (!normalized || !this.matchesFilter(normalized, filter)) continue;
|
|
1587
1299
|
total++;
|
|
1588
1300
|
if (skipped < offset) {
|
|
1589
1301
|
skipped++;
|
|
1590
1302
|
continue;
|
|
1591
1303
|
}
|
|
1592
|
-
if (results.length < limit) results.push(
|
|
1304
|
+
if (results.length < limit) results.push(normalized);
|
|
1593
1305
|
}
|
|
1594
1306
|
return {
|
|
1595
1307
|
messages: results,
|
|
@@ -1611,6 +1323,10 @@ var MessageStorage = class {
|
|
|
1611
1323
|
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
1612
1324
|
}
|
|
1613
1325
|
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
1326
|
+
if (filter.replyTo) {
|
|
1327
|
+
const replyIds = Array.isArray(filter.replyTo) ? filter.replyTo : [filter.replyTo];
|
|
1328
|
+
if (!msg.envelope.replyTo || !replyIds.includes(msg.envelope.replyTo)) return false;
|
|
1329
|
+
}
|
|
1614
1330
|
if (filter.unreadOnly && msg.readAt != null) return false;
|
|
1615
1331
|
if (filter.status) {
|
|
1616
1332
|
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
@@ -1621,6 +1337,7 @@ var MessageStorage = class {
|
|
|
1621
1337
|
if (age > filter.maxAge) return false;
|
|
1622
1338
|
}
|
|
1623
1339
|
if (filter.minTrustScore != null && (msg.trustScore ?? 0) < filter.minTrustScore) return false;
|
|
1340
|
+
if (filter.threadId && msg.envelope.threadId !== filter.threadId) return false;
|
|
1624
1341
|
return true;
|
|
1625
1342
|
}
|
|
1626
1343
|
async countMessages(direction, filter = {}) {
|
|
@@ -1631,10 +1348,29 @@ var MessageStorage = class {
|
|
|
1631
1348
|
lte: prefix + "\xFF",
|
|
1632
1349
|
valueEncoding: "json"
|
|
1633
1350
|
})) {
|
|
1634
|
-
|
|
1351
|
+
const normalized = this.normalizeStoredMessage(value);
|
|
1352
|
+
if (normalized && this.matchesFilter(normalized, filter)) count++;
|
|
1635
1353
|
}
|
|
1636
1354
|
return count;
|
|
1637
1355
|
}
|
|
1356
|
+
normalizeStoredMessage(msg) {
|
|
1357
|
+
const envelope = normalizeEnvelope(msg.envelope);
|
|
1358
|
+
if (!envelope) {
|
|
1359
|
+
logger4.warn("Skipping invalid stored message envelope", { id: msg.envelope?.id });
|
|
1360
|
+
return null;
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
...msg,
|
|
1364
|
+
envelope
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
requireStoredMessage(msg) {
|
|
1368
|
+
const normalized = this.normalizeStoredMessage(msg);
|
|
1369
|
+
if (!normalized) {
|
|
1370
|
+
throw new Error(`Invalid stored message envelope: ${msg.envelope?.id ?? "unknown"}`);
|
|
1371
|
+
}
|
|
1372
|
+
return normalized;
|
|
1373
|
+
}
|
|
1638
1374
|
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
1639
1375
|
async putBlock(entry) {
|
|
1640
1376
|
await this.db.put(`block:${entry.did}`, entry);
|
|
@@ -1737,10 +1473,160 @@ var MessageStorage = class {
|
|
|
1737
1473
|
}
|
|
1738
1474
|
await this.db.batch(toDelete.map((key) => ({ type: "del", key })));
|
|
1739
1475
|
}
|
|
1476
|
+
// ─── Session Management (CVP-0014) ────────────────────────────────────────
|
|
1477
|
+
async updateSessionMeta(msg) {
|
|
1478
|
+
if (!msg.envelope.threadId) return;
|
|
1479
|
+
const sessionKey = `session:${msg.envelope.threadId}`;
|
|
1480
|
+
let session;
|
|
1481
|
+
try {
|
|
1482
|
+
session = await this.db.get(sessionKey);
|
|
1483
|
+
} catch {
|
|
1484
|
+
const peerDid = msg.direction === "inbound" ? msg.envelope.from : msg.envelope.to;
|
|
1485
|
+
const text = typeof msg.envelope.payload === "object" && msg.envelope.payload !== null ? msg.envelope.payload.text ?? msg.envelope.payload.message ?? "" : String(msg.envelope.payload ?? "");
|
|
1486
|
+
const title = text ? String(text).slice(0, 50) : void 0;
|
|
1487
|
+
session = {
|
|
1488
|
+
threadId: msg.envelope.threadId,
|
|
1489
|
+
peerDid,
|
|
1490
|
+
startedAt: msg.receivedAt ?? msg.sentAt ?? Date.now(),
|
|
1491
|
+
lastMessageAt: msg.receivedAt ?? msg.sentAt ?? Date.now(),
|
|
1492
|
+
messageCount: 1,
|
|
1493
|
+
title
|
|
1494
|
+
};
|
|
1495
|
+
await this.db.put(sessionKey, session);
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
session.lastMessageAt = msg.receivedAt ?? msg.sentAt ?? Date.now();
|
|
1499
|
+
session.messageCount = (session.messageCount ?? 0) + 1;
|
|
1500
|
+
await this.db.put(sessionKey, session);
|
|
1501
|
+
}
|
|
1502
|
+
async getSession(threadId) {
|
|
1503
|
+
try {
|
|
1504
|
+
return await this.db.get(`session:${threadId}`);
|
|
1505
|
+
} catch {
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
async listSessions(peerDid, limit = 50, includeArchived = false) {
|
|
1510
|
+
const results = [];
|
|
1511
|
+
for await (const [, value] of this.db.iterator({
|
|
1512
|
+
gte: "session:",
|
|
1513
|
+
lte: "session:\xFF",
|
|
1514
|
+
reverse: true,
|
|
1515
|
+
// newest first
|
|
1516
|
+
valueEncoding: "json"
|
|
1517
|
+
})) {
|
|
1518
|
+
if (peerDid && value.peerDid !== peerDid) continue;
|
|
1519
|
+
if (!includeArchived && value.archived) continue;
|
|
1520
|
+
results.push(value);
|
|
1521
|
+
if (results.length >= limit) break;
|
|
1522
|
+
}
|
|
1523
|
+
return results;
|
|
1524
|
+
}
|
|
1525
|
+
async archiveSession(threadId) {
|
|
1526
|
+
const session = await this.getSession(threadId);
|
|
1527
|
+
if (!session) {
|
|
1528
|
+
throw new Error(`Session not found: ${threadId}`);
|
|
1529
|
+
}
|
|
1530
|
+
session.archived = true;
|
|
1531
|
+
session.archivedAt = Date.now();
|
|
1532
|
+
await this.db.put(`session:${threadId}`, session);
|
|
1533
|
+
}
|
|
1534
|
+
async unarchiveSession(threadId) {
|
|
1535
|
+
const session = await this.getSession(threadId);
|
|
1536
|
+
if (!session) {
|
|
1537
|
+
throw new Error(`Session not found: ${threadId}`);
|
|
1538
|
+
}
|
|
1539
|
+
session.archived = false;
|
|
1540
|
+
delete session.archivedAt;
|
|
1541
|
+
await this.db.put(`session:${threadId}`, session);
|
|
1542
|
+
}
|
|
1543
|
+
async listArchivedSessions(peerDid, limit = 50) {
|
|
1544
|
+
const results = [];
|
|
1545
|
+
for await (const [, value] of this.db.iterator({
|
|
1546
|
+
gte: "session:",
|
|
1547
|
+
lte: "session:\xFF",
|
|
1548
|
+
reverse: true,
|
|
1549
|
+
// newest first
|
|
1550
|
+
valueEncoding: "json"
|
|
1551
|
+
})) {
|
|
1552
|
+
if (!value.archived) continue;
|
|
1553
|
+
if (peerDid && value.peerDid !== peerDid) continue;
|
|
1554
|
+
results.push(value);
|
|
1555
|
+
if (results.length >= limit) break;
|
|
1556
|
+
}
|
|
1557
|
+
return results;
|
|
1558
|
+
}
|
|
1559
|
+
async searchSessions(query, limit = 50) {
|
|
1560
|
+
const results = [];
|
|
1561
|
+
const lowerQuery = query.toLowerCase();
|
|
1562
|
+
for await (const [, value] of this.db.iterator({
|
|
1563
|
+
gte: "session:",
|
|
1564
|
+
lte: "session:\xFF",
|
|
1565
|
+
reverse: true,
|
|
1566
|
+
// newest first
|
|
1567
|
+
valueEncoding: "json"
|
|
1568
|
+
})) {
|
|
1569
|
+
if (value.title && value.title.toLowerCase().includes(lowerQuery)) {
|
|
1570
|
+
results.push(value);
|
|
1571
|
+
if (results.length >= limit) break;
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
if (value.peerDid && value.peerDid.toLowerCase().includes(lowerQuery)) {
|
|
1575
|
+
results.push(value);
|
|
1576
|
+
if (results.length >= limit) break;
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
const messages = await this.queryMessagesByThread(value.threadId, { limit: 100 });
|
|
1580
|
+
for (const msg of messages.messages) {
|
|
1581
|
+
const payload = msg.envelope.payload;
|
|
1582
|
+
const text = payload?.text || payload?.message || "";
|
|
1583
|
+
if (text && String(text).toLowerCase().includes(lowerQuery)) {
|
|
1584
|
+
results.push(value);
|
|
1585
|
+
if (results.length >= limit) break;
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
if (results.length >= limit) break;
|
|
1590
|
+
}
|
|
1591
|
+
return results;
|
|
1592
|
+
}
|
|
1593
|
+
async queryMessagesByThread(threadId, pagination = {}) {
|
|
1594
|
+
const { limit = 50, offset = 0 } = pagination;
|
|
1595
|
+
const prefix = `idx:thread:${threadId}:`;
|
|
1596
|
+
const results = [];
|
|
1597
|
+
let total = 0;
|
|
1598
|
+
let skipped = 0;
|
|
1599
|
+
const messageIds = [];
|
|
1600
|
+
for await (const [key] of this.db.iterator({
|
|
1601
|
+
gte: prefix,
|
|
1602
|
+
lte: prefix + "\xFF",
|
|
1603
|
+
reverse: false
|
|
1604
|
+
// chronological order
|
|
1605
|
+
})) {
|
|
1606
|
+
const parts = key.split(":");
|
|
1607
|
+
const msgId = parts[parts.length - 1];
|
|
1608
|
+
messageIds.push(msgId);
|
|
1609
|
+
}
|
|
1610
|
+
for (const msgId of messageIds) {
|
|
1611
|
+
const msg = await this.getMessage(msgId);
|
|
1612
|
+
if (!msg) continue;
|
|
1613
|
+
total++;
|
|
1614
|
+
if (skipped < offset) {
|
|
1615
|
+
skipped++;
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (results.length < limit) results.push(msg);
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
messages: results,
|
|
1622
|
+
total,
|
|
1623
|
+
hasMore: total > offset + results.length
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1740
1626
|
};
|
|
1741
1627
|
|
|
1742
1628
|
// src/messaging/queue.ts
|
|
1743
|
-
var
|
|
1629
|
+
var logger5 = createLogger("message-queue");
|
|
1744
1630
|
var MessageQueue = class {
|
|
1745
1631
|
storage;
|
|
1746
1632
|
subscriptions = /* @__PURE__ */ new Map();
|
|
@@ -1753,12 +1639,12 @@ var MessageQueue = class {
|
|
|
1753
1639
|
}
|
|
1754
1640
|
async start() {
|
|
1755
1641
|
await this.storage.open();
|
|
1756
|
-
|
|
1642
|
+
logger5.info("Message queue started");
|
|
1757
1643
|
}
|
|
1758
1644
|
async stop() {
|
|
1759
1645
|
await this.storage.close();
|
|
1760
1646
|
this.subscriptions.clear();
|
|
1761
|
-
|
|
1647
|
+
logger5.info("Message queue stopped");
|
|
1762
1648
|
}
|
|
1763
1649
|
// ─── Inbox ────────────────────────────────────────────────────────────────
|
|
1764
1650
|
async getInbox(filter = {}, pagination = {}) {
|
|
@@ -1781,16 +1667,17 @@ var MessageQueue = class {
|
|
|
1781
1667
|
await this.storage.updateMessage(id, { status: "pending", error: void 0 });
|
|
1782
1668
|
}
|
|
1783
1669
|
// ─── Enqueue ──────────────────────────────────────────────────────────────
|
|
1784
|
-
async enqueueInbound(envelope, trustScore) {
|
|
1670
|
+
async enqueueInbound(envelope, trustScore, trustStatus) {
|
|
1785
1671
|
const msg = {
|
|
1786
1672
|
envelope,
|
|
1787
1673
|
direction: "inbound",
|
|
1788
1674
|
status: "pending",
|
|
1789
1675
|
receivedAt: Date.now(),
|
|
1790
|
-
trustScore
|
|
1676
|
+
trustScore,
|
|
1677
|
+
trustStatus
|
|
1791
1678
|
};
|
|
1792
1679
|
await this.storage.putMessage(msg);
|
|
1793
|
-
|
|
1680
|
+
logger5.debug("Enqueued inbound message", { id: envelope.id, from: envelope.from });
|
|
1794
1681
|
this.notifySubscribers(msg);
|
|
1795
1682
|
return msg;
|
|
1796
1683
|
}
|
|
@@ -1802,7 +1689,7 @@ var MessageQueue = class {
|
|
|
1802
1689
|
sentAt: Date.now()
|
|
1803
1690
|
};
|
|
1804
1691
|
await this.storage.putMessage(msg);
|
|
1805
|
-
|
|
1692
|
+
logger5.debug("Enqueued outbound message", { id: envelope.id, to: envelope.to });
|
|
1806
1693
|
return msg;
|
|
1807
1694
|
}
|
|
1808
1695
|
async markOutboundDelivered(id) {
|
|
@@ -1815,18 +1702,18 @@ var MessageQueue = class {
|
|
|
1815
1702
|
subscribe(filter, callback) {
|
|
1816
1703
|
const id = `sub_${++this.subCounter}`;
|
|
1817
1704
|
this.subscriptions.set(id, { id, filter, callback });
|
|
1818
|
-
|
|
1705
|
+
logger5.debug("Subscription added", { id });
|
|
1819
1706
|
return id;
|
|
1820
1707
|
}
|
|
1821
1708
|
unsubscribe(subscriptionId) {
|
|
1822
1709
|
this.subscriptions.delete(subscriptionId);
|
|
1823
|
-
|
|
1710
|
+
logger5.debug("Subscription removed", { id: subscriptionId });
|
|
1824
1711
|
}
|
|
1825
1712
|
notifySubscribers(msg) {
|
|
1826
1713
|
for (const sub of this.subscriptions.values()) {
|
|
1827
1714
|
if (this.matchesSubscriptionFilter(msg, sub.filter)) {
|
|
1828
1715
|
Promise.resolve(sub.callback(msg)).catch((err) => {
|
|
1829
|
-
|
|
1716
|
+
logger5.warn("Subscription callback error", { id: sub.id, error: err.message });
|
|
1830
1717
|
});
|
|
1831
1718
|
}
|
|
1832
1719
|
}
|
|
@@ -1841,6 +1728,11 @@ var MessageQueue = class {
|
|
|
1841
1728
|
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
1842
1729
|
}
|
|
1843
1730
|
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
1731
|
+
if (filter.replyTo) {
|
|
1732
|
+
const replyIds = Array.isArray(filter.replyTo) ? filter.replyTo : [filter.replyTo];
|
|
1733
|
+
if (!msg.envelope.replyTo || !replyIds.includes(msg.envelope.replyTo)) return false;
|
|
1734
|
+
}
|
|
1735
|
+
if (filter.threadId && msg.envelope.threadId !== filter.threadId) return false;
|
|
1844
1736
|
return true;
|
|
1845
1737
|
}
|
|
1846
1738
|
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
@@ -1925,12 +1817,11 @@ function getTierConfig(trustScore, tiers) {
|
|
|
1925
1817
|
}
|
|
1926
1818
|
|
|
1927
1819
|
// src/messaging/defense.ts
|
|
1928
|
-
var
|
|
1820
|
+
var logger6 = createLogger("defense");
|
|
1929
1821
|
var DefenseMiddleware = class {
|
|
1930
1822
|
trust;
|
|
1931
1823
|
storage;
|
|
1932
1824
|
minTrustScore;
|
|
1933
|
-
autoBlockThreshold;
|
|
1934
1825
|
tiers;
|
|
1935
1826
|
seenTtlMs;
|
|
1936
1827
|
// In-memory LRU-style seen cache (backed by LevelDB for persistence)
|
|
@@ -1943,7 +1834,6 @@ var DefenseMiddleware = class {
|
|
|
1943
1834
|
this.trust = config.trustSystem;
|
|
1944
1835
|
this.storage = config.storage;
|
|
1945
1836
|
this.minTrustScore = config.minTrustScore ?? 0;
|
|
1946
|
-
this.autoBlockThreshold = config.autoBlockThreshold ?? 0.1;
|
|
1947
1837
|
this.tiers = config.rateLimitTiers ?? DEFAULT_RATE_LIMIT_TIERS;
|
|
1948
1838
|
this.seenTtlMs = config.seenTtlMs ?? 60 * 60 * 1e3;
|
|
1949
1839
|
}
|
|
@@ -1956,36 +1846,47 @@ var DefenseMiddleware = class {
|
|
|
1956
1846
|
const did = envelope.from;
|
|
1957
1847
|
if (await this.isAllowed(did)) {
|
|
1958
1848
|
this.markAsSeen(envelope.id);
|
|
1959
|
-
return { allowed: true };
|
|
1849
|
+
return { allowed: true, trustStatus: "allowed" };
|
|
1960
1850
|
}
|
|
1961
1851
|
if (await this.isBlocked(did)) {
|
|
1962
|
-
|
|
1852
|
+
logger6.debug("Message rejected: blocked", { id: envelope.id, from: did });
|
|
1963
1853
|
return { allowed: false, reason: "blocked" };
|
|
1964
1854
|
}
|
|
1855
|
+
if (this.trust.isRateLimited(did)) {
|
|
1856
|
+
logger6.debug("Message rejected: sybil rate limited", { id: envelope.id, from: did });
|
|
1857
|
+
return { allowed: false, reason: "rate_limited" };
|
|
1858
|
+
}
|
|
1965
1859
|
if (this.hasSeen(envelope.id)) {
|
|
1966
|
-
|
|
1860
|
+
logger6.debug("Message rejected: duplicate", { id: envelope.id });
|
|
1967
1861
|
return { allowed: false, reason: "duplicate" };
|
|
1968
1862
|
}
|
|
1969
1863
|
let trustScore = 0;
|
|
1864
|
+
let trustStatus = "unknown";
|
|
1970
1865
|
try {
|
|
1971
1866
|
const score = await this.trust.getTrustScore(did);
|
|
1972
1867
|
trustScore = score.interactionScore;
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1868
|
+
trustStatus = score.status;
|
|
1869
|
+
const totalInteractions = score.totalInteractions ?? 0;
|
|
1870
|
+
const recentFailureRate = 1 - (score.recentSuccessRate ?? 1);
|
|
1871
|
+
if (totalInteractions >= 10 && recentFailureRate > 0.5) {
|
|
1872
|
+
logger6.warn("Suspicious agent detected", { did, recentFailureRate: recentFailureRate.toFixed(2), totalInteractions });
|
|
1873
|
+
trustScore = 0;
|
|
1874
|
+
}
|
|
1875
|
+
if (totalInteractions >= 20 && recentFailureRate > 0.7) {
|
|
1876
|
+
logger6.warn("Auto-blocking high-failure-rate agent", { did, recentFailureRate: recentFailureRate.toFixed(2), totalInteractions });
|
|
1877
|
+
await this.blockAgent(did, `Auto-blocked: ${(recentFailureRate * 100).toFixed(0)}% failure rate over last 20 interactions`);
|
|
1977
1878
|
return { allowed: false, reason: "blocked" };
|
|
1978
1879
|
}
|
|
1979
1880
|
if (trustScore < this.minTrustScore) {
|
|
1980
|
-
|
|
1881
|
+
logger6.debug("Message rejected: trust too low", { id: envelope.id, trustScore });
|
|
1981
1882
|
return { allowed: false, reason: "trust_too_low", trustScore };
|
|
1982
1883
|
}
|
|
1983
1884
|
} catch (err) {
|
|
1984
|
-
|
|
1885
|
+
logger6.warn("Trust score lookup failed, using 0", { did, error: err.message });
|
|
1985
1886
|
}
|
|
1986
1887
|
const rateLimitResult = await this.checkRateLimit(did, trustScore);
|
|
1987
1888
|
if (!rateLimitResult.allowed) {
|
|
1988
|
-
|
|
1889
|
+
logger6.debug("Message rejected: rate limited", {
|
|
1989
1890
|
id: envelope.id,
|
|
1990
1891
|
from: did,
|
|
1991
1892
|
resetTime: rateLimitResult.resetTime
|
|
@@ -1998,16 +1899,16 @@ var DefenseMiddleware = class {
|
|
|
1998
1899
|
};
|
|
1999
1900
|
}
|
|
2000
1901
|
this.markAsSeen(envelope.id);
|
|
2001
|
-
return { allowed: true, trustScore, remainingTokens: rateLimitResult.remaining };
|
|
1902
|
+
return { allowed: true, trustScore, trustStatus, remainingTokens: rateLimitResult.remaining };
|
|
2002
1903
|
}
|
|
2003
1904
|
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
2004
1905
|
async blockAgent(did, reason, blockedBy = "local") {
|
|
2005
1906
|
await this.storage.putBlock({ did, reason, blockedAt: Date.now(), blockedBy });
|
|
2006
|
-
|
|
1907
|
+
logger6.info("Agent blocked", { did, reason });
|
|
2007
1908
|
}
|
|
2008
1909
|
async unblockAgent(did) {
|
|
2009
1910
|
await this.storage.deleteBlock(did);
|
|
2010
|
-
|
|
1911
|
+
logger6.info("Agent unblocked", { did });
|
|
2011
1912
|
}
|
|
2012
1913
|
async isBlocked(did) {
|
|
2013
1914
|
return await this.storage.getBlock(did) !== null;
|
|
@@ -2015,11 +1916,11 @@ var DefenseMiddleware = class {
|
|
|
2015
1916
|
// ─── Allowlist ────────────────────────────────────────────────────────────
|
|
2016
1917
|
async allowAgent(did, note) {
|
|
2017
1918
|
await this.storage.putAllow({ did, addedAt: Date.now(), note });
|
|
2018
|
-
|
|
1919
|
+
logger6.info("Agent allowlisted", { did });
|
|
2019
1920
|
}
|
|
2020
1921
|
async removeFromAllowlist(did) {
|
|
2021
1922
|
await this.storage.deleteAllow(did);
|
|
2022
|
-
|
|
1923
|
+
logger6.info("Agent removed from allowlist", { did });
|
|
2023
1924
|
}
|
|
2024
1925
|
async isAllowed(did) {
|
|
2025
1926
|
return await this.storage.getAllow(did) !== null;
|
|
@@ -2088,41 +1989,51 @@ var DefenseMiddleware = class {
|
|
|
2088
1989
|
// src/trust/trust-score.ts
|
|
2089
1990
|
var TrustMetrics = class {
|
|
2090
1991
|
/**
|
|
2091
|
-
* Calculate trust score from interaction history
|
|
1992
|
+
* Calculate trust score from interaction history.
|
|
1993
|
+
* @param endorsementScore - Average endorsement score (0-1), not count
|
|
2092
1994
|
*/
|
|
2093
|
-
calculateScore(stats,
|
|
2094
|
-
const
|
|
2095
|
-
const interactionScore = stats.successRate *
|
|
2096
|
-
const
|
|
1995
|
+
calculateScore(stats, endorsementScore, uptime) {
|
|
1996
|
+
const maturityWeight = Math.min(stats.totalInteractions / 50, 1);
|
|
1997
|
+
const interactionScore = stats.successRate * maturityWeight;
|
|
1998
|
+
const status = this.deriveStatus(stats);
|
|
2097
1999
|
return {
|
|
2098
2000
|
interactionScore,
|
|
2099
|
-
endorsements,
|
|
2100
|
-
|
|
2001
|
+
endorsements: 0,
|
|
2002
|
+
// kept for backwards compat
|
|
2003
|
+
endorsementScore,
|
|
2004
|
+
completionRate: stats.successRate,
|
|
2101
2005
|
responseTime: stats.avgResponseTime,
|
|
2102
2006
|
uptime,
|
|
2103
2007
|
lastUpdated: Date.now(),
|
|
2104
|
-
totalInteractions: stats.totalInteractions
|
|
2008
|
+
totalInteractions: stats.totalInteractions,
|
|
2009
|
+
recentSuccessRate: stats.recentSuccessRate,
|
|
2010
|
+
status
|
|
2105
2011
|
};
|
|
2106
2012
|
}
|
|
2013
|
+
deriveStatus(stats) {
|
|
2014
|
+
if (stats.totalInteractions === 0) return "unknown";
|
|
2015
|
+
const recentFailureRate = 1 - stats.recentSuccessRate;
|
|
2016
|
+
if (stats.totalInteractions >= 10 && recentFailureRate > 0.5) return "suspicious";
|
|
2017
|
+
return "known";
|
|
2018
|
+
}
|
|
2107
2019
|
/**
|
|
2108
2020
|
* Calculate overall trust level (0-1)
|
|
2021
|
+
* @param score - TrustScore
|
|
2022
|
+
* @param endorsementScore - Average endorsement score (0-1)
|
|
2109
2023
|
*/
|
|
2110
2024
|
calculateOverallTrust(score) {
|
|
2111
2025
|
const weights = {
|
|
2112
|
-
interaction: 0.
|
|
2113
|
-
endorsement: 0.
|
|
2114
|
-
completion: 0.2,
|
|
2115
|
-
uptime: 0.2
|
|
2026
|
+
interaction: 0.6,
|
|
2027
|
+
endorsement: 0.4
|
|
2116
2028
|
};
|
|
2117
|
-
|
|
2118
|
-
return score.interactionScore * weights.interaction + endorsementScore * weights.endorsement + score.completionRate * weights.completion + score.uptime * weights.uptime;
|
|
2029
|
+
return score.interactionScore * weights.interaction + score.endorsementScore * weights.endorsement;
|
|
2119
2030
|
}
|
|
2120
2031
|
/**
|
|
2121
2032
|
* Get trust level category
|
|
2122
2033
|
*/
|
|
2123
2034
|
getTrustLevel(score) {
|
|
2124
2035
|
const overall = this.calculateOverallTrust(score);
|
|
2125
|
-
if (score.
|
|
2036
|
+
if (score.totalInteractions === 0) return "new";
|
|
2126
2037
|
if (overall < 0.3) return "low";
|
|
2127
2038
|
if (overall < 0.6) return "medium";
|
|
2128
2039
|
if (overall < 0.8) return "high";
|
|
@@ -2134,12 +2045,8 @@ var TrustMetrics = class {
|
|
|
2134
2045
|
shouldRateLimit(score, agentAge) {
|
|
2135
2046
|
const overall = this.calculateOverallTrust(score);
|
|
2136
2047
|
const ONE_DAY = 24 * 60 * 60 * 1e3;
|
|
2137
|
-
if (agentAge < ONE_DAY && overall < 0.3)
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
if (overall < 0.1) {
|
|
2141
|
-
return true;
|
|
2142
|
-
}
|
|
2048
|
+
if (agentAge < ONE_DAY && overall < 0.3) return true;
|
|
2049
|
+
if (overall < 0.1) return true;
|
|
2143
2050
|
return false;
|
|
2144
2051
|
}
|
|
2145
2052
|
};
|
|
@@ -2147,14 +2054,17 @@ function createDefaultTrustScore() {
|
|
|
2147
2054
|
return {
|
|
2148
2055
|
interactionScore: 0,
|
|
2149
2056
|
endorsements: 0,
|
|
2057
|
+
endorsementScore: 0,
|
|
2150
2058
|
completionRate: 0,
|
|
2151
2059
|
responseTime: 0,
|
|
2152
2060
|
uptime: 0,
|
|
2153
2061
|
lastUpdated: Date.now(),
|
|
2154
|
-
totalInteractions: 0
|
|
2062
|
+
totalInteractions: 0,
|
|
2063
|
+
recentSuccessRate: 1,
|
|
2064
|
+
status: "unknown"
|
|
2155
2065
|
};
|
|
2156
2066
|
}
|
|
2157
|
-
var
|
|
2067
|
+
var logger7 = createLogger("interaction-history");
|
|
2158
2068
|
var InteractionHistory = class {
|
|
2159
2069
|
db;
|
|
2160
2070
|
constructor(dbPath) {
|
|
@@ -2165,14 +2075,14 @@ var InteractionHistory = class {
|
|
|
2165
2075
|
*/
|
|
2166
2076
|
async open() {
|
|
2167
2077
|
await this.db.open();
|
|
2168
|
-
|
|
2078
|
+
logger7.info("Interaction history database opened", { path: this.db.location });
|
|
2169
2079
|
}
|
|
2170
2080
|
/**
|
|
2171
2081
|
* Close database connection
|
|
2172
2082
|
*/
|
|
2173
2083
|
async close() {
|
|
2174
2084
|
await this.db.close();
|
|
2175
|
-
|
|
2085
|
+
logger7.info("Interaction history database closed");
|
|
2176
2086
|
}
|
|
2177
2087
|
/**
|
|
2178
2088
|
* Record an interaction
|
|
@@ -2180,7 +2090,7 @@ var InteractionHistory = class {
|
|
|
2180
2090
|
async record(interaction) {
|
|
2181
2091
|
const key = `interaction:${interaction.agentDid}:${interaction.timestamp}`;
|
|
2182
2092
|
await this.db.put(key, interaction);
|
|
2183
|
-
|
|
2093
|
+
logger7.debug("Recorded interaction", { agentDid: interaction.agentDid, type: interaction.type });
|
|
2184
2094
|
}
|
|
2185
2095
|
/**
|
|
2186
2096
|
* Get interaction history for an agent
|
|
@@ -2199,7 +2109,7 @@ var InteractionHistory = class {
|
|
|
2199
2109
|
interactions.push(value);
|
|
2200
2110
|
}
|
|
2201
2111
|
} catch (error) {
|
|
2202
|
-
|
|
2112
|
+
logger7.error("Failed to get interaction history", { agentDid, error });
|
|
2203
2113
|
}
|
|
2204
2114
|
return interactions;
|
|
2205
2115
|
}
|
|
@@ -2212,15 +2122,21 @@ var InteractionHistory = class {
|
|
|
2212
2122
|
return {
|
|
2213
2123
|
totalInteractions: 0,
|
|
2214
2124
|
successRate: 0,
|
|
2125
|
+
recentSuccessRate: 1,
|
|
2126
|
+
// No history → assume good (don't penalize new agents)
|
|
2215
2127
|
avgResponseTime: 0,
|
|
2216
2128
|
lastInteraction: 0
|
|
2217
2129
|
};
|
|
2218
2130
|
}
|
|
2219
2131
|
const successCount = history.filter((i) => i.success).length;
|
|
2220
2132
|
const totalResponseTime = history.reduce((sum, i) => sum + i.responseTime, 0);
|
|
2133
|
+
const recent = history.slice(0, 20);
|
|
2134
|
+
const recentSuccessCount = recent.filter((i) => i.success).length;
|
|
2135
|
+
const recentSuccessRate = recentSuccessCount / recent.length;
|
|
2221
2136
|
return {
|
|
2222
2137
|
totalInteractions: history.length,
|
|
2223
2138
|
successRate: successCount / history.length,
|
|
2139
|
+
recentSuccessRate,
|
|
2224
2140
|
avgResponseTime: totalResponseTime / history.length,
|
|
2225
2141
|
lastInteraction: history[0].timestamp
|
|
2226
2142
|
};
|
|
@@ -2238,7 +2154,7 @@ var InteractionHistory = class {
|
|
|
2238
2154
|
}
|
|
2239
2155
|
}
|
|
2240
2156
|
} catch (error) {
|
|
2241
|
-
|
|
2157
|
+
logger7.error("Failed to get all agents", { error });
|
|
2242
2158
|
}
|
|
2243
2159
|
return Array.from(agents);
|
|
2244
2160
|
}
|
|
@@ -2255,7 +2171,7 @@ var InteractionHistory = class {
|
|
|
2255
2171
|
keysToDelete.push(key);
|
|
2256
2172
|
}
|
|
2257
2173
|
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
2258
|
-
|
|
2174
|
+
logger7.info("Deleted interaction history", { agentDid, count: keysToDelete.length });
|
|
2259
2175
|
}
|
|
2260
2176
|
/**
|
|
2261
2177
|
* Clean up old interactions (older than 90 days)
|
|
@@ -2270,21 +2186,65 @@ var InteractionHistory = class {
|
|
|
2270
2186
|
}
|
|
2271
2187
|
if (keysToDelete.length > 0) {
|
|
2272
2188
|
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
2273
|
-
|
|
2189
|
+
logger7.info("Cleaned up old interactions", { count: keysToDelete.length });
|
|
2274
2190
|
}
|
|
2275
2191
|
return keysToDelete.length;
|
|
2276
2192
|
}
|
|
2277
2193
|
};
|
|
2278
2194
|
|
|
2279
2195
|
// src/trust/endorsement.ts
|
|
2280
|
-
var
|
|
2196
|
+
var logger8 = createLogger("endorsement");
|
|
2197
|
+
var FAST_DOMAINS = ["translation", "transcription", "data-entry", "moderation"];
|
|
2198
|
+
var SLOW_DOMAINS = ["research", "architecture", "security-audit", "legal-review"];
|
|
2199
|
+
function getDefaultExpiration(domain) {
|
|
2200
|
+
if (!domain) return 180;
|
|
2201
|
+
if (FAST_DOMAINS.includes(domain)) return 90;
|
|
2202
|
+
if (SLOW_DOMAINS.includes(domain)) return 365;
|
|
2203
|
+
return 180;
|
|
2204
|
+
}
|
|
2205
|
+
function validateDomain(domain) {
|
|
2206
|
+
const pattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2207
|
+
return pattern.test(domain) && domain.length <= 64;
|
|
2208
|
+
}
|
|
2281
2209
|
var EndorsementManager = class {
|
|
2282
2210
|
constructor(db, getPublicKey) {
|
|
2283
2211
|
this.db = db;
|
|
2284
2212
|
this.getPublicKey = getPublicKey;
|
|
2285
2213
|
}
|
|
2286
2214
|
/**
|
|
2287
|
-
* Create an endorsement
|
|
2215
|
+
* Create an endorsement (v2)
|
|
2216
|
+
*/
|
|
2217
|
+
async endorseV2(fromDid, toDid, score, reason, signFn, domain, expires) {
|
|
2218
|
+
if (score < 0 || score > 1) {
|
|
2219
|
+
throw new Error("Score must be between 0 and 1");
|
|
2220
|
+
}
|
|
2221
|
+
if (domain && !validateDomain(domain)) {
|
|
2222
|
+
throw new Error("Invalid domain format. Must match ^[a-z0-9]+(-[a-z0-9]+)*$ and be <= 64 chars");
|
|
2223
|
+
}
|
|
2224
|
+
const timestamp = Date.now();
|
|
2225
|
+
const defaultExpires = timestamp + getDefaultExpiration(domain) * 24 * 60 * 60 * 1e3;
|
|
2226
|
+
const endorsement = {
|
|
2227
|
+
version: 2,
|
|
2228
|
+
from: fromDid,
|
|
2229
|
+
to: toDid,
|
|
2230
|
+
score,
|
|
2231
|
+
domain,
|
|
2232
|
+
reason,
|
|
2233
|
+
timestamp,
|
|
2234
|
+
expires: expires || defaultExpires
|
|
2235
|
+
};
|
|
2236
|
+
const data = new TextEncoder().encode(JSON.stringify(endorsement));
|
|
2237
|
+
const signatureBytes = await signFn(data);
|
|
2238
|
+
const signature = Buffer.from(signatureBytes).toString("hex");
|
|
2239
|
+
const signedEndorsement = {
|
|
2240
|
+
...endorsement,
|
|
2241
|
+
signature
|
|
2242
|
+
};
|
|
2243
|
+
logger8.info("Created endorsement v2", { from: fromDid, to: toDid, score, domain });
|
|
2244
|
+
return signedEndorsement;
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* Create an endorsement (v1 - legacy)
|
|
2288
2248
|
*/
|
|
2289
2249
|
async endorse(fromDid, toDid, score, reason, signFn) {
|
|
2290
2250
|
if (score < 0 || score > 1) {
|
|
@@ -2304,11 +2264,26 @@ var EndorsementManager = class {
|
|
|
2304
2264
|
...endorsement,
|
|
2305
2265
|
signature
|
|
2306
2266
|
};
|
|
2307
|
-
|
|
2267
|
+
logger8.info("Created endorsement", { from: fromDid, to: toDid, score });
|
|
2308
2268
|
return signedEndorsement;
|
|
2309
2269
|
}
|
|
2310
2270
|
/**
|
|
2311
|
-
* Verify endorsement signature
|
|
2271
|
+
* Verify endorsement signature (v2)
|
|
2272
|
+
*/
|
|
2273
|
+
async verifyV2(endorsement, verifyFn) {
|
|
2274
|
+
try {
|
|
2275
|
+
const { signature, ...endorsementWithoutSig } = endorsement;
|
|
2276
|
+
const data = new TextEncoder().encode(JSON.stringify(endorsementWithoutSig));
|
|
2277
|
+
const signatureBytes = Buffer.from(signature, "hex");
|
|
2278
|
+
const publicKey = await this.getPublicKey(endorsement.from);
|
|
2279
|
+
return await verifyFn(signatureBytes, data, publicKey);
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
logger8.error("Failed to verify endorsement v2", { error });
|
|
2282
|
+
return false;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Verify endorsement signature (v1)
|
|
2312
2287
|
*/
|
|
2313
2288
|
async verify(endorsement, verifyFn) {
|
|
2314
2289
|
try {
|
|
@@ -2318,17 +2293,36 @@ var EndorsementManager = class {
|
|
|
2318
2293
|
const publicKey = await this.getPublicKey(endorsement.from);
|
|
2319
2294
|
return await verifyFn(signatureBytes, data, publicKey);
|
|
2320
2295
|
} catch (error) {
|
|
2321
|
-
|
|
2296
|
+
logger8.error("Failed to verify endorsement", { error });
|
|
2322
2297
|
return false;
|
|
2323
2298
|
}
|
|
2324
2299
|
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Upgrade v1 endorsement to v2
|
|
2302
|
+
*/
|
|
2303
|
+
upgradeEndorsement(e) {
|
|
2304
|
+
return {
|
|
2305
|
+
version: 2,
|
|
2306
|
+
from: e.from,
|
|
2307
|
+
to: e.to,
|
|
2308
|
+
score: e.score,
|
|
2309
|
+
domain: void 0,
|
|
2310
|
+
// v1 endorsements are domain-agnostic
|
|
2311
|
+
reason: e.reason,
|
|
2312
|
+
timestamp: e.timestamp,
|
|
2313
|
+
expires: e.timestamp + 90 * 24 * 60 * 60 * 1e3,
|
|
2314
|
+
// 90-day default
|
|
2315
|
+
signature: e.signature
|
|
2316
|
+
// v1 signature remains valid
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2325
2319
|
/**
|
|
2326
2320
|
* Publish endorsement to local database
|
|
2327
2321
|
*/
|
|
2328
2322
|
async publish(endorsement) {
|
|
2329
2323
|
const key = `endorsement:${endorsement.to}:${endorsement.from}`;
|
|
2330
2324
|
await this.db.put(key, endorsement);
|
|
2331
|
-
|
|
2325
|
+
logger8.info("Published endorsement", { from: endorsement.from, to: endorsement.to });
|
|
2332
2326
|
}
|
|
2333
2327
|
/**
|
|
2334
2328
|
* Get all endorsements for an agent
|
|
@@ -2344,7 +2338,7 @@ var EndorsementManager = class {
|
|
|
2344
2338
|
endorsements.push(value);
|
|
2345
2339
|
}
|
|
2346
2340
|
} catch (error) {
|
|
2347
|
-
|
|
2341
|
+
logger8.error("Failed to get endorsements", { agentDid, error });
|
|
2348
2342
|
}
|
|
2349
2343
|
return endorsements;
|
|
2350
2344
|
}
|
|
@@ -2360,7 +2354,7 @@ var EndorsementManager = class {
|
|
|
2360
2354
|
}
|
|
2361
2355
|
}
|
|
2362
2356
|
} catch (error) {
|
|
2363
|
-
|
|
2357
|
+
logger8.error("Failed to get endorsements by agent", { fromDid, error });
|
|
2364
2358
|
}
|
|
2365
2359
|
return endorsements;
|
|
2366
2360
|
}
|
|
@@ -2381,10 +2375,29 @@ var EndorsementManager = class {
|
|
|
2381
2375
|
async deleteEndorsement(fromDid, toDid) {
|
|
2382
2376
|
const key = `endorsement:${toDid}:${fromDid}`;
|
|
2383
2377
|
await this.db.del(key);
|
|
2384
|
-
|
|
2378
|
+
logger8.info("Deleted endorsement", { from: fromDid, to: toDid });
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Publish endorsement to relay (CVP-0017)
|
|
2382
|
+
*/
|
|
2383
|
+
async publishToRelay(relay, endorsement) {
|
|
2384
|
+
logger8.info("Publishing endorsement to relay", { from: endorsement.from, to: endorsement.to });
|
|
2385
|
+
return await relay.publishEndorsement(endorsement);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Query endorsements from relay (CVP-0017)
|
|
2389
|
+
*/
|
|
2390
|
+
async queryFromRelay(relay, target, domain, since) {
|
|
2391
|
+
logger8.info("Querying endorsements from relay", { target, domain });
|
|
2392
|
+
const result = await relay.queryTrust(target, domain, since);
|
|
2393
|
+
return {
|
|
2394
|
+
endorsements: result.endorsements,
|
|
2395
|
+
total: result.endorsementCount ?? result.endorsements.length,
|
|
2396
|
+
averageScore: result.averageScore ?? (result.endorsements.length > 0 ? result.endorsements.reduce((sum, e) => sum + e.score, 0) / result.endorsements.length : 0)
|
|
2397
|
+
};
|
|
2385
2398
|
}
|
|
2386
2399
|
};
|
|
2387
|
-
var
|
|
2400
|
+
var logger9 = createLogger("sybil-defense");
|
|
2388
2401
|
var SybilDefense = class {
|
|
2389
2402
|
rateLimits = /* @__PURE__ */ new Map();
|
|
2390
2403
|
peerFirstSeen = /* @__PURE__ */ new Map();
|
|
@@ -2414,7 +2427,7 @@ var SybilDefense = class {
|
|
|
2414
2427
|
verifyChallenge(solution) {
|
|
2415
2428
|
const { challenge, solution: solutionNonce } = solution;
|
|
2416
2429
|
if (Date.now() - challenge.timestamp > 60 * 60 * 1e3) {
|
|
2417
|
-
|
|
2430
|
+
logger9.warn("Challenge expired", { did: challenge.did });
|
|
2418
2431
|
return false;
|
|
2419
2432
|
}
|
|
2420
2433
|
const data = `${challenge.did}:${challenge.nonce}:${solutionNonce}`;
|
|
@@ -2422,9 +2435,9 @@ var SybilDefense = class {
|
|
|
2422
2435
|
const leadingZeros = this.countLeadingZeroBits(hash);
|
|
2423
2436
|
const valid = leadingZeros >= challenge.difficulty;
|
|
2424
2437
|
if (valid) {
|
|
2425
|
-
|
|
2438
|
+
logger9.info("Challenge verified", { did: challenge.did, leadingZeros });
|
|
2426
2439
|
} else {
|
|
2427
|
-
|
|
2440
|
+
logger9.warn("Challenge failed", { did: challenge.did, leadingZeros, required: challenge.difficulty });
|
|
2428
2441
|
}
|
|
2429
2442
|
return valid;
|
|
2430
2443
|
}
|
|
@@ -2464,7 +2477,7 @@ var SybilDefense = class {
|
|
|
2464
2477
|
record.requests = record.requests.filter(
|
|
2465
2478
|
(t) => now - t < this.RATE_LIMIT_WINDOW
|
|
2466
2479
|
);
|
|
2467
|
-
|
|
2480
|
+
logger9.debug("Recorded request", { did, count: record.requests.length });
|
|
2468
2481
|
}
|
|
2469
2482
|
/**
|
|
2470
2483
|
* Get peer trust level based on age
|
|
@@ -2489,7 +2502,7 @@ var SybilDefense = class {
|
|
|
2489
2502
|
recordPeerSeen(peerId) {
|
|
2490
2503
|
if (!this.peerFirstSeen.has(peerId)) {
|
|
2491
2504
|
this.peerFirstSeen.set(peerId, Date.now());
|
|
2492
|
-
|
|
2505
|
+
logger9.debug("Recorded new peer", { peerId });
|
|
2493
2506
|
}
|
|
2494
2507
|
}
|
|
2495
2508
|
/**
|
|
@@ -2503,7 +2516,7 @@ var SybilDefense = class {
|
|
|
2503
2516
|
this.rateLimits.delete(did);
|
|
2504
2517
|
}
|
|
2505
2518
|
}
|
|
2506
|
-
|
|
2519
|
+
logger9.info("Cleaned up Sybil defense records", {
|
|
2507
2520
|
rateLimits: this.rateLimits.size,
|
|
2508
2521
|
peers: this.peerFirstSeen.size
|
|
2509
2522
|
});
|
|
@@ -2536,7 +2549,342 @@ var SybilDefense = class {
|
|
|
2536
2549
|
return count;
|
|
2537
2550
|
}
|
|
2538
2551
|
};
|
|
2539
|
-
|
|
2552
|
+
|
|
2553
|
+
// src/trust/trust-computer.ts
|
|
2554
|
+
var logger10 = createLogger("trust-computer");
|
|
2555
|
+
var DEFAULT_CONFIG = {
|
|
2556
|
+
seedPeers: [],
|
|
2557
|
+
maxRecursionDepth: 3,
|
|
2558
|
+
decayHalfLife: {
|
|
2559
|
+
default: 90,
|
|
2560
|
+
translation: 30,
|
|
2561
|
+
transcription: 30,
|
|
2562
|
+
"data-entry": 30,
|
|
2563
|
+
moderation: 30,
|
|
2564
|
+
research: 180,
|
|
2565
|
+
architecture: 180,
|
|
2566
|
+
"security-audit": 180,
|
|
2567
|
+
"legal-review": 180
|
|
2568
|
+
},
|
|
2569
|
+
localInteractionWeight: 0.6,
|
|
2570
|
+
networkEndorsementWeight: 0.4
|
|
2571
|
+
};
|
|
2572
|
+
var TrustComputer = class {
|
|
2573
|
+
config;
|
|
2574
|
+
cache = /* @__PURE__ */ new Map();
|
|
2575
|
+
CACHE_TTL = 5 * 60 * 1e3;
|
|
2576
|
+
// 5 minutes
|
|
2577
|
+
// Cached SCC results: endorser DID → penalty factor (1.0 = no penalty, 0.1 = collusion)
|
|
2578
|
+
sccPenaltyCache = /* @__PURE__ */ new Map();
|
|
2579
|
+
sccCacheExpiresAt = 0;
|
|
2580
|
+
SCC_CACHE_TTL = 5 * 60 * 1e3;
|
|
2581
|
+
// 5 minutes
|
|
2582
|
+
// Endorsements by target DID, used for recursive weight lookup
|
|
2583
|
+
endorsementsByTarget = /* @__PURE__ */ new Map();
|
|
2584
|
+
constructor(config = {}) {
|
|
2585
|
+
this.config = {
|
|
2586
|
+
...DEFAULT_CONFIG,
|
|
2587
|
+
...config,
|
|
2588
|
+
decayHalfLife: {
|
|
2589
|
+
...DEFAULT_CONFIG.decayHalfLife,
|
|
2590
|
+
...config.decayHalfLife
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
if (this.config.maxRecursionDepth > 6) {
|
|
2594
|
+
logger10.warn("Max recursion depth capped at 6", { requested: this.config.maxRecursionDepth });
|
|
2595
|
+
this.config.maxRecursionDepth = 6;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Compute trust score for a target DID.
|
|
2600
|
+
* allEndorsements: flat list of all endorsements known to the caller (used for recursive weight lookup).
|
|
2601
|
+
*/
|
|
2602
|
+
compute(target, endorsements, localInteractionScore = 0, localInteractionCount = 0, domain, allEndorsements) {
|
|
2603
|
+
const cached = this.cache.get(this.getCacheKey(target, domain));
|
|
2604
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
2605
|
+
logger10.debug("Trust score cache hit", { target, domain });
|
|
2606
|
+
return cached.result;
|
|
2607
|
+
}
|
|
2608
|
+
if (allEndorsements && allEndorsements.length > 0) {
|
|
2609
|
+
this.endorsementsByTarget.clear();
|
|
2610
|
+
for (const e of allEndorsements) {
|
|
2611
|
+
if (!this.endorsementsByTarget.has(e.to)) {
|
|
2612
|
+
this.endorsementsByTarget.set(e.to, []);
|
|
2613
|
+
}
|
|
2614
|
+
this.endorsementsByTarget.get(e.to).push(e);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const allEnds = allEndorsements ?? endorsements;
|
|
2618
|
+
if (Date.now() > this.sccCacheExpiresAt) {
|
|
2619
|
+
this.rebuildSccPenaltyCache(allEnds);
|
|
2620
|
+
}
|
|
2621
|
+
const filteredEndorsements = this.filterByDomain(endorsements, domain);
|
|
2622
|
+
const networkScore = this.computeNetworkTrust(target, filteredEndorsements, domain);
|
|
2623
|
+
const alpha = Math.min(localInteractionCount / 20, 0.8);
|
|
2624
|
+
const score = alpha * localInteractionScore + (1 - alpha) * networkScore;
|
|
2625
|
+
const collusionDetected = this.detectCollusion(target, allEnds);
|
|
2626
|
+
const result = {
|
|
2627
|
+
score,
|
|
2628
|
+
localScore: localInteractionScore,
|
|
2629
|
+
networkScore,
|
|
2630
|
+
endorsementCount: filteredEndorsements.length,
|
|
2631
|
+
collusionDetected,
|
|
2632
|
+
computedAt: Date.now()
|
|
2633
|
+
};
|
|
2634
|
+
this.cache.set(this.getCacheKey(target, domain), {
|
|
2635
|
+
result,
|
|
2636
|
+
expiresAt: Date.now() + this.CACHE_TTL
|
|
2637
|
+
});
|
|
2638
|
+
logger10.debug("Computed trust score", { target, domain, score, networkScore, collusionDetected });
|
|
2639
|
+
return result;
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Compute network trust score using EigenTrust-lite
|
|
2643
|
+
*/
|
|
2644
|
+
computeNetworkTrust(target, endorsements, domain, depth = 0, visited = /* @__PURE__ */ new Set()) {
|
|
2645
|
+
if (endorsements.length === 0) {
|
|
2646
|
+
return 0;
|
|
2647
|
+
}
|
|
2648
|
+
if (depth >= this.config.maxRecursionDepth) {
|
|
2649
|
+
return 0.1;
|
|
2650
|
+
}
|
|
2651
|
+
if (visited.has(target)) {
|
|
2652
|
+
return 0.1;
|
|
2653
|
+
}
|
|
2654
|
+
visited.add(target);
|
|
2655
|
+
const now = Date.now();
|
|
2656
|
+
let weightedSum = 0;
|
|
2657
|
+
let totalWeight = 0;
|
|
2658
|
+
for (const endorsement of endorsements) {
|
|
2659
|
+
if (endorsement.expires && endorsement.expires < now) {
|
|
2660
|
+
continue;
|
|
2661
|
+
}
|
|
2662
|
+
const endorserWeight = this.getEndorserWeight(
|
|
2663
|
+
endorsement.from,
|
|
2664
|
+
domain,
|
|
2665
|
+
depth + 1,
|
|
2666
|
+
visited
|
|
2667
|
+
);
|
|
2668
|
+
const timeDecay = this.computeTimeDecay(endorsement, domain);
|
|
2669
|
+
const collusionPenalty = this.getCollusionPenalty(endorsement.from);
|
|
2670
|
+
const baseWeight = endorserWeight * collusionPenalty;
|
|
2671
|
+
weightedSum += endorsement.score * timeDecay * baseWeight;
|
|
2672
|
+
totalWeight += baseWeight;
|
|
2673
|
+
}
|
|
2674
|
+
return totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Get endorser weight (recursive EigenTrust-lite).
|
|
2678
|
+
* Uses endorsementsByTarget populated by compute() for local graph traversal.
|
|
2679
|
+
*/
|
|
2680
|
+
getEndorserWeight(endorser, domain, depth, visited) {
|
|
2681
|
+
if (this.config.seedPeers.includes(endorser)) {
|
|
2682
|
+
return 1;
|
|
2683
|
+
}
|
|
2684
|
+
if (depth >= this.config.maxRecursionDepth) {
|
|
2685
|
+
return 0.1;
|
|
2686
|
+
}
|
|
2687
|
+
if (visited.has(endorser)) {
|
|
2688
|
+
return 0.1;
|
|
2689
|
+
}
|
|
2690
|
+
const endorserEndorsements = this.endorsementsByTarget.get(endorser);
|
|
2691
|
+
if (!endorserEndorsements || endorserEndorsements.length === 0) {
|
|
2692
|
+
return 0.1;
|
|
2693
|
+
}
|
|
2694
|
+
const endorserScore = this.computeNetworkTrust(
|
|
2695
|
+
endorser,
|
|
2696
|
+
this.filterByDomain(endorserEndorsements, domain),
|
|
2697
|
+
domain,
|
|
2698
|
+
depth,
|
|
2699
|
+
new Set(visited)
|
|
2700
|
+
// copy so sibling branches don't share state
|
|
2701
|
+
);
|
|
2702
|
+
return 0.1 + endorserScore * 0.9;
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* Compute time decay for an endorsement
|
|
2706
|
+
*/
|
|
2707
|
+
computeTimeDecay(endorsement, domain) {
|
|
2708
|
+
const age = Date.now() - endorsement.timestamp;
|
|
2709
|
+
const ageDays = age / (24 * 60 * 60 * 1e3);
|
|
2710
|
+
const halfLife = this.getDecayHalfLife(endorsement.domain || domain);
|
|
2711
|
+
return Math.exp(-ageDays / halfLife);
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Get decay half-life for a domain
|
|
2715
|
+
*/
|
|
2716
|
+
getDecayHalfLife(domain) {
|
|
2717
|
+
if (!domain) {
|
|
2718
|
+
return this.config.decayHalfLife.default || 90;
|
|
2719
|
+
}
|
|
2720
|
+
if (FAST_DOMAINS.includes(domain)) {
|
|
2721
|
+
return this.config.decayHalfLife[domain] || 30;
|
|
2722
|
+
}
|
|
2723
|
+
if (SLOW_DOMAINS.includes(domain)) {
|
|
2724
|
+
return this.config.decayHalfLife[domain] || 180;
|
|
2725
|
+
}
|
|
2726
|
+
return this.config.decayHalfLife[domain] || this.config.decayHalfLife.default || 90;
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* Detect collusion using Tarjan's SCC algorithm
|
|
2730
|
+
*/
|
|
2731
|
+
detectCollusion(target, endorsements) {
|
|
2732
|
+
const graph = this.buildGraph(endorsements);
|
|
2733
|
+
const sccs = this.findSCCs(graph);
|
|
2734
|
+
for (const scc of sccs) {
|
|
2735
|
+
if (scc.nodes.includes(target) && scc.nodes.length > 3) {
|
|
2736
|
+
const ratio = scc.externalEdges / (scc.internalEdges || 1);
|
|
2737
|
+
if (ratio < 0.2) {
|
|
2738
|
+
logger10.warn("Collusion detected", {
|
|
2739
|
+
target,
|
|
2740
|
+
sccSize: scc.nodes.length,
|
|
2741
|
+
ratio
|
|
2742
|
+
});
|
|
2743
|
+
return true;
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
return false;
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Build endorsement graph
|
|
2751
|
+
*/
|
|
2752
|
+
buildGraph(endorsements) {
|
|
2753
|
+
const graph = /* @__PURE__ */ new Map();
|
|
2754
|
+
for (const endorsement of endorsements) {
|
|
2755
|
+
if (!graph.has(endorsement.from)) {
|
|
2756
|
+
graph.set(endorsement.from, { did: endorsement.from, endorsements: [] });
|
|
2757
|
+
}
|
|
2758
|
+
if (!graph.has(endorsement.to)) {
|
|
2759
|
+
graph.set(endorsement.to, { did: endorsement.to, endorsements: [] });
|
|
2760
|
+
}
|
|
2761
|
+
graph.get(endorsement.from).endorsements.push(endorsement);
|
|
2762
|
+
}
|
|
2763
|
+
return graph;
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Find strongly connected components using Tarjan's algorithm
|
|
2767
|
+
*/
|
|
2768
|
+
findSCCs(graph) {
|
|
2769
|
+
const sccs = [];
|
|
2770
|
+
const stack = [];
|
|
2771
|
+
let index = 0;
|
|
2772
|
+
const strongConnect = (node) => {
|
|
2773
|
+
node.index = index;
|
|
2774
|
+
node.lowlink = index;
|
|
2775
|
+
index++;
|
|
2776
|
+
stack.push(node.did);
|
|
2777
|
+
node.onStack = true;
|
|
2778
|
+
for (const endorsement of node.endorsements) {
|
|
2779
|
+
const successor = graph.get(endorsement.to);
|
|
2780
|
+
if (!successor) continue;
|
|
2781
|
+
if (successor.index === void 0) {
|
|
2782
|
+
strongConnect(successor);
|
|
2783
|
+
node.lowlink = Math.min(node.lowlink, successor.lowlink);
|
|
2784
|
+
} else if (successor.onStack) {
|
|
2785
|
+
node.lowlink = Math.min(node.lowlink, successor.index);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
if (node.lowlink === node.index) {
|
|
2789
|
+
const sccNodes = [];
|
|
2790
|
+
let w;
|
|
2791
|
+
do {
|
|
2792
|
+
w = stack.pop();
|
|
2793
|
+
graph.get(w).onStack = false;
|
|
2794
|
+
sccNodes.push(w);
|
|
2795
|
+
} while (w !== node.did);
|
|
2796
|
+
if (sccNodes.length > 1) {
|
|
2797
|
+
let internalEdges = 0;
|
|
2798
|
+
let externalEdges = 0;
|
|
2799
|
+
for (const did of sccNodes) {
|
|
2800
|
+
const n = graph.get(did);
|
|
2801
|
+
for (const endorsement of n.endorsements) {
|
|
2802
|
+
if (sccNodes.includes(endorsement.to)) {
|
|
2803
|
+
internalEdges++;
|
|
2804
|
+
} else {
|
|
2805
|
+
externalEdges++;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
sccs.push({ nodes: sccNodes, internalEdges, externalEdges });
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
};
|
|
2813
|
+
for (const node of graph.values()) {
|
|
2814
|
+
if (node.index === void 0) {
|
|
2815
|
+
strongConnect(node);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
return sccs;
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Rebuild the SCC penalty cache from a full endorsement graph.
|
|
2822
|
+
* Called lazily when the cache is stale.
|
|
2823
|
+
*/
|
|
2824
|
+
rebuildSccPenaltyCache(endorsements) {
|
|
2825
|
+
this.sccPenaltyCache.clear();
|
|
2826
|
+
const graph = this.buildGraph(endorsements);
|
|
2827
|
+
const sccs = this.findSCCs(graph);
|
|
2828
|
+
for (const scc of sccs) {
|
|
2829
|
+
if (scc.nodes.length > 3) {
|
|
2830
|
+
const ratio = scc.externalEdges / (scc.internalEdges || 1);
|
|
2831
|
+
if (ratio < 0.2) {
|
|
2832
|
+
for (const did of scc.nodes) {
|
|
2833
|
+
this.sccPenaltyCache.set(did, 0.1);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
this.sccCacheExpiresAt = Date.now() + this.SCC_CACHE_TTL;
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Get collusion penalty for an endorser.
|
|
2842
|
+
* Returns 0.1 if the endorser is in a detected collusion cluster, 1.0 otherwise.
|
|
2843
|
+
*/
|
|
2844
|
+
getCollusionPenalty(endorser) {
|
|
2845
|
+
return this.sccPenaltyCache.get(endorser) ?? 1;
|
|
2846
|
+
}
|
|
2847
|
+
/**
|
|
2848
|
+
* Filter endorsements by domain
|
|
2849
|
+
*/
|
|
2850
|
+
filterByDomain(endorsements, domain) {
|
|
2851
|
+
if (!domain) {
|
|
2852
|
+
return endorsements;
|
|
2853
|
+
}
|
|
2854
|
+
return endorsements.filter(
|
|
2855
|
+
(e) => e.domain === domain || e.domain === void 0 || e.domain === "*"
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Get cache key
|
|
2860
|
+
*/
|
|
2861
|
+
getCacheKey(target, domain) {
|
|
2862
|
+
return `${target}:${domain || "*"}`;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Clear cache
|
|
2866
|
+
*/
|
|
2867
|
+
clearCache() {
|
|
2868
|
+
this.cache.clear();
|
|
2869
|
+
logger10.debug("Trust score cache cleared");
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Update configuration
|
|
2873
|
+
*/
|
|
2874
|
+
updateConfig(config) {
|
|
2875
|
+
this.config = {
|
|
2876
|
+
...this.config,
|
|
2877
|
+
...config,
|
|
2878
|
+
decayHalfLife: {
|
|
2879
|
+
...this.config.decayHalfLife,
|
|
2880
|
+
...config.decayHalfLife
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
this.clearCache();
|
|
2884
|
+
logger10.info("Trust config updated", { config });
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
var logger11 = createLogger("trust-system");
|
|
2540
2888
|
var TrustSystem = class {
|
|
2541
2889
|
metrics;
|
|
2542
2890
|
history;
|
|
@@ -2545,6 +2893,8 @@ var TrustSystem = class {
|
|
|
2545
2893
|
trustCache = /* @__PURE__ */ new Map();
|
|
2546
2894
|
CACHE_TTL = 5 * 60 * 1e3;
|
|
2547
2895
|
// 5 minutes
|
|
2896
|
+
// Uptime tracking: DID → { firstSeen, totalOnlineMs, lastOnlineAt }
|
|
2897
|
+
uptimeTracker = /* @__PURE__ */ new Map();
|
|
2548
2898
|
constructor(config) {
|
|
2549
2899
|
this.metrics = new TrustMetrics();
|
|
2550
2900
|
this.history = new InteractionHistory(`${config.dbPath}/interactions`);
|
|
@@ -2559,14 +2909,14 @@ var TrustSystem = class {
|
|
|
2559
2909
|
*/
|
|
2560
2910
|
async start() {
|
|
2561
2911
|
await this.history.open();
|
|
2562
|
-
|
|
2912
|
+
logger11.info("Trust system started");
|
|
2563
2913
|
}
|
|
2564
2914
|
/**
|
|
2565
2915
|
* Shutdown the trust system
|
|
2566
2916
|
*/
|
|
2567
2917
|
async stop() {
|
|
2568
2918
|
await this.history.close();
|
|
2569
|
-
|
|
2919
|
+
logger11.info("Trust system stopped");
|
|
2570
2920
|
}
|
|
2571
2921
|
/**
|
|
2572
2922
|
* Record an interaction
|
|
@@ -2576,6 +2926,42 @@ var TrustSystem = class {
|
|
|
2576
2926
|
this.sybilDefense.recordRequest(interaction.agentDid);
|
|
2577
2927
|
this.trustCache.delete(interaction.agentDid);
|
|
2578
2928
|
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Record agent coming online (for uptime tracking)
|
|
2931
|
+
*/
|
|
2932
|
+
recordOnline(agentDid) {
|
|
2933
|
+
const now = Date.now();
|
|
2934
|
+
const existing = this.uptimeTracker.get(agentDid);
|
|
2935
|
+
if (existing) {
|
|
2936
|
+
existing.lastOnlineAt = now;
|
|
2937
|
+
} else {
|
|
2938
|
+
this.uptimeTracker.set(agentDid, { firstSeen: now, totalOnlineMs: 0, lastOnlineAt: now });
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
/**
|
|
2942
|
+
* Record agent going offline (for uptime tracking)
|
|
2943
|
+
*/
|
|
2944
|
+
recordOffline(agentDid) {
|
|
2945
|
+
const now = Date.now();
|
|
2946
|
+
const entry = this.uptimeTracker.get(agentDid);
|
|
2947
|
+
if (entry && entry.lastOnlineAt > 0) {
|
|
2948
|
+
entry.totalOnlineMs += now - entry.lastOnlineAt;
|
|
2949
|
+
entry.lastOnlineAt = 0;
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Get uptime ratio for an agent (0.0 - 1.0)
|
|
2954
|
+
* Based on time since first seen vs total online time.
|
|
2955
|
+
*/
|
|
2956
|
+
getUptime(agentDid) {
|
|
2957
|
+
const entry = this.uptimeTracker.get(agentDid);
|
|
2958
|
+
if (!entry) return 1;
|
|
2959
|
+
const now = Date.now();
|
|
2960
|
+
const totalMs = now - entry.firstSeen;
|
|
2961
|
+
if (totalMs < 6e4) return 1;
|
|
2962
|
+
const onlineMs = entry.totalOnlineMs + (entry.lastOnlineAt > 0 ? now - entry.lastOnlineAt : 0);
|
|
2963
|
+
return Math.min(1, onlineMs / totalMs);
|
|
2964
|
+
}
|
|
2579
2965
|
/**
|
|
2580
2966
|
* Get trust score for an agent
|
|
2581
2967
|
*/
|
|
@@ -2586,8 +2972,9 @@ var TrustSystem = class {
|
|
|
2586
2972
|
}
|
|
2587
2973
|
const stats = await this.history.getStats(agentDid);
|
|
2588
2974
|
const endorsementList = await this.endorsements.getEndorsements(agentDid);
|
|
2589
|
-
const uptime =
|
|
2590
|
-
const
|
|
2975
|
+
const uptime = this.getUptime(agentDid);
|
|
2976
|
+
const endorsementScore = endorsementList.length > 0 ? endorsementList.reduce((sum, e) => sum + e.score, 0) / endorsementList.length : 0;
|
|
2977
|
+
const score = this.metrics.calculateScore(stats, endorsementScore, uptime);
|
|
2591
2978
|
this.trustCache.set(agentDid, { score, timestamp: Date.now() });
|
|
2592
2979
|
return score;
|
|
2593
2980
|
}
|
|
@@ -2655,13 +3042,13 @@ var TrustSystem = class {
|
|
|
2655
3042
|
await this.history.cleanup();
|
|
2656
3043
|
this.sybilDefense.cleanup();
|
|
2657
3044
|
this.trustCache.clear();
|
|
2658
|
-
|
|
3045
|
+
logger11.info("Trust system cleanup completed");
|
|
2659
3046
|
}
|
|
2660
3047
|
};
|
|
2661
3048
|
function createTrustSystem(config) {
|
|
2662
3049
|
return new TrustSystem(config);
|
|
2663
3050
|
}
|
|
2664
3051
|
|
|
2665
|
-
export { CLAWIVERSE_CONTEXT,
|
|
3052
|
+
export { CLAWIVERSE_CONTEXT, CapabilityTypes, ClawiverseError, DEFAULT_RATE_LIMIT_TIERS, DefenseMiddleware, DiscoveryError, EndorsementManager, FAST_DOMAINS, IdentityError, InteractionHistory, LogLevel, Logger, MessageQueue, MessageStorage, MessagingError, ParameterTypes, RELAY_PROTOCOL_VERSION, SCHEMA_ORG_CONTEXT, SLOW_DOMAINS, SybilDefense, TokenBucket, TransportError, TrustComputer, TrustMetrics, TrustSystem, clawiverseContext, createAgentCard, createDIDDocument, createDefaultTrustScore, createEnvelope, createLegacyAgentCard, createLogger, createMessageRouter, createRelayClient, createRelayIndexOperations, createTrustSystem, decodeAgentCard, decodeFromCBOR, decodeFromJSON, decodeMessage, decodeMessageJSON, deriveDID, downgradeToLegacyCard, encodeForDHT, encodeForWeb, encodeMessage, encodeMessageJSON, exportKeyPair, extractPublicKey, formatDidWithAlias, formatDidWithAliasDetailed, generateAnonymousIdentity, generateKeyPair, generateThreadId, getAgentCardContext, getDefaultExpiration, getEncodedSize, getTierConfig, importKeyPair, isLegacyCard, isValidContext, matchesCapability, normalizeEnvelope, normalizeEnvelopeType, resolveDid, reverseAlias, sign, signAgentCard, signEnvelope, signMessage, upgradeLegacyCard, validateAgentCard, validateAliasName, validateDID, validateDIDDocument, validateDomain, validateEnvelope, verify, verifyAgentCard, verifyEnvelope, verifyMessage };
|
|
2666
3053
|
//# sourceMappingURL=index.js.map
|
|
2667
3054
|
//# sourceMappingURL=index.js.map
|