@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.
- package/package.json +5 -18
- package/src/discovery/agent-card-encoder.ts +0 -119
- package/src/discovery/agent-card-schema.ts +0 -87
- package/src/discovery/agent-card-types.ts +0 -99
- package/src/discovery/agent-card.ts +0 -190
- package/src/discovery/bootstrap.ts +0 -63
- package/src/discovery/capability-matcher.ts +0 -167
- package/src/discovery/dht.ts +0 -310
- package/src/discovery/index.ts +0 -3
- package/src/discovery/relay-index.ts +0 -98
- package/src/discovery/search-index.ts +0 -247
- package/src/discovery/semantic-search.ts +0 -218
- package/src/identity/did.ts +0 -48
- package/src/identity/document.ts +0 -77
- package/src/identity/index.ts +0 -4
- package/src/identity/keys.ts +0 -79
- package/src/identity/signer.ts +0 -55
- package/src/index.ts +0 -39
- package/src/messaging/codec.ts +0 -47
- package/src/messaging/defense.ts +0 -236
- package/src/messaging/envelope.ts +0 -107
- package/src/messaging/index.ts +0 -8
- package/src/messaging/queue.ts +0 -181
- package/src/messaging/rate-limiter.ts +0 -85
- package/src/messaging/router.ts +0 -209
- package/src/messaging/storage.ts +0 -281
- package/src/messaging/types.ts +0 -149
- package/src/transport/connection.ts +0 -77
- package/src/transport/index.ts +0 -2
- package/src/transport/node.ts +0 -154
- package/src/transport/relay-client.ts +0 -437
- package/src/transport/relay-types.ts +0 -196
- package/src/trust/endorsement.ts +0 -167
- package/src/trust/index.ts +0 -194
- package/src/trust/interaction-history.ts +0 -155
- package/src/trust/sybil-defense.ts +0 -232
- package/src/trust/trust-score.ts +0 -136
- package/src/utils/errors.ts +0 -38
- package/src/utils/logger.ts +0 -48
package/src/identity/signer.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { sign, verify } from './keys.js';
|
|
2
|
-
import { deriveDID } from './did.js';
|
|
3
|
-
import { IdentityError } from '../utils/errors.js';
|
|
4
|
-
|
|
5
|
-
export interface SignedMessage {
|
|
6
|
-
payload: Uint8Array;
|
|
7
|
-
signature: Uint8Array;
|
|
8
|
-
signer: string; // DID
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Sign a message and return a signed message object
|
|
13
|
-
*/
|
|
14
|
-
export async function signMessage(
|
|
15
|
-
payload: Uint8Array,
|
|
16
|
-
privateKey: Uint8Array,
|
|
17
|
-
publicKey: Uint8Array
|
|
18
|
-
): Promise<SignedMessage> {
|
|
19
|
-
try {
|
|
20
|
-
const signature = await sign(payload, privateKey);
|
|
21
|
-
const signer = deriveDID(publicKey);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
payload,
|
|
25
|
-
signature,
|
|
26
|
-
signer,
|
|
27
|
-
};
|
|
28
|
-
} catch (error) {
|
|
29
|
-
throw new IdentityError('Failed to sign message', error);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Verify a signed message
|
|
35
|
-
*/
|
|
36
|
-
export async function verifyMessage(
|
|
37
|
-
signedMessage: SignedMessage,
|
|
38
|
-
expectedPublicKey: Uint8Array
|
|
39
|
-
): Promise<boolean> {
|
|
40
|
-
try {
|
|
41
|
-
const expectedDID = deriveDID(expectedPublicKey);
|
|
42
|
-
|
|
43
|
-
if (signedMessage.signer !== expectedDID) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return await verify(
|
|
48
|
-
signedMessage.signature,
|
|
49
|
-
signedMessage.payload,
|
|
50
|
-
expectedPublicKey
|
|
51
|
-
);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new IdentityError('Failed to verify message', error);
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Clawiverse Core - Main Export
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Identity
|
|
6
|
-
export * from './identity/keys.js';
|
|
7
|
-
export * from './identity/did.js';
|
|
8
|
-
export * from './identity/signer.js';
|
|
9
|
-
|
|
10
|
-
// Transport (CVP-0011: relay-based)
|
|
11
|
-
export * from './transport/relay-client.js';
|
|
12
|
-
export * from './transport/relay-types.js';
|
|
13
|
-
|
|
14
|
-
// Discovery
|
|
15
|
-
export * from './discovery/agent-card.js';
|
|
16
|
-
export * from './discovery/agent-card-types.js';
|
|
17
|
-
export * from './discovery/agent-card-schema.js';
|
|
18
|
-
export * from './discovery/agent-card-encoder.js';
|
|
19
|
-
export * from './discovery/relay-index.js';
|
|
20
|
-
export * from './discovery/search-index.js';
|
|
21
|
-
export * from './discovery/capability-matcher.js';
|
|
22
|
-
export * from './discovery/semantic-search.js';
|
|
23
|
-
|
|
24
|
-
// Messaging
|
|
25
|
-
export * from './messaging/envelope.js';
|
|
26
|
-
export * from './messaging/codec.js';
|
|
27
|
-
export * from './messaging/router.js';
|
|
28
|
-
export * from './messaging/types.js';
|
|
29
|
-
export * from './messaging/storage.js';
|
|
30
|
-
export * from './messaging/queue.js';
|
|
31
|
-
export * from './messaging/defense.js';
|
|
32
|
-
export * from './messaging/rate-limiter.js';
|
|
33
|
-
|
|
34
|
-
// Trust (Phase 2)
|
|
35
|
-
export * from './trust/index.js';
|
|
36
|
-
|
|
37
|
-
// Utils
|
|
38
|
-
export * from './utils/logger.js';
|
|
39
|
-
export * from './utils/errors.js';
|
package/src/messaging/codec.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { encode, decode } from 'cbor-x';
|
|
2
|
-
import type { MessageEnvelope } from './envelope.js';
|
|
3
|
-
import { MessagingError } from '../utils/errors.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Encode a message envelope to CBOR
|
|
7
|
-
*/
|
|
8
|
-
export function encodeMessage(envelope: MessageEnvelope): Uint8Array {
|
|
9
|
-
try {
|
|
10
|
-
return encode(envelope);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
throw new MessagingError('Failed to encode message', error);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Decode a CBOR message to envelope
|
|
18
|
-
*/
|
|
19
|
-
export function decodeMessage(data: Uint8Array): MessageEnvelope {
|
|
20
|
-
try {
|
|
21
|
-
return decode(data) as MessageEnvelope;
|
|
22
|
-
} catch (error) {
|
|
23
|
-
throw new MessagingError('Failed to decode message', error);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Encode message to JSON (for debugging/logging)
|
|
29
|
-
*/
|
|
30
|
-
export function encodeMessageJSON(envelope: MessageEnvelope): string {
|
|
31
|
-
try {
|
|
32
|
-
return JSON.stringify(envelope, null, 2);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
throw new MessagingError('Failed to encode message to JSON', error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Decode JSON message to envelope
|
|
40
|
-
*/
|
|
41
|
-
export function decodeMessageJSON(json: string): MessageEnvelope {
|
|
42
|
-
try {
|
|
43
|
-
return JSON.parse(json) as MessageEnvelope;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
throw new MessagingError('Failed to decode message from JSON', error);
|
|
46
|
-
}
|
|
47
|
-
}
|
package/src/messaging/defense.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Defense Middleware
|
|
3
|
-
*
|
|
4
|
-
* Checks incoming messages against:
|
|
5
|
-
* 1. Allowlist bypass
|
|
6
|
-
* 2. Blocklist rejection
|
|
7
|
-
* 3. Deduplication (seen cache)
|
|
8
|
-
* 4. Trust score filtering
|
|
9
|
-
* 5. Rate limiting (token bucket, tiered by trust)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { createLogger } from '../utils/logger.js';
|
|
13
|
-
import type { MessageEnvelope } from './envelope.js';
|
|
14
|
-
import type { TrustSystem } from '../trust/index.js';
|
|
15
|
-
import type { MessageStorage } from './storage.js';
|
|
16
|
-
import type { DefenseResult, RateLimitResult } from './types.js';
|
|
17
|
-
import {
|
|
18
|
-
TokenBucket,
|
|
19
|
-
DEFAULT_RATE_LIMIT_TIERS,
|
|
20
|
-
getTierConfig,
|
|
21
|
-
type RateLimitTiers,
|
|
22
|
-
} from './rate-limiter.js';
|
|
23
|
-
|
|
24
|
-
const logger = createLogger('defense');
|
|
25
|
-
|
|
26
|
-
export interface DefenseConfig {
|
|
27
|
-
trustSystem: TrustSystem;
|
|
28
|
-
storage: MessageStorage;
|
|
29
|
-
/** Minimum trust score to accept messages (0 = accept all) */
|
|
30
|
-
minTrustScore?: number;
|
|
31
|
-
/** Auto-block agents below this score */
|
|
32
|
-
autoBlockThreshold?: number;
|
|
33
|
-
rateLimitTiers?: RateLimitTiers;
|
|
34
|
-
/** TTL for seen-cache entries in ms (default: 1 hour) */
|
|
35
|
-
seenTtlMs?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export class DefenseMiddleware {
|
|
39
|
-
private readonly trust: TrustSystem;
|
|
40
|
-
private readonly storage: MessageStorage;
|
|
41
|
-
private readonly minTrustScore: number;
|
|
42
|
-
private readonly autoBlockThreshold: number;
|
|
43
|
-
private readonly tiers: RateLimitTiers;
|
|
44
|
-
private readonly seenTtlMs: number;
|
|
45
|
-
|
|
46
|
-
// In-memory LRU-style seen cache (backed by LevelDB for persistence)
|
|
47
|
-
private readonly seenCache = new Map<string, number>(); // id → seenAt
|
|
48
|
-
private readonly MAX_SEEN_CACHE = 10_000;
|
|
49
|
-
|
|
50
|
-
// In-memory token buckets (backed by LevelDB for persistence)
|
|
51
|
-
private readonly buckets = new Map<string, TokenBucket>();
|
|
52
|
-
|
|
53
|
-
constructor(config: DefenseConfig) {
|
|
54
|
-
this.trust = config.trustSystem;
|
|
55
|
-
this.storage = config.storage;
|
|
56
|
-
this.minTrustScore = config.minTrustScore ?? 0;
|
|
57
|
-
this.autoBlockThreshold = config.autoBlockThreshold ?? 0.1;
|
|
58
|
-
this.tiers = config.rateLimitTiers ?? DEFAULT_RATE_LIMIT_TIERS;
|
|
59
|
-
this.seenTtlMs = config.seenTtlMs ?? 60 * 60 * 1000; // 1 hour
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Run all defense checks on an incoming message.
|
|
64
|
-
* Returns { allowed: true } if the message should be processed,
|
|
65
|
-
* or { allowed: false, reason } if it should be dropped.
|
|
66
|
-
*/
|
|
67
|
-
async checkMessage(envelope: MessageEnvelope): Promise<DefenseResult> {
|
|
68
|
-
const did = envelope.from;
|
|
69
|
-
|
|
70
|
-
// 1. Allowlist bypass — skip all other checks
|
|
71
|
-
if (await this.isAllowed(did)) {
|
|
72
|
-
this.markAsSeen(envelope.id);
|
|
73
|
-
return { allowed: true };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 2. Blocklist check
|
|
77
|
-
if (await this.isBlocked(did)) {
|
|
78
|
-
logger.debug('Message rejected: blocked', { id: envelope.id, from: did });
|
|
79
|
-
return { allowed: false, reason: 'blocked' };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 3. Deduplication
|
|
83
|
-
if (this.hasSeen(envelope.id)) {
|
|
84
|
-
logger.debug('Message rejected: duplicate', { id: envelope.id });
|
|
85
|
-
return { allowed: false, reason: 'duplicate' };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 4. Trust score check
|
|
89
|
-
let trustScore = 0;
|
|
90
|
-
try {
|
|
91
|
-
const score = await this.trust.getTrustScore(did);
|
|
92
|
-
trustScore = score.interactionScore;
|
|
93
|
-
|
|
94
|
-
// Auto-block very low trust agents
|
|
95
|
-
if (trustScore < this.autoBlockThreshold && trustScore > 0) {
|
|
96
|
-
logger.warn('Auto-blocking low-trust agent', { did, trustScore });
|
|
97
|
-
await this.blockAgent(did, `Auto-blocked: trust score ${trustScore.toFixed(2)} below threshold`);
|
|
98
|
-
return { allowed: false, reason: 'blocked' };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (trustScore < this.minTrustScore) {
|
|
102
|
-
logger.debug('Message rejected: trust too low', { id: envelope.id, trustScore });
|
|
103
|
-
return { allowed: false, reason: 'trust_too_low', trustScore };
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
logger.warn('Trust score lookup failed, using 0', { did, error: (err as Error).message });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 5. Rate limiting (tiered by trust)
|
|
110
|
-
const rateLimitResult = await this.checkRateLimit(did, trustScore);
|
|
111
|
-
if (!rateLimitResult.allowed) {
|
|
112
|
-
logger.debug('Message rejected: rate limited', {
|
|
113
|
-
id: envelope.id,
|
|
114
|
-
from: did,
|
|
115
|
-
resetTime: rateLimitResult.resetTime,
|
|
116
|
-
});
|
|
117
|
-
return {
|
|
118
|
-
allowed: false,
|
|
119
|
-
reason: 'rate_limited',
|
|
120
|
-
remainingTokens: rateLimitResult.remaining,
|
|
121
|
-
resetTime: rateLimitResult.resetTime,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// All checks passed
|
|
126
|
-
this.markAsSeen(envelope.id);
|
|
127
|
-
return { allowed: true, trustScore, remainingTokens: rateLimitResult.remaining };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
async blockAgent(did: string, reason: string, blockedBy = 'local'): Promise<void> {
|
|
133
|
-
await this.storage.putBlock({ did, reason, blockedAt: Date.now(), blockedBy });
|
|
134
|
-
logger.info('Agent blocked', { did, reason });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async unblockAgent(did: string): Promise<void> {
|
|
138
|
-
await this.storage.deleteBlock(did);
|
|
139
|
-
logger.info('Agent unblocked', { did });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async isBlocked(did: string): Promise<boolean> {
|
|
143
|
-
return (await this.storage.getBlock(did)) !== null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ─── Allowlist ────────────────────────────────────────────────────────────
|
|
147
|
-
|
|
148
|
-
async allowAgent(did: string, note?: string): Promise<void> {
|
|
149
|
-
await this.storage.putAllow({ did, addedAt: Date.now(), note });
|
|
150
|
-
logger.info('Agent allowlisted', { did });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async removeFromAllowlist(did: string): Promise<void> {
|
|
154
|
-
await this.storage.deleteAllow(did);
|
|
155
|
-
logger.info('Agent removed from allowlist', { did });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async isAllowed(did: string): Promise<boolean> {
|
|
159
|
-
return (await this.storage.getAllow(did)) !== null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ─── Rate Limiting ────────────────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
async checkRateLimit(did: string, trustScore: number): Promise<RateLimitResult> {
|
|
165
|
-
const tierConfig = getTierConfig(trustScore, this.tiers);
|
|
166
|
-
|
|
167
|
-
// Load or create bucket
|
|
168
|
-
let bucket = this.buckets.get(did);
|
|
169
|
-
if (!bucket) {
|
|
170
|
-
// Try to restore from LevelDB
|
|
171
|
-
const persisted = await this.storage.getRateLimit(did);
|
|
172
|
-
if (persisted) {
|
|
173
|
-
bucket = new TokenBucket(tierConfig, persisted.tokens, persisted.lastRefill);
|
|
174
|
-
} else {
|
|
175
|
-
bucket = new TokenBucket(tierConfig);
|
|
176
|
-
}
|
|
177
|
-
this.buckets.set(did, bucket);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const allowed = bucket.consume();
|
|
181
|
-
const state = bucket.toState();
|
|
182
|
-
|
|
183
|
-
// Persist updated state
|
|
184
|
-
await this.storage.putRateLimit({
|
|
185
|
-
did,
|
|
186
|
-
tokens: state.tokens,
|
|
187
|
-
lastRefill: state.lastRefill,
|
|
188
|
-
totalRequests: 0,
|
|
189
|
-
firstSeen: Date.now(),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
allowed,
|
|
194
|
-
remaining: bucket.getRemaining(),
|
|
195
|
-
resetTime: bucket.getResetTime(),
|
|
196
|
-
limit: tierConfig.capacity,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ─── Seen Cache (deduplication) ───────────────────────────────────────────
|
|
201
|
-
|
|
202
|
-
hasSeen(messageId: string): boolean {
|
|
203
|
-
return this.seenCache.has(messageId);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
markAsSeen(messageId: string): void {
|
|
207
|
-
// Evict oldest entries if at capacity
|
|
208
|
-
if (this.seenCache.size >= this.MAX_SEEN_CACHE) {
|
|
209
|
-
const firstKey = this.seenCache.keys().next().value;
|
|
210
|
-
if (firstKey) this.seenCache.delete(firstKey);
|
|
211
|
-
}
|
|
212
|
-
this.seenCache.set(messageId, Date.now());
|
|
213
|
-
|
|
214
|
-
// Persist to LevelDB (fire-and-forget)
|
|
215
|
-
this.storage.putSeen({ messageId, seenAt: Date.now(), fromDid: '' }).catch(() => {});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Periodic cleanup of expired seen entries */
|
|
219
|
-
async cleanupSeen(): Promise<void> {
|
|
220
|
-
const cutoff = Date.now() - this.seenTtlMs;
|
|
221
|
-
for (const [id, seenAt] of this.seenCache) {
|
|
222
|
-
if (seenAt < cutoff) this.seenCache.delete(id);
|
|
223
|
-
}
|
|
224
|
-
await this.storage.cleanupSeen(this.seenTtlMs);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Periodic cleanup of stale rate limit buckets (24h inactive) */
|
|
228
|
-
async cleanupRateLimits(): Promise<void> {
|
|
229
|
-
const staleMs = 24 * 60 * 60 * 1000;
|
|
230
|
-
const cutoff = Date.now() - staleMs;
|
|
231
|
-
for (const [did, bucket] of this.buckets) {
|
|
232
|
-
if (bucket.toState().lastRefill < cutoff) this.buckets.delete(did);
|
|
233
|
-
}
|
|
234
|
-
await this.storage.cleanupRateLimits(staleMs);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { MessagingError } from '../utils/errors.js';
|
|
2
|
-
|
|
3
|
-
export interface MessageEnvelope {
|
|
4
|
-
id: string;
|
|
5
|
-
from: string; // DID
|
|
6
|
-
to: string; // DID
|
|
7
|
-
type: 'request' | 'response' | 'notification';
|
|
8
|
-
protocol: string;
|
|
9
|
-
payload: unknown;
|
|
10
|
-
timestamp: number;
|
|
11
|
-
signature: string;
|
|
12
|
-
replyTo?: string; // For responses, the ID of the request
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a message envelope
|
|
17
|
-
*/
|
|
18
|
-
export function createEnvelope(
|
|
19
|
-
from: string,
|
|
20
|
-
to: string,
|
|
21
|
-
type: 'request' | 'response' | 'notification',
|
|
22
|
-
protocol: string,
|
|
23
|
-
payload: unknown,
|
|
24
|
-
replyTo?: string
|
|
25
|
-
): Omit<MessageEnvelope, 'signature'> {
|
|
26
|
-
return {
|
|
27
|
-
id: generateMessageId(),
|
|
28
|
-
from,
|
|
29
|
-
to,
|
|
30
|
-
type,
|
|
31
|
-
protocol,
|
|
32
|
-
payload,
|
|
33
|
-
timestamp: Date.now(),
|
|
34
|
-
replyTo,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Sign a message envelope
|
|
40
|
-
*/
|
|
41
|
-
export async function signEnvelope(
|
|
42
|
-
envelope: Omit<MessageEnvelope, 'signature'>,
|
|
43
|
-
signFn: (data: Uint8Array) => Promise<Uint8Array>
|
|
44
|
-
): Promise<MessageEnvelope> {
|
|
45
|
-
try {
|
|
46
|
-
const envelopeJson = JSON.stringify(envelope);
|
|
47
|
-
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
48
|
-
const signature = await signFn(envelopeBytes);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
...envelope,
|
|
52
|
-
signature: Buffer.from(signature).toString('hex'),
|
|
53
|
-
};
|
|
54
|
-
} catch (error) {
|
|
55
|
-
throw new MessagingError('Failed to sign envelope', error);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Verify a message envelope signature
|
|
61
|
-
*/
|
|
62
|
-
export async function verifyEnvelope(
|
|
63
|
-
envelope: MessageEnvelope,
|
|
64
|
-
verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
|
|
65
|
-
): Promise<boolean> {
|
|
66
|
-
try {
|
|
67
|
-
const { signature, ...envelopeWithoutSig } = envelope;
|
|
68
|
-
const envelopeJson = JSON.stringify(envelopeWithoutSig);
|
|
69
|
-
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
70
|
-
const signatureBytes = Buffer.from(signature, 'hex');
|
|
71
|
-
|
|
72
|
-
return await verifyFn(signatureBytes, envelopeBytes);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
throw new MessagingError('Failed to verify envelope', error);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Validate message envelope structure
|
|
80
|
-
*/
|
|
81
|
-
export function validateEnvelope(msg: unknown): msg is MessageEnvelope {
|
|
82
|
-
if (typeof msg !== 'object' || msg === null) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const m = msg as Partial<MessageEnvelope>;
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
typeof m.id === 'string' &&
|
|
90
|
-
typeof m.from === 'string' &&
|
|
91
|
-
m.from.startsWith('did:clawiverse:') &&
|
|
92
|
-
typeof m.to === 'string' &&
|
|
93
|
-
m.to.startsWith('did:clawiverse:') &&
|
|
94
|
-
(m.type === 'request' || m.type === 'response' || m.type === 'notification') &&
|
|
95
|
-
typeof m.protocol === 'string' &&
|
|
96
|
-
m.payload !== undefined &&
|
|
97
|
-
typeof m.timestamp === 'number' &&
|
|
98
|
-
typeof m.signature === 'string'
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Generate a unique message ID
|
|
104
|
-
*/
|
|
105
|
-
function generateMessageId(): string {
|
|
106
|
-
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
107
|
-
}
|
package/src/messaging/index.ts
DELETED
package/src/messaging/queue.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Queue
|
|
3
|
-
*
|
|
4
|
-
* Persistent inbox/outbox backed by LevelDB.
|
|
5
|
-
* Supports real-time subscriptions and pagination.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createLogger } from '../utils/logger.js';
|
|
9
|
-
import type { MessageEnvelope } from './envelope.js';
|
|
10
|
-
import { MessageStorage } from './storage.js';
|
|
11
|
-
import type {
|
|
12
|
-
StoredMessage,
|
|
13
|
-
MessageFilter,
|
|
14
|
-
PaginationOptions,
|
|
15
|
-
MessagePage,
|
|
16
|
-
MessageCallback,
|
|
17
|
-
SubscriptionFilter,
|
|
18
|
-
QueueStats,
|
|
19
|
-
} from './types.js';
|
|
20
|
-
|
|
21
|
-
const logger = createLogger('message-queue');
|
|
22
|
-
|
|
23
|
-
export interface MessageQueueConfig {
|
|
24
|
-
dbPath: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface Subscription {
|
|
28
|
-
id: string;
|
|
29
|
-
filter: SubscriptionFilter;
|
|
30
|
-
callback: MessageCallback;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class MessageQueue {
|
|
34
|
-
private storage: MessageStorage;
|
|
35
|
-
private subscriptions = new Map<string, Subscription>();
|
|
36
|
-
private subCounter = 0;
|
|
37
|
-
|
|
38
|
-
constructor(config: MessageQueueConfig) {
|
|
39
|
-
this.storage = new MessageStorage(config.dbPath);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get store(): MessageStorage {
|
|
43
|
-
return this.storage;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async start(): Promise<void> {
|
|
47
|
-
await this.storage.open();
|
|
48
|
-
logger.info('Message queue started');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async stop(): Promise<void> {
|
|
52
|
-
await this.storage.close();
|
|
53
|
-
this.subscriptions.clear();
|
|
54
|
-
logger.info('Message queue stopped');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ─── Inbox ────────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
async getInbox(filter: MessageFilter = {}, pagination: PaginationOptions = {}): Promise<MessagePage> {
|
|
60
|
-
return this.storage.queryMessages('inbound', filter, pagination);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async getMessage(id: string): Promise<StoredMessage | null> {
|
|
64
|
-
return this.storage.getMessage(id);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async markAsRead(id: string): Promise<void> {
|
|
68
|
-
await this.storage.updateMessage(id, { readAt: Date.now() });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async deleteMessage(id: string): Promise<void> {
|
|
72
|
-
await this.storage.deleteMessage(id);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ─── Outbox ───────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
async getOutbox(pagination: PaginationOptions = {}): Promise<MessagePage> {
|
|
78
|
-
return this.storage.queryMessages('outbound', {}, pagination);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async retryMessage(id: string): Promise<void> {
|
|
82
|
-
await this.storage.updateMessage(id, { status: 'pending', error: undefined });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ─── Enqueue ──────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
async enqueueInbound(envelope: MessageEnvelope, trustScore?: number): Promise<StoredMessage> {
|
|
88
|
-
const msg: StoredMessage = {
|
|
89
|
-
envelope,
|
|
90
|
-
direction: 'inbound',
|
|
91
|
-
status: 'pending',
|
|
92
|
-
receivedAt: Date.now(),
|
|
93
|
-
trustScore,
|
|
94
|
-
};
|
|
95
|
-
await this.storage.putMessage(msg);
|
|
96
|
-
logger.debug('Enqueued inbound message', { id: envelope.id, from: envelope.from });
|
|
97
|
-
this.notifySubscribers(msg);
|
|
98
|
-
return msg;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async enqueueOutbound(envelope: MessageEnvelope): Promise<StoredMessage> {
|
|
102
|
-
const msg: StoredMessage = {
|
|
103
|
-
envelope,
|
|
104
|
-
direction: 'outbound',
|
|
105
|
-
status: 'pending',
|
|
106
|
-
sentAt: Date.now(),
|
|
107
|
-
};
|
|
108
|
-
await this.storage.putMessage(msg);
|
|
109
|
-
logger.debug('Enqueued outbound message', { id: envelope.id, to: envelope.to });
|
|
110
|
-
return msg;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async markOutboundDelivered(id: string): Promise<void> {
|
|
114
|
-
await this.storage.updateMessage(id, { status: 'delivered' });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async markOutboundFailed(id: string, error: string): Promise<void> {
|
|
118
|
-
await this.storage.updateMessage(id, { status: 'failed', error });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ─── Subscriptions ────────────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
subscribe(filter: SubscriptionFilter, callback: MessageCallback): string {
|
|
124
|
-
const id = `sub_${++this.subCounter}`;
|
|
125
|
-
this.subscriptions.set(id, { id, filter, callback });
|
|
126
|
-
logger.debug('Subscription added', { id });
|
|
127
|
-
return id;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
unsubscribe(subscriptionId: string): void {
|
|
131
|
-
this.subscriptions.delete(subscriptionId);
|
|
132
|
-
logger.debug('Subscription removed', { id: subscriptionId });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private notifySubscribers(msg: StoredMessage): void {
|
|
136
|
-
for (const sub of this.subscriptions.values()) {
|
|
137
|
-
if (this.matchesSubscriptionFilter(msg, sub.filter)) {
|
|
138
|
-
Promise.resolve(sub.callback(msg)).catch((err) => {
|
|
139
|
-
logger.warn('Subscription callback error', { id: sub.id, error: (err as Error).message });
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private matchesSubscriptionFilter(msg: StoredMessage, filter: SubscriptionFilter): boolean {
|
|
146
|
-
if (filter.fromDid) {
|
|
147
|
-
const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
|
|
148
|
-
if (!froms.includes(msg.envelope.from)) return false;
|
|
149
|
-
}
|
|
150
|
-
if (filter.protocol) {
|
|
151
|
-
const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
|
|
152
|
-
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
153
|
-
}
|
|
154
|
-
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
async getStats(): Promise<QueueStats> {
|
|
161
|
-
const [inboxTotal, inboxUnread, outboxPending, outboxFailed, blocked, allowed] =
|
|
162
|
-
await Promise.all([
|
|
163
|
-
this.storage.countMessages('inbound'),
|
|
164
|
-
this.storage.countMessages('inbound', { unreadOnly: true }),
|
|
165
|
-
this.storage.countMessages('outbound', { status: 'pending' }),
|
|
166
|
-
this.storage.countMessages('outbound', { status: 'failed' }),
|
|
167
|
-
this.storage.listBlocked().then((l) => l.length),
|
|
168
|
-
this.storage.listAllowed().then((l) => l.length),
|
|
169
|
-
]);
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
inboxTotal,
|
|
173
|
-
inboxUnread,
|
|
174
|
-
outboxPending,
|
|
175
|
-
outboxFailed,
|
|
176
|
-
blockedAgents: blocked,
|
|
177
|
-
allowedAgents: allowed,
|
|
178
|
-
rateLimitedAgents: 0,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|