@highway1/core 0.1.53 → 0.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +5 -18
  2. package/src/discovery/agent-card-encoder.ts +0 -119
  3. package/src/discovery/agent-card-schema.ts +0 -87
  4. package/src/discovery/agent-card-types.ts +0 -99
  5. package/src/discovery/agent-card.ts +0 -190
  6. package/src/discovery/bootstrap.ts +0 -63
  7. package/src/discovery/capability-matcher.ts +0 -167
  8. package/src/discovery/dht.ts +0 -310
  9. package/src/discovery/index.ts +0 -3
  10. package/src/discovery/relay-index.ts +0 -98
  11. package/src/discovery/search-index.ts +0 -247
  12. package/src/discovery/semantic-search.ts +0 -218
  13. package/src/identity/did.ts +0 -48
  14. package/src/identity/document.ts +0 -77
  15. package/src/identity/index.ts +0 -4
  16. package/src/identity/keys.ts +0 -79
  17. package/src/identity/signer.ts +0 -55
  18. package/src/index.ts +0 -39
  19. package/src/messaging/codec.ts +0 -47
  20. package/src/messaging/defense.ts +0 -236
  21. package/src/messaging/envelope.ts +0 -107
  22. package/src/messaging/index.ts +0 -8
  23. package/src/messaging/queue.ts +0 -181
  24. package/src/messaging/rate-limiter.ts +0 -85
  25. package/src/messaging/router.ts +0 -209
  26. package/src/messaging/storage.ts +0 -281
  27. package/src/messaging/types.ts +0 -149
  28. package/src/transport/connection.ts +0 -77
  29. package/src/transport/index.ts +0 -2
  30. package/src/transport/node.ts +0 -154
  31. package/src/transport/relay-client.ts +0 -437
  32. package/src/transport/relay-types.ts +0 -196
  33. package/src/trust/endorsement.ts +0 -167
  34. package/src/trust/index.ts +0 -194
  35. package/src/trust/interaction-history.ts +0 -155
  36. package/src/trust/sybil-defense.ts +0 -232
  37. package/src/trust/trust-score.ts +0 -136
  38. package/src/utils/errors.ts +0 -38
  39. package/src/utils/logger.ts +0 -48
@@ -1,85 +0,0 @@
1
- /**
2
- * Token Bucket Rate Limiter
3
- *
4
- * Classic token bucket algorithm for per-sender rate limiting.
5
- * Tokens refill at a constant rate up to capacity.
6
- */
7
-
8
- export interface TokenBucketConfig {
9
- capacity: number; // Max tokens (burst size)
10
- refillRate: number; // Tokens per millisecond
11
- }
12
-
13
- export class TokenBucket {
14
- private tokens: number;
15
- private lastRefill: number;
16
- private readonly capacity: number;
17
- private readonly refillRate: number; // tokens per ms
18
-
19
- constructor(config: TokenBucketConfig, initialTokens?: number, lastRefill?: number) {
20
- this.capacity = config.capacity;
21
- this.refillRate = config.refillRate;
22
- this.tokens = initialTokens ?? config.capacity;
23
- this.lastRefill = lastRefill ?? Date.now();
24
- }
25
-
26
- /** Attempt to consume one token. Returns true if allowed. */
27
- consume(): boolean {
28
- this.refill();
29
- if (this.tokens >= 1) {
30
- this.tokens -= 1;
31
- return true;
32
- }
33
- return false;
34
- }
35
-
36
- getRemaining(): number {
37
- this.refill();
38
- return Math.floor(this.tokens);
39
- }
40
-
41
- /** Milliseconds until at least one token is available */
42
- getResetTime(): number {
43
- this.refill();
44
- if (this.tokens >= 1) return 0;
45
- const needed = 1 - this.tokens;
46
- return Math.ceil(needed / this.refillRate);
47
- }
48
-
49
- /** Serialize state for persistence */
50
- toState(): { tokens: number; lastRefill: number } {
51
- return { tokens: this.tokens, lastRefill: this.lastRefill };
52
- }
53
-
54
- private refill(): void {
55
- const now = Date.now();
56
- const elapsed = now - this.lastRefill;
57
- const newTokens = elapsed * this.refillRate;
58
- this.tokens = Math.min(this.capacity, this.tokens + newTokens);
59
- this.lastRefill = now;
60
- }
61
- }
62
-
63
- /**
64
- * Rate limiter tiers based on trust score
65
- */
66
- export interface RateLimitTiers {
67
- /** Trust < 0.3: new/unknown agents */
68
- newAgent: TokenBucketConfig;
69
- /** Trust 0.3–0.6: established agents */
70
- established: TokenBucketConfig;
71
- /** Trust > 0.6: trusted agents */
72
- trusted: TokenBucketConfig;
73
- }
74
-
75
- export const DEFAULT_RATE_LIMIT_TIERS: RateLimitTiers = {
76
- newAgent: { capacity: 10, refillRate: 10 / (60 * 1000) }, // 10/min, burst 10
77
- established: { capacity: 100, refillRate: 60 / (60 * 1000) }, // 60/min, burst 100
78
- trusted: { capacity: 1000, refillRate: 600 / (60 * 1000) }, // 600/min, burst 1000
79
- };
80
-
81
- export function getTierConfig(trustScore: number, tiers: RateLimitTiers): TokenBucketConfig {
82
- if (trustScore >= 0.6) return tiers.trusted;
83
- if (trustScore >= 0.3) return tiers.established;
84
- return tiers.newAgent;
85
- }
@@ -1,209 +0,0 @@
1
- import type { MessageEnvelope } from './envelope.js';
2
- import type { RelayClient } from '../transport/relay-client.js';
3
- import { encodeMessage, decodeMessage } from './codec.js';
4
- import { validateEnvelope, verifyEnvelope } from './envelope.js';
5
- import { createLogger } from '../utils/logger.js';
6
- import { MessagingError } from '../utils/errors.js';
7
- import { extractPublicKey } from '../identity/did.js';
8
- import { verify } from '../identity/keys.js';
9
-
10
- const logger = createLogger('router');
11
-
12
- export type MessageHandler = (
13
- envelope: MessageEnvelope
14
- ) => Promise<MessageEnvelope | void>;
15
-
16
- export interface MessageRouter {
17
- registerHandler: (protocol: string, handler: MessageHandler) => void;
18
- unregisterHandler: (protocol: string) => void;
19
- registerCatchAllHandler: (handler: MessageHandler) => void;
20
- sendMessage: (envelope: MessageEnvelope) => Promise<MessageEnvelope | void>;
21
- start: () => Promise<void>;
22
- stop: () => Promise<void>;
23
- }
24
-
25
- /**
26
- * Create a message router using relay client (CVP-0011)
27
- */
28
- export function createMessageRouter(
29
- relayClient: RelayClient,
30
- verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
31
- ): MessageRouter {
32
- const handlers = new Map<string, MessageHandler>();
33
- let catchAllHandler: MessageHandler | undefined;
34
-
35
- // Map to track pending requests waiting for responses
36
- const pendingRequests = new Map<string, {
37
- resolve: (envelope: MessageEnvelope | undefined) => void;
38
- timeout: NodeJS.Timeout;
39
- }>();
40
-
41
- return {
42
- registerHandler: (protocol: string, handler: MessageHandler) => {
43
- handlers.set(protocol, handler);
44
- logger.info('Registered message handler', { protocol });
45
- },
46
-
47
- unregisterHandler: (protocol: string) => {
48
- handlers.delete(protocol);
49
- logger.info('Unregistered message handler', { protocol });
50
- },
51
-
52
- registerCatchAllHandler: (handler: MessageHandler) => {
53
- catchAllHandler = handler;
54
- logger.info('Registered catch-all message handler');
55
- },
56
-
57
- sendMessage: async (envelope: MessageEnvelope) => {
58
- try {
59
- if (!validateEnvelope(envelope)) {
60
- throw new MessagingError('Invalid message envelope');
61
- }
62
-
63
- const encoded = encodeMessage(envelope);
64
- await relayClient.sendEnvelope(envelope.to, encoded);
65
-
66
- logger.info('Message sent via relay', {
67
- id: envelope.id,
68
- from: envelope.from,
69
- to: envelope.to,
70
- protocol: envelope.protocol,
71
- });
72
-
73
- // If this is a request, wait for response
74
- if (envelope.type === 'request') {
75
- logger.debug('Waiting for response to request', { id: envelope.id });
76
-
77
- const RESPONSE_TIMEOUT = 30000; // 30 seconds (CVP-0010 §4.2)
78
-
79
- return new Promise<MessageEnvelope | undefined>((resolve) => {
80
- const timeout = setTimeout(() => {
81
- pendingRequests.delete(envelope.id);
82
- logger.warn('Response timeout', { id: envelope.id, timeout: RESPONSE_TIMEOUT });
83
- resolve(undefined);
84
- }, RESPONSE_TIMEOUT);
85
-
86
- pendingRequests.set(envelope.id, { resolve, timeout });
87
- });
88
- }
89
-
90
- return undefined;
91
- } catch (error) {
92
- if (error instanceof MessagingError) throw error;
93
- throw new MessagingError('Failed to send message', error);
94
- }
95
- },
96
-
97
- start: async () => {
98
- // Register handler for incoming DELIVER messages
99
- relayClient.onDeliver(async (deliverMsg) => {
100
- try {
101
- const envelope = decodeMessage(deliverMsg.envelope);
102
-
103
- if (!validateEnvelope(envelope)) {
104
- logger.warn('Received invalid message envelope');
105
- return;
106
- }
107
-
108
- // Verify signature against sender DID embedded public key
109
- const isValidSignature = await verifyEnvelope(envelope, async (signature, data) => {
110
- const senderPublicKey = extractPublicKey(envelope.from);
111
- return verify(signature, data, senderPublicKey);
112
- });
113
- if (!isValidSignature) {
114
- logger.warn('Received message with invalid signature', {
115
- id: envelope.id,
116
- from: envelope.from,
117
- });
118
- return;
119
- }
120
-
121
- // Optional caller-supplied verification hook (e.g. policy checks)
122
- try {
123
- const { signature, ...envelopeWithoutSig } = envelope;
124
- const dataBytes = new TextEncoder().encode(JSON.stringify(envelopeWithoutSig));
125
- const signatureBytes = Buffer.from(signature, 'hex');
126
- const hookValid = await verifyFn(signatureBytes, dataBytes);
127
- if (!hookValid) {
128
- logger.warn('Message rejected by custom verifier', {
129
- id: envelope.id,
130
- from: envelope.from,
131
- });
132
- return;
133
- }
134
- } catch (error) {
135
- logger.warn('Custom verification hook failed', {
136
- id: envelope.id,
137
- from: envelope.from,
138
- error: (error as Error).message,
139
- });
140
- return;
141
- }
142
-
143
- logger.info('Received message', {
144
- id: envelope.id,
145
- from: envelope.from,
146
- to: envelope.to,
147
- protocol: envelope.protocol,
148
- type: envelope.type,
149
- });
150
-
151
- // Check if this is a response to a pending request
152
- if (envelope.type === 'response' && envelope.replyTo) {
153
- const pending = pendingRequests.get(envelope.replyTo);
154
- if (pending) {
155
- clearTimeout(pending.timeout);
156
- pendingRequests.delete(envelope.replyTo);
157
- pending.resolve(envelope);
158
- logger.info('Matched response to pending request', {
159
- requestId: envelope.replyTo,
160
- responseId: envelope.id,
161
- });
162
- return;
163
- }
164
- }
165
-
166
- // Dispatch to handler
167
- const handler = handlers.get(envelope.protocol);
168
- let response: MessageEnvelope | void = undefined;
169
-
170
- if (handler) {
171
- response = await handler(envelope);
172
- } else if (catchAllHandler) {
173
- logger.debug('Using catch-all handler for protocol', { protocol: envelope.protocol });
174
- response = await catchAllHandler(envelope);
175
- } else {
176
- logger.warn('No handler for protocol', { protocol: envelope.protocol });
177
- }
178
-
179
- // Send response back if handler returned one
180
- if (response) {
181
- const encoded = encodeMessage(response);
182
- await relayClient.sendEnvelope(response.to, encoded);
183
- logger.info('Sent response back to sender', {
184
- responseId: response.id,
185
- replyTo: response.replyTo,
186
- });
187
- }
188
- } catch (error) {
189
- logger.error('Error handling incoming message', error);
190
- }
191
- });
192
-
193
- logger.info('Message router started');
194
- },
195
-
196
- stop: async () => {
197
- // Clear all pending requests
198
- for (const [, pending] of pendingRequests.entries()) {
199
- clearTimeout(pending.timeout);
200
- pending.resolve(undefined);
201
- }
202
- pendingRequests.clear();
203
-
204
- handlers.clear();
205
- catchAllHandler = undefined;
206
- logger.info('Message router stopped');
207
- },
208
- };
209
- }
@@ -1,281 +0,0 @@
1
- /**
2
- * Message Storage - LevelDB operations for message queue
3
- *
4
- * Key schema:
5
- * msg:inbound:{timestamp}:{id} → StoredMessage
6
- * msg:outbound:{timestamp}:{id} → StoredMessage
7
- * block:{did} → BlocklistEntry
8
- * allow:{did} → AllowlistEntry
9
- * seen:{messageId} → SeenEntry
10
- * rate:{did} → RateLimitState
11
- * idx:from:{did}:{timestamp}:{id} → '1'
12
- */
13
-
14
- import { Level } from 'level';
15
- import { createLogger } from '../utils/logger.js';
16
- import type {
17
- StoredMessage,
18
- BlocklistEntry,
19
- AllowlistEntry,
20
- SeenEntry,
21
- RateLimitState,
22
- MessageFilter,
23
- PaginationOptions,
24
- MessagePage,
25
- } from './types.js';
26
-
27
- const logger = createLogger('message-storage');
28
-
29
- export class MessageStorage {
30
- private db: Level<string, any>;
31
- private ready = false;
32
-
33
- constructor(dbPath: string) {
34
- this.db = new Level<string, any>(dbPath, { valueEncoding: 'json' });
35
- }
36
-
37
- async open(): Promise<void> {
38
- await this.db.open();
39
- this.ready = true;
40
- logger.info('Message storage opened');
41
- }
42
-
43
- async close(): Promise<void> {
44
- if (this.ready) {
45
- await this.db.close();
46
- this.ready = false;
47
- logger.info('Message storage closed');
48
- }
49
- }
50
-
51
- // ─── Message Operations ───────────────────────────────────────────────────
52
-
53
- async putMessage(msg: StoredMessage): Promise<void> {
54
- const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
55
- const key = `msg:${msg.direction}:${ts}:${msg.envelope.id}`;
56
- await this.db.put(key, msg);
57
-
58
- // Secondary index by sender
59
- const idxKey = `idx:from:${msg.envelope.from}:${ts}:${msg.envelope.id}`;
60
- await this.db.put(idxKey, '1');
61
- }
62
-
63
- async getMessage(id: string): Promise<StoredMessage | null> {
64
- // Scan both directions since we don't know the timestamp
65
- for (const direction of ['inbound', 'outbound'] as const) {
66
- const prefix = `msg:${direction}:`;
67
- for await (const [, value] of this.db.iterator<string, StoredMessage>({
68
- gte: prefix,
69
- lte: prefix + '\xff',
70
- valueEncoding: 'json',
71
- })) {
72
- if (value.envelope.id === id) return value;
73
- }
74
- }
75
- return null;
76
- }
77
-
78
- async updateMessage(id: string, updates: Partial<StoredMessage>): Promise<void> {
79
- const msg = await this.getMessage(id);
80
- if (!msg) return;
81
- const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
82
- const key = `msg:${msg.direction}:${ts}:${id}`;
83
- await this.db.put(key, { ...msg, ...updates });
84
- }
85
-
86
- async deleteMessage(id: string): Promise<void> {
87
- const msg = await this.getMessage(id);
88
- if (!msg) return;
89
- const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, '0');
90
- const key = `msg:${msg.direction}:${ts}:${id}`;
91
- const idxKey = `idx:from:${msg.envelope.from}:${ts}:${id}`;
92
- await this.db.batch([
93
- { type: 'del', key },
94
- { type: 'del', key: idxKey },
95
- ]);
96
- }
97
-
98
- async queryMessages(
99
- direction: 'inbound' | 'outbound',
100
- filter: MessageFilter = {},
101
- pagination: PaginationOptions = {}
102
- ): Promise<MessagePage> {
103
- const { limit = 50, offset = 0 } = pagination;
104
- const prefix = `msg:${direction}:`;
105
- const results: StoredMessage[] = [];
106
- let total = 0;
107
- let skipped = 0;
108
-
109
- for await (const [, value] of this.db.iterator<string, StoredMessage>({
110
- gte: prefix,
111
- lte: prefix + '\xff',
112
- reverse: true, // newest first
113
- valueEncoding: 'json',
114
- })) {
115
- if (!this.matchesFilter(value, filter)) continue;
116
- total++;
117
- if (skipped < offset) { skipped++; continue; }
118
- if (results.length < limit) results.push(value);
119
- }
120
-
121
- return {
122
- messages: results,
123
- total,
124
- hasMore: total > offset + results.length,
125
- };
126
- }
127
-
128
- private matchesFilter(msg: StoredMessage, filter: MessageFilter): boolean {
129
- if (filter.fromDid) {
130
- const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
131
- if (!froms.includes(msg.envelope.from)) return false;
132
- }
133
- if (filter.toDid) {
134
- const tos = Array.isArray(filter.toDid) ? filter.toDid : [filter.toDid];
135
- if (!tos.includes(msg.envelope.to)) return false;
136
- }
137
- if (filter.protocol) {
138
- const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
139
- if (!protos.includes(msg.envelope.protocol)) return false;
140
- }
141
- if (filter.type && msg.envelope.type !== filter.type) return false;
142
- if (filter.unreadOnly && msg.readAt != null) return false;
143
- if (filter.status) {
144
- const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
145
- if (!statuses.includes(msg.status)) return false;
146
- }
147
- if (filter.maxAge) {
148
- const age = Date.now() - (msg.receivedAt ?? msg.sentAt ?? 0);
149
- if (age > filter.maxAge) return false;
150
- }
151
- if (filter.minTrustScore != null && (msg.trustScore ?? 0) < filter.minTrustScore) return false;
152
- return true;
153
- }
154
-
155
- async countMessages(direction: 'inbound' | 'outbound', filter: MessageFilter = {}): Promise<number> {
156
- const prefix = `msg:${direction}:`;
157
- let count = 0;
158
- for await (const [, value] of this.db.iterator<string, StoredMessage>({
159
- gte: prefix,
160
- lte: prefix + '\xff',
161
- valueEncoding: 'json',
162
- })) {
163
- if (this.matchesFilter(value, filter)) count++;
164
- }
165
- return count;
166
- }
167
-
168
- // ─── Blocklist ────────────────────────────────────────────────────────────
169
-
170
- async putBlock(entry: BlocklistEntry): Promise<void> {
171
- await this.db.put(`block:${entry.did}`, entry);
172
- }
173
-
174
- async getBlock(did: string): Promise<BlocklistEntry | null> {
175
- try {
176
- return await this.db.get(`block:${did}`);
177
- } catch {
178
- return null;
179
- }
180
- }
181
-
182
- async deleteBlock(did: string): Promise<void> {
183
- try { await this.db.del(`block:${did}`); } catch { /* not found */ }
184
- }
185
-
186
- async listBlocked(): Promise<BlocklistEntry[]> {
187
- const results: BlocklistEntry[] = [];
188
- for await (const [, value] of this.db.iterator<string, BlocklistEntry>({
189
- gte: 'block:',
190
- lte: 'block:\xff',
191
- valueEncoding: 'json',
192
- })) {
193
- results.push(value);
194
- }
195
- return results;
196
- }
197
-
198
- // ─── Allowlist ────────────────────────────────────────────────────────────
199
-
200
- async putAllow(entry: AllowlistEntry): Promise<void> {
201
- await this.db.put(`allow:${entry.did}`, entry);
202
- }
203
-
204
- async getAllow(did: string): Promise<AllowlistEntry | null> {
205
- try {
206
- return await this.db.get(`allow:${did}`);
207
- } catch {
208
- return null;
209
- }
210
- }
211
-
212
- async deleteAllow(did: string): Promise<void> {
213
- try { await this.db.del(`allow:${did}`); } catch { /* not found */ }
214
- }
215
-
216
- async listAllowed(): Promise<AllowlistEntry[]> {
217
- const results: AllowlistEntry[] = [];
218
- for await (const [, value] of this.db.iterator<string, AllowlistEntry>({
219
- gte: 'allow:',
220
- lte: 'allow:\xff',
221
- valueEncoding: 'json',
222
- })) {
223
- results.push(value);
224
- }
225
- return results;
226
- }
227
-
228
- // ─── Seen Cache ───────────────────────────────────────────────────────────
229
-
230
- async putSeen(entry: SeenEntry): Promise<void> {
231
- await this.db.put(`seen:${entry.messageId}`, entry);
232
- }
233
-
234
- async getSeen(messageId: string): Promise<SeenEntry | null> {
235
- try {
236
- return await this.db.get(`seen:${messageId}`);
237
- } catch {
238
- return null;
239
- }
240
- }
241
-
242
- async cleanupSeen(maxAgeMs: number): Promise<void> {
243
- const cutoff = Date.now() - maxAgeMs;
244
- const toDelete: string[] = [];
245
- for await (const [key, value] of this.db.iterator<string, SeenEntry>({
246
- gte: 'seen:',
247
- lte: 'seen:\xff',
248
- valueEncoding: 'json',
249
- })) {
250
- if (value.seenAt < cutoff) toDelete.push(key);
251
- }
252
- await this.db.batch(toDelete.map((key) => ({ type: 'del' as const, key })));
253
- }
254
-
255
- // ─── Rate Limit State ─────────────────────────────────────────────────────
256
-
257
- async putRateLimit(state: RateLimitState): Promise<void> {
258
- await this.db.put(`rate:${state.did}`, state);
259
- }
260
-
261
- async getRateLimit(did: string): Promise<RateLimitState | null> {
262
- try {
263
- return await this.db.get(`rate:${did}`);
264
- } catch {
265
- return null;
266
- }
267
- }
268
-
269
- async cleanupRateLimits(maxAgeMs: number): Promise<void> {
270
- const cutoff = Date.now() - maxAgeMs;
271
- const toDelete: string[] = [];
272
- for await (const [key, value] of this.db.iterator<string, RateLimitState>({
273
- gte: 'rate:',
274
- lte: 'rate:\xff',
275
- valueEncoding: 'json',
276
- })) {
277
- if (value.lastRefill < cutoff) toDelete.push(key);
278
- }
279
- await this.db.batch(toDelete.map((key) => ({ type: 'del' as const, key })));
280
- }
281
- }