@sanctuary-framework/mcp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs ADDED
@@ -0,0 +1,4451 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var sha256 = require('@noble/hashes/sha256');
5
+ var hmac = require('@noble/hashes/hmac');
6
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
7
+ var promises = require('fs/promises');
8
+ var path = require('path');
9
+ var os = require('os');
10
+ var crypto = require('crypto');
11
+ var aes_js = require('@noble/ciphers/aes.js');
12
+ var ed25519 = require('@noble/curves/ed25519');
13
+ var hashWasm = require('hash-wasm');
14
+ var hkdf = require('@noble/hashes/hkdf');
15
+ var index_js = require('@modelcontextprotocol/sdk/server/index.js');
16
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
17
+
18
+ var __defProp = Object.defineProperty;
19
+ var __getOwnPropNames = Object.getOwnPropertyNames;
20
+ var __esm = (fn, res) => function __init() {
21
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
+ };
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+
28
+ // src/core/encoding.ts
29
+ var encoding_exports = {};
30
+ __export(encoding_exports, {
31
+ bytesToString: () => bytesToString,
32
+ concatBytes: () => concatBytes,
33
+ constantTimeEqual: () => constantTimeEqual,
34
+ fromBase64url: () => fromBase64url,
35
+ stringToBytes: () => stringToBytes,
36
+ toBase64url: () => toBase64url
37
+ });
38
+ function toBase64url(bytes) {
39
+ const base64 = Buffer.from(bytes).toString("base64");
40
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
41
+ }
42
+ function fromBase64url(str) {
43
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
44
+ while (base64.length % 4 !== 0) {
45
+ base64 += "=";
46
+ }
47
+ const buf = Buffer.from(base64, "base64");
48
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
49
+ }
50
+ function stringToBytes(str) {
51
+ return new TextEncoder().encode(str);
52
+ }
53
+ function bytesToString(bytes) {
54
+ return new TextDecoder().decode(bytes);
55
+ }
56
+ function concatBytes(...arrays) {
57
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
58
+ const result = new Uint8Array(totalLength);
59
+ let offset = 0;
60
+ for (const arr of arrays) {
61
+ result.set(arr, offset);
62
+ offset += arr.length;
63
+ }
64
+ return result;
65
+ }
66
+ function constantTimeEqual(a, b) {
67
+ if (a.length !== b.length) return false;
68
+ let diff = 0;
69
+ for (let i = 0; i < a.length; i++) {
70
+ diff |= a[i] ^ b[i];
71
+ }
72
+ return diff === 0;
73
+ }
74
+ var init_encoding = __esm({
75
+ "src/core/encoding.ts"() {
76
+ }
77
+ });
78
+
79
+ // src/core/hashing.ts
80
+ var hashing_exports = {};
81
+ __export(hashing_exports, {
82
+ buildMerkleTree: () => buildMerkleTree,
83
+ computeMerkleRoot: () => computeMerkleRoot,
84
+ generateMerkleProof: () => generateMerkleProof,
85
+ hash: () => hash,
86
+ hashToString: () => hashToString,
87
+ hmacSha256: () => hmacSha256,
88
+ verifyMerkleProof: () => verifyMerkleProof
89
+ });
90
+ function hash(data) {
91
+ return sha256.sha256(data);
92
+ }
93
+ function hashToString(data) {
94
+ return toBase64url(hash(data));
95
+ }
96
+ function hmacSha256(key, data) {
97
+ return hmac.hmac(sha256.sha256, key, data);
98
+ }
99
+ function buildMerkleTree(entries) {
100
+ if (entries.size === 0) return null;
101
+ const sortedKeys = Array.from(entries.keys()).sort();
102
+ let nodes = sortedKeys.map((key) => {
103
+ const contentHash = entries.get(key);
104
+ const leafData = concatBytes(
105
+ stringToBytes(key),
106
+ stringToBytes(contentHash)
107
+ );
108
+ return {
109
+ hash: hashToString(leafData),
110
+ key
111
+ };
112
+ });
113
+ while (nodes.length > 1) {
114
+ const nextLevel = [];
115
+ for (let i = 0; i < nodes.length; i += 2) {
116
+ const left = nodes[i];
117
+ if (i + 1 < nodes.length) {
118
+ const right = nodes[i + 1];
119
+ const parentData = concatBytes(
120
+ stringToBytes(left.hash),
121
+ stringToBytes(right.hash)
122
+ );
123
+ nextLevel.push({
124
+ hash: hashToString(parentData),
125
+ left,
126
+ right
127
+ });
128
+ } else {
129
+ nextLevel.push(left);
130
+ }
131
+ }
132
+ nodes = nextLevel;
133
+ }
134
+ return nodes[0] ?? null;
135
+ }
136
+ function generateMerkleProof(entries, targetKey) {
137
+ if (!entries.has(targetKey)) return null;
138
+ const sortedKeys = Array.from(entries.keys()).sort();
139
+ const targetIndex = sortedKeys.indexOf(targetKey);
140
+ if (targetIndex === -1) return null;
141
+ const leafHashes = sortedKeys.map((key) => {
142
+ const contentHash = entries.get(key);
143
+ const leafData = concatBytes(
144
+ stringToBytes(key),
145
+ stringToBytes(contentHash)
146
+ );
147
+ return hashToString(leafData);
148
+ });
149
+ const path = [];
150
+ let currentIndex = targetIndex;
151
+ let currentLevel = leafHashes;
152
+ while (currentLevel.length > 1) {
153
+ const nextLevel = [];
154
+ for (let i = 0; i < currentLevel.length; i += 2) {
155
+ const left = currentLevel[i];
156
+ if (i + 1 < currentLevel.length) {
157
+ const right = currentLevel[i + 1];
158
+ if (i === currentIndex || i + 1 === currentIndex) {
159
+ if (currentIndex === i) {
160
+ path.push({ hash: right, position: "right" });
161
+ } else {
162
+ path.push({ hash: left, position: "left" });
163
+ }
164
+ }
165
+ const parentData = concatBytes(
166
+ stringToBytes(left),
167
+ stringToBytes(right)
168
+ );
169
+ nextLevel.push(hashToString(parentData));
170
+ } else {
171
+ nextLevel.push(left);
172
+ }
173
+ }
174
+ currentIndex = Math.floor(currentIndex / 2);
175
+ currentLevel = nextLevel;
176
+ }
177
+ const root = buildMerkleTree(entries);
178
+ return {
179
+ leaf: leafHashes[targetIndex],
180
+ path,
181
+ root: root?.hash ?? ""
182
+ };
183
+ }
184
+ function verifyMerkleProof(proof) {
185
+ let currentHash = proof.leaf;
186
+ for (const step of proof.path) {
187
+ const left = step.position === "left" ? step.hash : currentHash;
188
+ const right = step.position === "right" ? step.hash : currentHash;
189
+ const parentData = concatBytes(
190
+ stringToBytes(left),
191
+ stringToBytes(right)
192
+ );
193
+ currentHash = hashToString(parentData);
194
+ }
195
+ return currentHash === proof.root;
196
+ }
197
+ function computeMerkleRoot(entries) {
198
+ const tree = buildMerkleTree(entries);
199
+ return tree?.hash ?? "";
200
+ }
201
+ var init_hashing = __esm({
202
+ "src/core/hashing.ts"() {
203
+ init_encoding();
204
+ }
205
+ });
206
+ function defaultConfig() {
207
+ return {
208
+ version: "0.2.0",
209
+ storage_path: path.join(os.homedir(), ".sanctuary"),
210
+ state: {
211
+ encryption: "aes-256-gcm",
212
+ key_protection: "none",
213
+ key_derivation: "argon2id",
214
+ integrity: "merkle-sha256",
215
+ identity_provider: "ed25519"
216
+ },
217
+ execution: {
218
+ environment: "local-process",
219
+ attestation: true,
220
+ resource_limits: {
221
+ max_memory_mb: 512,
222
+ max_storage_mb: 1024,
223
+ max_cpu_percent: 50
224
+ }
225
+ },
226
+ disclosure: {
227
+ proof_system: "commitment-only",
228
+ default_policy: "minimum-necessary"
229
+ },
230
+ reputation: {
231
+ mode: "self-custodied",
232
+ attestation_format: "eas-compatible",
233
+ export_format: "SANCTUARY_REP_V1",
234
+ service_endpoints: []
235
+ },
236
+ transport: "stdio",
237
+ http_port: 3500
238
+ };
239
+ }
240
+ async function loadConfig(configPath) {
241
+ const config = defaultConfig();
242
+ if (process.env.SANCTUARY_STORAGE_PATH) {
243
+ config.storage_path = process.env.SANCTUARY_STORAGE_PATH;
244
+ }
245
+ if (process.env.SANCTUARY_TRANSPORT) {
246
+ config.transport = process.env.SANCTUARY_TRANSPORT;
247
+ }
248
+ if (process.env.SANCTUARY_HTTP_PORT) {
249
+ config.http_port = parseInt(process.env.SANCTUARY_HTTP_PORT, 10);
250
+ }
251
+ const path$1 = configPath ?? path.join(config.storage_path, "sanctuary.json");
252
+ try {
253
+ const raw = await promises.readFile(path$1, "utf-8");
254
+ const fileConfig = JSON.parse(raw);
255
+ return deepMerge(config, fileConfig);
256
+ } catch {
257
+ return config;
258
+ }
259
+ }
260
+ async function saveConfig(config, configPath) {
261
+ const path$1 = path.join(config.storage_path, "sanctuary.json");
262
+ await promises.writeFile(path$1, JSON.stringify(config, null, 2), { mode: 384 });
263
+ }
264
+ function deepMerge(base, override) {
265
+ const result = { ...base };
266
+ for (const [key, value] of Object.entries(override)) {
267
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null) {
268
+ result[key] = deepMerge(
269
+ result[key],
270
+ value
271
+ );
272
+ } else {
273
+ result[key] = value;
274
+ }
275
+ }
276
+ return result;
277
+ }
278
+ function randomBytes(length) {
279
+ if (length <= 0) {
280
+ throw new RangeError("Length must be positive");
281
+ }
282
+ const buf = crypto.randomBytes(length);
283
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
284
+ }
285
+ function generateIV() {
286
+ return randomBytes(12);
287
+ }
288
+ function generateSalt() {
289
+ return randomBytes(32);
290
+ }
291
+ function generateRandomKey() {
292
+ return randomBytes(32);
293
+ }
294
+
295
+ // src/storage/filesystem.ts
296
+ var FilesystemStorage = class {
297
+ basePath;
298
+ constructor(basePath) {
299
+ this.basePath = basePath;
300
+ }
301
+ entryPath(namespace, key) {
302
+ const safeNamespace = namespace.replace(/[^a-zA-Z0-9_-]/g, "_");
303
+ const safeKey = key.replace(/[^a-zA-Z0-9_.-]/g, "_");
304
+ return path.join(this.basePath, safeNamespace, `${safeKey}.enc`);
305
+ }
306
+ namespacePath(namespace) {
307
+ const safeNamespace = namespace.replace(/[^a-zA-Z0-9_-]/g, "_");
308
+ return path.join(this.basePath, safeNamespace);
309
+ }
310
+ async write(namespace, key, data) {
311
+ const dirPath = this.namespacePath(namespace);
312
+ const filePath = this.entryPath(namespace, key);
313
+ await promises.mkdir(dirPath, { recursive: true, mode: 448 });
314
+ await promises.writeFile(filePath, data, { mode: 384 });
315
+ }
316
+ async read(namespace, key) {
317
+ const filePath = this.entryPath(namespace, key);
318
+ try {
319
+ const buf = await promises.readFile(filePath);
320
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
321
+ } catch (err) {
322
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
323
+ return null;
324
+ }
325
+ throw err;
326
+ }
327
+ }
328
+ async delete(namespace, key, secureOverwrite = true) {
329
+ const filePath = this.entryPath(namespace, key);
330
+ try {
331
+ if (secureOverwrite) {
332
+ const fileStat = await promises.stat(filePath);
333
+ const size = fileStat.size;
334
+ for (let pass = 0; pass < 3; pass++) {
335
+ const randomData = randomBytes(size);
336
+ await promises.writeFile(filePath, randomData, { mode: 384 });
337
+ }
338
+ }
339
+ await promises.unlink(filePath);
340
+ return true;
341
+ } catch (err) {
342
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
343
+ return false;
344
+ }
345
+ throw err;
346
+ }
347
+ }
348
+ async list(namespace, prefix) {
349
+ const dirPath = this.namespacePath(namespace);
350
+ try {
351
+ const files = await promises.readdir(dirPath);
352
+ const entries = [];
353
+ for (const file of files) {
354
+ if (!file.endsWith(".enc")) continue;
355
+ const key = file.slice(0, -4);
356
+ if (prefix && !key.startsWith(prefix)) continue;
357
+ const filePath = path.join(dirPath, file);
358
+ const fileStat = await promises.stat(filePath);
359
+ entries.push({
360
+ key,
361
+ namespace,
362
+ size_bytes: fileStat.size,
363
+ modified_at: fileStat.mtime.toISOString()
364
+ });
365
+ }
366
+ return entries.sort((a, b) => a.key.localeCompare(b.key));
367
+ } catch (err) {
368
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
369
+ return [];
370
+ }
371
+ throw err;
372
+ }
373
+ }
374
+ async exists(namespace, key) {
375
+ const filePath = this.entryPath(namespace, key);
376
+ try {
377
+ await promises.stat(filePath);
378
+ return true;
379
+ } catch {
380
+ return false;
381
+ }
382
+ }
383
+ async totalSize() {
384
+ let total = 0;
385
+ try {
386
+ const namespaces = await promises.readdir(this.basePath);
387
+ for (const ns of namespaces) {
388
+ const nsPath = path.join(this.basePath, ns);
389
+ const nsStat = await promises.stat(nsPath);
390
+ if (!nsStat.isDirectory()) continue;
391
+ const files = await promises.readdir(nsPath);
392
+ for (const file of files) {
393
+ const filePath = path.join(nsPath, file);
394
+ const fileStat = await promises.stat(filePath);
395
+ total += fileStat.size;
396
+ }
397
+ }
398
+ } catch {
399
+ }
400
+ return total;
401
+ }
402
+ };
403
+ init_encoding();
404
+ function encrypt(plaintext, key, aad) {
405
+ if (key.length !== 32) {
406
+ throw new Error("Key must be exactly 32 bytes (256 bits)");
407
+ }
408
+ const iv = generateIV();
409
+ const cipher = aes_js.gcm(key, iv, aad);
410
+ const ciphertext = cipher.encrypt(plaintext);
411
+ return {
412
+ v: 1,
413
+ alg: "aes-256-gcm",
414
+ iv: toBase64url(iv),
415
+ ct: toBase64url(ciphertext),
416
+ ts: (/* @__PURE__ */ new Date()).toISOString()
417
+ };
418
+ }
419
+ function decrypt(payload, key, aad) {
420
+ if (key.length !== 32) {
421
+ throw new Error("Key must be exactly 32 bytes (256 bits)");
422
+ }
423
+ if (payload.v !== 1) {
424
+ throw new Error(`Unsupported payload version: ${payload.v}`);
425
+ }
426
+ if (payload.alg !== "aes-256-gcm") {
427
+ throw new Error(`Unsupported algorithm: ${payload.alg}`);
428
+ }
429
+ const iv = fromBase64url(payload.iv);
430
+ const ciphertext = fromBase64url(payload.ct);
431
+ const cipher = aes_js.gcm(key, iv, aad);
432
+ return cipher.decrypt(ciphertext);
433
+ }
434
+
435
+ // src/l1-cognitive/state-store.ts
436
+ init_hashing();
437
+
438
+ // src/core/identity.ts
439
+ init_encoding();
440
+ init_hashing();
441
+ function generateKeypair() {
442
+ const privateKey = randomBytes(32);
443
+ const publicKey = ed25519.ed25519.getPublicKey(privateKey);
444
+ return { publicKey, privateKey };
445
+ }
446
+ function publicKeyToDid(publicKey) {
447
+ const multicodec = new Uint8Array([237, 1, ...publicKey]);
448
+ return `did:key:z${toBase64url(multicodec)}`;
449
+ }
450
+ function generateIdentityId(publicKey) {
451
+ const keyHash = hash(publicKey);
452
+ return Array.from(keyHash.slice(0, 16)).map((b) => b.toString(16).padStart(2, "0")).join("");
453
+ }
454
+ function createIdentity(label, encryptionKey, keyProtection) {
455
+ const { publicKey, privateKey } = generateKeypair();
456
+ const identityId = generateIdentityId(publicKey);
457
+ const did = publicKeyToDid(publicKey);
458
+ const now = (/* @__PURE__ */ new Date()).toISOString();
459
+ const encryptedPrivateKey = encrypt(privateKey, encryptionKey);
460
+ privateKey.fill(0);
461
+ const publicIdentity = {
462
+ identity_id: identityId,
463
+ label,
464
+ public_key: toBase64url(publicKey),
465
+ did,
466
+ created_at: now,
467
+ key_type: "ed25519",
468
+ key_protection: keyProtection
469
+ };
470
+ const storedIdentity = {
471
+ ...publicIdentity,
472
+ encrypted_private_key: encryptedPrivateKey,
473
+ rotation_history: []
474
+ };
475
+ return { publicIdentity, storedIdentity };
476
+ }
477
+ function sign(payload, encryptedPrivateKey, encryptionKey) {
478
+ const privateKey = decrypt(encryptedPrivateKey, encryptionKey);
479
+ try {
480
+ return ed25519.ed25519.sign(payload, privateKey);
481
+ } finally {
482
+ privateKey.fill(0);
483
+ }
484
+ }
485
+ function verify(payload, signature, publicKey) {
486
+ try {
487
+ return ed25519.ed25519.verify(signature, payload, publicKey);
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+ function rotateKeys(storedIdentity, encryptionKey, reason) {
493
+ const { publicKey: newPublicKey, privateKey: newPrivateKey } = generateKeypair();
494
+ const newIdentityDid = publicKeyToDid(newPublicKey);
495
+ const now = (/* @__PURE__ */ new Date()).toISOString();
496
+ const eventData = JSON.stringify({
497
+ old_public_key: storedIdentity.public_key,
498
+ new_public_key: toBase64url(newPublicKey),
499
+ identity_id: storedIdentity.identity_id,
500
+ reason,
501
+ rotated_at: now
502
+ });
503
+ const eventBytes = new TextEncoder().encode(eventData);
504
+ const signature = sign(
505
+ eventBytes,
506
+ storedIdentity.encrypted_private_key,
507
+ encryptionKey
508
+ );
509
+ const rotationEvent = {
510
+ old_public_key: storedIdentity.public_key,
511
+ new_public_key: toBase64url(newPublicKey),
512
+ identity_id: storedIdentity.identity_id,
513
+ reason,
514
+ rotated_at: now,
515
+ signature: toBase64url(signature)
516
+ };
517
+ const encryptedNewPrivateKey = encrypt(newPrivateKey, encryptionKey);
518
+ newPrivateKey.fill(0);
519
+ const updatedIdentity = {
520
+ ...storedIdentity,
521
+ public_key: toBase64url(newPublicKey),
522
+ did: newIdentityDid,
523
+ encrypted_private_key: encryptedNewPrivateKey,
524
+ rotation_history: [
525
+ ...storedIdentity.rotation_history,
526
+ {
527
+ old_public_key: storedIdentity.public_key,
528
+ new_public_key: toBase64url(newPublicKey),
529
+ rotation_event: toBase64url(
530
+ new TextEncoder().encode(JSON.stringify(rotationEvent))
531
+ ),
532
+ rotated_at: now
533
+ }
534
+ ]
535
+ };
536
+ return { updatedIdentity, rotationEvent };
537
+ }
538
+ init_encoding();
539
+ var ARGON2_MEMORY_COST = 65536;
540
+ var ARGON2_TIME_COST = 3;
541
+ var ARGON2_PARALLELISM = 4;
542
+ var ARGON2_HASH_LENGTH = 32;
543
+ async function deriveMasterKey(passphrase, existingParams) {
544
+ const salt = existingParams ? fromBase64url(existingParams.salt) : generateSalt();
545
+ const params = existingParams ?? {
546
+ alg: "argon2id",
547
+ salt: toBase64url(salt),
548
+ m: ARGON2_MEMORY_COST,
549
+ t: ARGON2_TIME_COST,
550
+ p: ARGON2_PARALLELISM,
551
+ l: ARGON2_HASH_LENGTH
552
+ };
553
+ const hashHex = await hashWasm.argon2id({
554
+ password: passphrase,
555
+ salt,
556
+ parallelism: params.p,
557
+ iterations: params.t,
558
+ memorySize: params.m,
559
+ hashLength: params.l,
560
+ outputType: "hex"
561
+ });
562
+ const key = new Uint8Array(params.l);
563
+ for (let i = 0; i < params.l; i++) {
564
+ key[i] = parseInt(hashHex.substring(i * 2, i * 2 + 2), 16);
565
+ }
566
+ return { key, params };
567
+ }
568
+ function deriveNamespaceKey(masterKey, namespace) {
569
+ if (masterKey.length !== 32) {
570
+ throw new Error("Master key must be 32 bytes");
571
+ }
572
+ return hkdf.hkdf(
573
+ sha256.sha256,
574
+ masterKey,
575
+ stringToBytes("sanctuary-namespace-v1"),
576
+ // salt (fixed, acts as domain separator)
577
+ stringToBytes(namespace),
578
+ // info (namespace name)
579
+ 32
580
+ // output length: 256 bits
581
+ );
582
+ }
583
+ function derivePurposeKey(masterKey, purpose) {
584
+ if (masterKey.length !== 32) {
585
+ throw new Error("Master key must be 32 bytes");
586
+ }
587
+ return hkdf.hkdf(
588
+ sha256.sha256,
589
+ masterKey,
590
+ stringToBytes("sanctuary-purpose-v1"),
591
+ stringToBytes(purpose),
592
+ 32
593
+ );
594
+ }
595
+
596
+ // src/l1-cognitive/state-store.ts
597
+ init_encoding();
598
+ var RESERVED_NAMESPACE_PREFIXES = [
599
+ "_identities",
600
+ "_policies",
601
+ "_audit",
602
+ "_meta",
603
+ "_principal",
604
+ "_commitments",
605
+ "_reputation",
606
+ "_escrow",
607
+ "_guarantees"
608
+ ];
609
+ var StateStore = class {
610
+ storage;
611
+ masterKey;
612
+ // Cache of version numbers per namespace/key for anti-rollback
613
+ versionCache = /* @__PURE__ */ new Map();
614
+ // Cache of content hashes per namespace for Merkle tree computation
615
+ contentHashes = /* @__PURE__ */ new Map();
616
+ constructor(storage, masterKey) {
617
+ this.storage = storage;
618
+ this.masterKey = masterKey;
619
+ }
620
+ versionKey(namespace, key) {
621
+ return `${namespace}/${key}`;
622
+ }
623
+ /**
624
+ * Get or initialize the content hash map for a namespace.
625
+ */
626
+ async getNamespaceHashes(namespace) {
627
+ if (this.contentHashes.has(namespace)) {
628
+ return this.contentHashes.get(namespace);
629
+ }
630
+ const entries = await this.storage.list(namespace);
631
+ const hashMap = /* @__PURE__ */ new Map();
632
+ for (const entry of entries) {
633
+ const raw = await this.storage.read(namespace, entry.key);
634
+ if (raw) {
635
+ try {
636
+ const stateEntry = JSON.parse(bytesToString(raw));
637
+ hashMap.set(entry.key, stateEntry.integrity_hash);
638
+ this.versionCache.set(
639
+ this.versionKey(namespace, entry.key),
640
+ stateEntry.ver
641
+ );
642
+ } catch {
643
+ }
644
+ }
645
+ }
646
+ this.contentHashes.set(namespace, hashMap);
647
+ return hashMap;
648
+ }
649
+ /**
650
+ * Write encrypted state.
651
+ *
652
+ * @param namespace - Logical grouping
653
+ * @param key - State key
654
+ * @param value - Plaintext value (will be encrypted)
655
+ * @param identityId - Identity performing the write
656
+ * @param encryptedPrivateKey - Identity's encrypted private key (for signing)
657
+ * @param identityEncryptionKey - Key to decrypt the identity's private key
658
+ * @param options - Optional metadata
659
+ */
660
+ async write(namespace, key, value, identityId, encryptedPrivateKey, identityEncryptionKey, options = {}) {
661
+ const namespaceKey = deriveNamespaceKey(this.masterKey, namespace);
662
+ const plaintext = stringToBytes(value);
663
+ const integrityHash = hashToString(plaintext);
664
+ const payload = encrypt(plaintext, namespaceKey);
665
+ const vk = this.versionKey(namespace, key);
666
+ const currentVersion = this.versionCache.get(vk) ?? 0;
667
+ const newVersion = currentVersion + 1;
668
+ const ciphertextBytes = fromBase64url(payload.ct);
669
+ const signature = sign(
670
+ ciphertextBytes,
671
+ encryptedPrivateKey,
672
+ identityEncryptionKey
673
+ );
674
+ const now = (/* @__PURE__ */ new Date()).toISOString();
675
+ const stateEntry = {
676
+ v: 1,
677
+ payload,
678
+ ver: newVersion,
679
+ sig: toBase64url(signature),
680
+ kid: identityId,
681
+ integrity_hash: integrityHash,
682
+ metadata: {
683
+ content_type: options.content_type,
684
+ ttl_seconds: options.ttl_seconds,
685
+ tags: options.tags,
686
+ written_at: now
687
+ }
688
+ };
689
+ const serialized = stringToBytes(JSON.stringify(stateEntry));
690
+ await this.storage.write(namespace, key, serialized);
691
+ this.versionCache.set(vk, newVersion);
692
+ const nsHashes = await this.getNamespaceHashes(namespace);
693
+ nsHashes.set(key, integrityHash);
694
+ const merkleRoot = computeMerkleRoot(nsHashes);
695
+ return {
696
+ key,
697
+ namespace,
698
+ version: newVersion,
699
+ merkle_root: merkleRoot,
700
+ written_at: now,
701
+ size_bytes: serialized.length,
702
+ integrity_hash: integrityHash
703
+ };
704
+ }
705
+ /**
706
+ * Read and decrypt state.
707
+ *
708
+ * @param namespace - Logical grouping
709
+ * @param key - State key
710
+ * @param signerPublicKey - Expected signer's public key (for signature verification)
711
+ * @param verifyIntegrity - Whether to verify Merkle proof (default: true)
712
+ */
713
+ async read(namespace, key, signerPublicKey, verifyIntegrity = true) {
714
+ const raw = await this.storage.read(namespace, key);
715
+ if (!raw) return null;
716
+ let stateEntry;
717
+ try {
718
+ stateEntry = JSON.parse(bytesToString(raw));
719
+ } catch {
720
+ throw new Error(`Corrupted state entry: ${namespace}/${key}`);
721
+ }
722
+ if (stateEntry.v !== 1) {
723
+ throw new Error(`Unsupported state entry version: ${stateEntry.v}`);
724
+ }
725
+ const vk = this.versionKey(namespace, key);
726
+ const cachedVersion = this.versionCache.get(vk);
727
+ if (cachedVersion !== void 0 && stateEntry.ver < cachedVersion) {
728
+ throw new Error(
729
+ `Rollback detected for ${namespace}/${key}: found version ${stateEntry.ver}, expected >= ${cachedVersion}`
730
+ );
731
+ }
732
+ if (signerPublicKey) {
733
+ const ciphertextBytes = fromBase64url(stateEntry.payload.ct);
734
+ const signatureBytes = fromBase64url(stateEntry.sig);
735
+ const sigValid = verify(ciphertextBytes, signatureBytes, signerPublicKey);
736
+ if (!sigValid) {
737
+ throw new Error(
738
+ `Signature verification failed for ${namespace}/${key}`
739
+ );
740
+ }
741
+ }
742
+ const namespaceKey = deriveNamespaceKey(this.masterKey, namespace);
743
+ const plaintext = decrypt(stateEntry.payload, namespaceKey);
744
+ const value = bytesToString(plaintext);
745
+ const computedHash = hashToString(plaintext);
746
+ if (computedHash !== stateEntry.integrity_hash) {
747
+ throw new Error(
748
+ `Integrity hash mismatch for ${namespace}/${key}: computed ${computedHash}, stored ${stateEntry.integrity_hash}`
749
+ );
750
+ }
751
+ let merkleProofPath = [];
752
+ let integrityVerified = true;
753
+ if (verifyIntegrity) {
754
+ const nsHashes = await this.getNamespaceHashes(namespace);
755
+ const proof = generateMerkleProof(nsHashes, key);
756
+ if (proof) {
757
+ integrityVerified = verifyMerkleProof(proof);
758
+ merkleProofPath = proof.path.map(
759
+ (step) => `${step.position}:${step.hash}`
760
+ );
761
+ }
762
+ }
763
+ this.versionCache.set(vk, stateEntry.ver);
764
+ return {
765
+ key,
766
+ namespace,
767
+ value,
768
+ version: stateEntry.ver,
769
+ integrity_verified: integrityVerified,
770
+ merkle_proof: merkleProofPath,
771
+ written_at: stateEntry.metadata.written_at,
772
+ written_by: stateEntry.kid
773
+ };
774
+ }
775
+ /**
776
+ * List keys in a namespace (metadata only — no decryption).
777
+ */
778
+ async list(namespace, prefix, tags, limit = 100, offset = 0) {
779
+ const storageEntries = await this.storage.list(namespace, prefix);
780
+ const result = [];
781
+ for (const entry of storageEntries) {
782
+ const raw = await this.storage.read(namespace, entry.key);
783
+ if (!raw) continue;
784
+ try {
785
+ const stateEntry = JSON.parse(bytesToString(raw));
786
+ if (tags && tags.length > 0) {
787
+ const entryTags = stateEntry.metadata.tags ?? [];
788
+ const hasMatchingTag = tags.some((t) => entryTags.includes(t));
789
+ if (!hasMatchingTag) continue;
790
+ }
791
+ result.push({
792
+ key: entry.key,
793
+ version: stateEntry.ver,
794
+ size_bytes: entry.size_bytes,
795
+ written_at: stateEntry.metadata.written_at,
796
+ tags: stateEntry.metadata.tags ?? []
797
+ });
798
+ } catch {
799
+ }
800
+ }
801
+ const nsHashes = await this.getNamespaceHashes(namespace);
802
+ const merkleRoot = computeMerkleRoot(nsHashes);
803
+ return {
804
+ keys: result.slice(offset, offset + limit),
805
+ total: result.length,
806
+ merkle_root: merkleRoot
807
+ };
808
+ }
809
+ /**
810
+ * Securely delete state (overwrite with random bytes before removal).
811
+ */
812
+ async delete(namespace, key) {
813
+ const deleted = await this.storage.delete(namespace, key, true);
814
+ const vk = this.versionKey(namespace, key);
815
+ this.versionCache.delete(vk);
816
+ const nsHashes = await this.getNamespaceHashes(namespace);
817
+ nsHashes.delete(key);
818
+ const merkleRoot = computeMerkleRoot(nsHashes);
819
+ return {
820
+ deleted,
821
+ key,
822
+ namespace,
823
+ new_merkle_root: merkleRoot,
824
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString()
825
+ };
826
+ }
827
+ /**
828
+ * Export all state for a namespace as an encrypted bundle.
829
+ */
830
+ async export(namespace) {
831
+ const namespacesToExport = [];
832
+ if (namespace) {
833
+ namespacesToExport.push(namespace);
834
+ } else {
835
+ for (const ns of this.contentHashes.keys()) {
836
+ namespacesToExport.push(ns);
837
+ }
838
+ }
839
+ const exportData = {};
840
+ let totalKeys = 0;
841
+ for (const ns of namespacesToExport) {
842
+ const entries = await this.storage.list(ns);
843
+ exportData[ns] = [];
844
+ for (const entry of entries) {
845
+ const raw = await this.storage.read(ns, entry.key);
846
+ if (!raw) continue;
847
+ try {
848
+ const stateEntry = JSON.parse(bytesToString(raw));
849
+ exportData[ns].push({ key: entry.key, entry: stateEntry });
850
+ totalKeys++;
851
+ } catch {
852
+ }
853
+ }
854
+ }
855
+ const bundleJson = JSON.stringify({
856
+ sanctuary_export_version: 1,
857
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
858
+ namespaces: namespacesToExport,
859
+ data: exportData
860
+ });
861
+ const bundleBytes = stringToBytes(bundleJson);
862
+ const bundleHash = hashToString(bundleBytes);
863
+ return {
864
+ bundle: toBase64url(bundleBytes),
865
+ namespaces: namespacesToExport,
866
+ total_keys: totalKeys,
867
+ bundle_hash: bundleHash,
868
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
869
+ };
870
+ }
871
+ /**
872
+ * Import a previously exported state bundle.
873
+ */
874
+ async import(bundleBase64, conflictResolution = "skip") {
875
+ const bundleBytes = fromBase64url(bundleBase64);
876
+ const bundleJson = bytesToString(bundleBytes);
877
+ const bundle = JSON.parse(bundleJson);
878
+ let importedKeys = 0;
879
+ let skippedKeys = 0;
880
+ let conflicts = 0;
881
+ const namespaces = [];
882
+ for (const [ns, entries] of Object.entries(
883
+ bundle.data
884
+ )) {
885
+ if (RESERVED_NAMESPACE_PREFIXES.some(
886
+ (prefix) => ns === prefix || ns.startsWith(prefix + "/")
887
+ )) {
888
+ skippedKeys += entries.length;
889
+ continue;
890
+ }
891
+ namespaces.push(ns);
892
+ for (const { key, entry } of entries) {
893
+ const exists = await this.storage.exists(ns, key);
894
+ if (exists) {
895
+ conflicts++;
896
+ if (conflictResolution === "skip") {
897
+ skippedKeys++;
898
+ continue;
899
+ }
900
+ if (conflictResolution === "version") {
901
+ const raw = await this.storage.read(ns, key);
902
+ if (raw) {
903
+ try {
904
+ const existingEntry = JSON.parse(
905
+ bytesToString(raw)
906
+ );
907
+ if (entry.ver <= existingEntry.ver) {
908
+ skippedKeys++;
909
+ continue;
910
+ }
911
+ } catch {
912
+ }
913
+ }
914
+ }
915
+ }
916
+ const serialized = stringToBytes(JSON.stringify(entry));
917
+ await this.storage.write(ns, key, serialized);
918
+ importedKeys++;
919
+ const vk = this.versionKey(ns, key);
920
+ this.versionCache.set(vk, entry.ver);
921
+ const nsHashes = await this.getNamespaceHashes(ns);
922
+ nsHashes.set(key, entry.integrity_hash);
923
+ }
924
+ }
925
+ return {
926
+ imported_keys: importedKeys,
927
+ skipped_keys: skippedKeys,
928
+ conflicts,
929
+ namespaces,
930
+ imported_at: (/* @__PURE__ */ new Date()).toISOString()
931
+ };
932
+ }
933
+ };
934
+ var MAX_STRING_BYTES = 1048576;
935
+ var MAX_BUNDLE_BYTES = 5242880;
936
+ var BUNDLE_FIELDS = /* @__PURE__ */ new Set(["bundle"]);
937
+ function validateArgs(args, schema) {
938
+ const errors = [];
939
+ const properties = schema.properties ?? {};
940
+ const required = schema.required ?? [];
941
+ for (const field of required) {
942
+ if (args[field] === void 0 || args[field] === null) {
943
+ errors.push({ field, message: `Required field "${field}" is missing` });
944
+ }
945
+ }
946
+ const knownFields = new Set(Object.keys(properties));
947
+ for (const field of Object.keys(args)) {
948
+ if (!knownFields.has(field)) {
949
+ errors.push({ field, message: `Unknown field "${field}"` });
950
+ }
951
+ }
952
+ for (const [field, value] of Object.entries(args)) {
953
+ if (value === void 0 || value === null) continue;
954
+ const propSchema = properties[field];
955
+ if (!propSchema) continue;
956
+ const typeError = checkType(field, value, propSchema);
957
+ if (typeError) {
958
+ errors.push(typeError);
959
+ continue;
960
+ }
961
+ if (typeof value === "string") {
962
+ const maxBytes = BUNDLE_FIELDS.has(field) ? MAX_BUNDLE_BYTES : MAX_STRING_BYTES;
963
+ const byteLength = new TextEncoder().encode(value).length;
964
+ if (byteLength > maxBytes) {
965
+ errors.push({
966
+ field,
967
+ message: `Field "${field}" exceeds maximum size (${byteLength} bytes > ${maxBytes} bytes)`
968
+ });
969
+ }
970
+ }
971
+ if (propSchema.enum && !propSchema.enum.includes(value)) {
972
+ errors.push({
973
+ field,
974
+ message: `Field "${field}" must be one of: ${propSchema.enum.join(", ")}`
975
+ });
976
+ }
977
+ }
978
+ return errors;
979
+ }
980
+ function checkType(field, value, schema) {
981
+ if (!schema.type) return null;
982
+ switch (schema.type) {
983
+ case "string":
984
+ if (typeof value !== "string") {
985
+ return { field, message: `Expected string for "${field}", got ${typeof value}` };
986
+ }
987
+ break;
988
+ case "number":
989
+ if (typeof value !== "number") {
990
+ return { field, message: `Expected number for "${field}", got ${typeof value}` };
991
+ }
992
+ break;
993
+ case "boolean":
994
+ if (typeof value !== "boolean") {
995
+ return { field, message: `Expected boolean for "${field}", got ${typeof value}` };
996
+ }
997
+ break;
998
+ case "object":
999
+ if (typeof value !== "object" || Array.isArray(value)) {
1000
+ return { field, message: `Expected object for "${field}", got ${typeof value}` };
1001
+ }
1002
+ break;
1003
+ case "array":
1004
+ if (!Array.isArray(value)) {
1005
+ return { field, message: `Expected array for "${field}", got ${typeof value}` };
1006
+ }
1007
+ break;
1008
+ }
1009
+ return null;
1010
+ }
1011
+ function createServer(tools, options) {
1012
+ const gate = options?.gate;
1013
+ const server = new index_js.Server(
1014
+ {
1015
+ name: "sanctuary-mcp-server",
1016
+ version: "0.2.0"
1017
+ },
1018
+ {
1019
+ capabilities: {
1020
+ tools: {}
1021
+ }
1022
+ }
1023
+ );
1024
+ server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
1025
+ return {
1026
+ tools: tools.map((t) => ({
1027
+ name: t.name,
1028
+ description: t.description,
1029
+ inputSchema: t.inputSchema
1030
+ }))
1031
+ };
1032
+ });
1033
+ server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
1034
+ const { name, arguments: args } = request.params;
1035
+ const typedArgs = args ?? {};
1036
+ const tool = tools.find((t) => t.name === name);
1037
+ if (!tool) {
1038
+ return {
1039
+ content: [
1040
+ {
1041
+ type: "text",
1042
+ text: JSON.stringify({ error: `Unknown tool: ${name}` })
1043
+ }
1044
+ ],
1045
+ isError: true
1046
+ };
1047
+ }
1048
+ const validationErrors = validateArgs(typedArgs, tool.inputSchema);
1049
+ if (validationErrors.length > 0) {
1050
+ return {
1051
+ content: [
1052
+ {
1053
+ type: "text",
1054
+ text: JSON.stringify({
1055
+ error: "validation_failed",
1056
+ message: "Tool arguments failed schema validation",
1057
+ violations: validationErrors
1058
+ })
1059
+ }
1060
+ ],
1061
+ isError: true
1062
+ };
1063
+ }
1064
+ if (gate) {
1065
+ const result = await gate.evaluate(name, typedArgs);
1066
+ if (!result.allowed) {
1067
+ return {
1068
+ content: [
1069
+ {
1070
+ type: "text",
1071
+ text: JSON.stringify({
1072
+ error: "Operation not permitted",
1073
+ approval_required: result.approval_required
1074
+ })
1075
+ }
1076
+ ],
1077
+ isError: true
1078
+ };
1079
+ }
1080
+ }
1081
+ try {
1082
+ return await tool.handler(typedArgs);
1083
+ } catch (err) {
1084
+ const message = err instanceof Error ? err.message : "Unknown error";
1085
+ return {
1086
+ content: [
1087
+ {
1088
+ type: "text",
1089
+ text: JSON.stringify({ error: message })
1090
+ }
1091
+ ],
1092
+ isError: true
1093
+ };
1094
+ }
1095
+ });
1096
+ return server;
1097
+ }
1098
+ function toolResult(data) {
1099
+ return {
1100
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1101
+ };
1102
+ }
1103
+
1104
+ // src/l1-cognitive/tools.ts
1105
+ init_encoding();
1106
+ init_encoding();
1107
+ var RESERVED_NAMESPACE_PREFIXES2 = [
1108
+ "_identities",
1109
+ "_policies",
1110
+ "_audit",
1111
+ "_meta",
1112
+ "_principal",
1113
+ "_commitments",
1114
+ "_reputation",
1115
+ "_escrow",
1116
+ "_guarantees"
1117
+ ];
1118
+ function getReservedNamespaceViolation(namespace) {
1119
+ for (const prefix of RESERVED_NAMESPACE_PREFIXES2) {
1120
+ if (namespace === prefix || namespace.startsWith(prefix + "/")) {
1121
+ return prefix;
1122
+ }
1123
+ }
1124
+ return null;
1125
+ }
1126
+ var IdentityManager = class {
1127
+ storage;
1128
+ masterKey;
1129
+ identities = /* @__PURE__ */ new Map();
1130
+ primaryIdentityId = null;
1131
+ constructor(storage, masterKey) {
1132
+ this.storage = storage;
1133
+ this.masterKey = masterKey;
1134
+ }
1135
+ get encryptionKey() {
1136
+ return derivePurposeKey(this.masterKey, "identity-encryption");
1137
+ }
1138
+ /** Load identities from storage on startup */
1139
+ async load() {
1140
+ const entries = await this.storage.list("_identities");
1141
+ for (const entry of entries) {
1142
+ const raw = await this.storage.read("_identities", entry.key);
1143
+ if (!raw) continue;
1144
+ try {
1145
+ const encrypted = JSON.parse(bytesToString(raw));
1146
+ const decrypted = decrypt(encrypted, this.encryptionKey);
1147
+ const identity = JSON.parse(bytesToString(decrypted));
1148
+ this.identities.set(identity.identity_id, identity);
1149
+ if (!this.primaryIdentityId) {
1150
+ this.primaryIdentityId = identity.identity_id;
1151
+ }
1152
+ } catch {
1153
+ }
1154
+ }
1155
+ }
1156
+ /** Save an identity to storage */
1157
+ async save(identity) {
1158
+ const serialized = stringToBytes(JSON.stringify(identity));
1159
+ const encrypted = encrypt(serialized, this.encryptionKey);
1160
+ await this.storage.write(
1161
+ "_identities",
1162
+ identity.identity_id,
1163
+ stringToBytes(JSON.stringify(encrypted))
1164
+ );
1165
+ this.identities.set(identity.identity_id, identity);
1166
+ if (!this.primaryIdentityId) {
1167
+ this.primaryIdentityId = identity.identity_id;
1168
+ }
1169
+ }
1170
+ get(id) {
1171
+ return this.identities.get(id);
1172
+ }
1173
+ getDefault() {
1174
+ if (!this.primaryIdentityId) return void 0;
1175
+ return this.identities.get(this.primaryIdentityId);
1176
+ }
1177
+ list() {
1178
+ return Array.from(this.identities.values()).map((si) => ({
1179
+ identity_id: si.identity_id,
1180
+ label: si.label,
1181
+ public_key: si.public_key,
1182
+ did: si.did,
1183
+ created_at: si.created_at,
1184
+ key_type: si.key_type,
1185
+ key_protection: si.key_protection
1186
+ }));
1187
+ }
1188
+ };
1189
+ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog) {
1190
+ const identityMgr = new IdentityManager(storage, masterKey);
1191
+ const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
1192
+ function resolveIdentity(identityId) {
1193
+ const id = identityId ? identityMgr.get(identityId) : identityMgr.getDefault();
1194
+ if (!id) {
1195
+ throw new Error(
1196
+ identityId ? `Identity not found: ${identityId}` : "No default identity. Create one with sanctuary/identity_create."
1197
+ );
1198
+ }
1199
+ return id;
1200
+ }
1201
+ const tools = [
1202
+ // ── Identity Tools ──────────────────────────────────────────────────
1203
+ {
1204
+ name: "sanctuary/identity_create",
1205
+ description: "Create a new sovereign identity (Ed25519 keypair). The private key is encrypted and never exposed.",
1206
+ inputSchema: {
1207
+ type: "object",
1208
+ properties: {
1209
+ label: {
1210
+ type: "string",
1211
+ description: 'Human-readable label (e.g., "my-agent")'
1212
+ }
1213
+ },
1214
+ required: ["label"]
1215
+ },
1216
+ handler: async (args) => {
1217
+ const label = args.label;
1218
+ const { publicIdentity, storedIdentity } = createIdentity(
1219
+ label,
1220
+ identityEncKey,
1221
+ keyProtection
1222
+ );
1223
+ await identityMgr.save(storedIdentity);
1224
+ auditLog?.append("l1", "identity_create", publicIdentity.identity_id, {
1225
+ label
1226
+ });
1227
+ return toolResult({
1228
+ identity_id: publicIdentity.identity_id,
1229
+ public_key: publicIdentity.public_key,
1230
+ did: publicIdentity.did,
1231
+ created_at: publicIdentity.created_at,
1232
+ key_type: publicIdentity.key_type,
1233
+ key_protection: publicIdentity.key_protection,
1234
+ backed_up: false
1235
+ });
1236
+ }
1237
+ },
1238
+ {
1239
+ name: "sanctuary/identity_list",
1240
+ description: "List all managed sovereign identities.",
1241
+ inputSchema: {
1242
+ type: "object",
1243
+ properties: {
1244
+ filter: {
1245
+ type: "object",
1246
+ properties: {
1247
+ label: { type: "string" }
1248
+ }
1249
+ }
1250
+ }
1251
+ },
1252
+ handler: async (args) => {
1253
+ let identities = identityMgr.list();
1254
+ const filter = args.filter;
1255
+ if (filter?.label) {
1256
+ identities = identities.filter(
1257
+ (i) => i.label.includes(filter.label)
1258
+ );
1259
+ }
1260
+ return toolResult({ identities });
1261
+ }
1262
+ },
1263
+ {
1264
+ name: "sanctuary/identity_sign",
1265
+ description: "Sign data with a managed identity. The private key is decrypted in memory only during signing.",
1266
+ inputSchema: {
1267
+ type: "object",
1268
+ properties: {
1269
+ identity_id: { type: "string" },
1270
+ payload: {
1271
+ type: "string",
1272
+ description: "Base64url-encoded data to sign"
1273
+ }
1274
+ },
1275
+ required: ["payload"]
1276
+ },
1277
+ handler: async (args) => {
1278
+ const identity = resolveIdentity(args.identity_id);
1279
+ const payloadStr = args.payload;
1280
+ let payload;
1281
+ try {
1282
+ payload = fromBase64url(payloadStr);
1283
+ } catch {
1284
+ payload = stringToBytes(payloadStr);
1285
+ }
1286
+ const signature = sign(
1287
+ payload,
1288
+ identity.encrypted_private_key,
1289
+ identityEncKey
1290
+ );
1291
+ auditLog?.append("l1", "identity_sign", identity.identity_id);
1292
+ return toolResult({
1293
+ signature: toBase64url(signature),
1294
+ algorithm: "Ed25519",
1295
+ signed_at: (/* @__PURE__ */ new Date()).toISOString(),
1296
+ public_key: identity.public_key,
1297
+ payload_encoding: "base64url"
1298
+ });
1299
+ }
1300
+ },
1301
+ {
1302
+ name: "sanctuary/identity_verify",
1303
+ description: "Verify an Ed25519 signature. Provide either identity_id or public_key.",
1304
+ inputSchema: {
1305
+ type: "object",
1306
+ properties: {
1307
+ payload: {
1308
+ type: "string",
1309
+ description: "Original data (plain text or base64url-encoded)"
1310
+ },
1311
+ signature: { type: "string", description: "Base64url signature" },
1312
+ identity_id: {
1313
+ type: "string",
1314
+ description: "Identity ID to look up public key (alternative to public_key)"
1315
+ },
1316
+ public_key: {
1317
+ type: "string",
1318
+ description: "Base64url public key (alternative to identity_id)"
1319
+ }
1320
+ },
1321
+ required: ["payload", "signature"]
1322
+ },
1323
+ handler: async (args) => {
1324
+ const payloadStr = args.payload;
1325
+ let payload;
1326
+ try {
1327
+ payload = fromBase64url(payloadStr);
1328
+ } catch {
1329
+ payload = stringToBytes(payloadStr);
1330
+ }
1331
+ const signature = fromBase64url(args.signature);
1332
+ let publicKey;
1333
+ if (args.identity_id) {
1334
+ const identity = resolveIdentity(args.identity_id);
1335
+ publicKey = fromBase64url(identity.public_key);
1336
+ } else if (args.public_key) {
1337
+ publicKey = fromBase64url(args.public_key);
1338
+ } else {
1339
+ return toolResult({
1340
+ error: "Provide either identity_id or public_key for verification."
1341
+ });
1342
+ }
1343
+ const valid = verify(payload, signature, publicKey);
1344
+ return toolResult({
1345
+ valid,
1346
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
1347
+ });
1348
+ }
1349
+ },
1350
+ {
1351
+ name: "sanctuary/identity_rotate",
1352
+ description: "Rotate keys for an identity. Generates a new keypair and signs a rotation event with the old key for verifiable chain.",
1353
+ inputSchema: {
1354
+ type: "object",
1355
+ properties: {
1356
+ identity_id: { type: "string" },
1357
+ reason: { type: "string" }
1358
+ },
1359
+ required: ["identity_id"]
1360
+ },
1361
+ handler: async (args) => {
1362
+ const identity = resolveIdentity(args.identity_id);
1363
+ const reason = args.reason ?? "Key rotation";
1364
+ const { updatedIdentity, rotationEvent } = rotateKeys(
1365
+ identity,
1366
+ identityEncKey,
1367
+ reason
1368
+ );
1369
+ await identityMgr.save(updatedIdentity);
1370
+ auditLog?.append("l1", "identity_rotate", identity.identity_id, {
1371
+ reason
1372
+ });
1373
+ return toolResult({
1374
+ identity_id: updatedIdentity.identity_id,
1375
+ old_public_key: rotationEvent.old_public_key,
1376
+ new_public_key: rotationEvent.new_public_key,
1377
+ new_did: updatedIdentity.did,
1378
+ rotated_at: rotationEvent.rotated_at
1379
+ });
1380
+ }
1381
+ },
1382
+ // ── State Tools ─────────────────────────────────────────────────────
1383
+ {
1384
+ name: "sanctuary/state_write",
1385
+ description: "Write encrypted state to the sovereign store. Value is encrypted with a namespace-specific key. The write is signed by the active identity.",
1386
+ inputSchema: {
1387
+ type: "object",
1388
+ properties: {
1389
+ namespace: {
1390
+ type: "string",
1391
+ description: 'Logical grouping (e.g., "memory", "config")'
1392
+ },
1393
+ key: { type: "string", description: "State key within namespace" },
1394
+ value: {
1395
+ type: "string",
1396
+ description: "Plaintext value (encrypted before storage)"
1397
+ },
1398
+ metadata: {
1399
+ type: "object",
1400
+ properties: {
1401
+ content_type: { type: "string" },
1402
+ ttl_seconds: { type: "number" },
1403
+ tags: { type: "array", items: { type: "string" } }
1404
+ }
1405
+ },
1406
+ identity_id: { type: "string" }
1407
+ },
1408
+ required: ["namespace", "key", "value"]
1409
+ },
1410
+ handler: async (args) => {
1411
+ const reservedViolation = getReservedNamespaceViolation(args.namespace);
1412
+ if (reservedViolation) {
1413
+ return toolResult({
1414
+ error: "namespace_reserved",
1415
+ message: `Namespace "${args.namespace}" is reserved for internal use (prefix: ${reservedViolation}). Choose a different namespace.`
1416
+ });
1417
+ }
1418
+ const identity = resolveIdentity(args.identity_id);
1419
+ const metadata = args.metadata;
1420
+ const result = await stateStore.write(
1421
+ args.namespace,
1422
+ args.key,
1423
+ args.value,
1424
+ identity.identity_id,
1425
+ identity.encrypted_private_key,
1426
+ identityEncKey,
1427
+ {
1428
+ content_type: metadata?.content_type,
1429
+ ttl_seconds: metadata?.ttl_seconds,
1430
+ tags: metadata?.tags
1431
+ }
1432
+ );
1433
+ auditLog?.append("l1", "state_write", identity.identity_id, {
1434
+ namespace: args.namespace,
1435
+ key: args.key
1436
+ });
1437
+ return toolResult(result);
1438
+ }
1439
+ },
1440
+ {
1441
+ name: "sanctuary/state_read",
1442
+ description: "Read and decrypt state from the sovereign store. Verifies integrity via Merkle proof and signature.",
1443
+ inputSchema: {
1444
+ type: "object",
1445
+ properties: {
1446
+ namespace: { type: "string" },
1447
+ key: { type: "string" },
1448
+ verify_integrity: { type: "boolean", default: true }
1449
+ },
1450
+ required: ["namespace", "key"]
1451
+ },
1452
+ handler: async (args) => {
1453
+ const result = await stateStore.read(
1454
+ args.namespace,
1455
+ args.key,
1456
+ void 0,
1457
+ // Skip signature verification for now (would need writer's pubkey)
1458
+ args.verify_integrity ?? true
1459
+ );
1460
+ if (!result) {
1461
+ return toolResult({
1462
+ error: "not_found",
1463
+ namespace: args.namespace,
1464
+ key: args.key
1465
+ });
1466
+ }
1467
+ auditLog?.append("l1", "state_read", result.written_by, {
1468
+ namespace: args.namespace,
1469
+ key: args.key
1470
+ });
1471
+ return toolResult(result);
1472
+ }
1473
+ },
1474
+ {
1475
+ name: "sanctuary/state_list",
1476
+ description: "List keys in a namespace (metadata only \u2014 no decryption).",
1477
+ inputSchema: {
1478
+ type: "object",
1479
+ properties: {
1480
+ namespace: { type: "string" },
1481
+ prefix: { type: "string" },
1482
+ tags: { type: "array", items: { type: "string" } },
1483
+ limit: { type: "number", default: 100 },
1484
+ offset: { type: "number", default: 0 }
1485
+ },
1486
+ required: ["namespace"]
1487
+ },
1488
+ handler: async (args) => {
1489
+ const result = await stateStore.list(
1490
+ args.namespace,
1491
+ args.prefix,
1492
+ args.tags,
1493
+ args.limit ?? 100,
1494
+ args.offset ?? 0
1495
+ );
1496
+ return toolResult(result);
1497
+ }
1498
+ },
1499
+ {
1500
+ name: "sanctuary/state_delete",
1501
+ description: "Securely delete state. Overwrites file with random bytes before removal (right to deletion, S1.6).",
1502
+ inputSchema: {
1503
+ type: "object",
1504
+ properties: {
1505
+ namespace: { type: "string" },
1506
+ key: { type: "string" },
1507
+ reason: { type: "string" }
1508
+ },
1509
+ required: ["namespace", "key"]
1510
+ },
1511
+ handler: async (args) => {
1512
+ const reservedViolation = getReservedNamespaceViolation(args.namespace);
1513
+ if (reservedViolation) {
1514
+ return toolResult({
1515
+ error: "namespace_reserved",
1516
+ message: `Namespace "${args.namespace}" is reserved for internal use (prefix: ${reservedViolation}). Cannot delete from reserved namespaces.`
1517
+ });
1518
+ }
1519
+ const result = await stateStore.delete(
1520
+ args.namespace,
1521
+ args.key
1522
+ );
1523
+ auditLog?.append("l1", "state_delete", "principal", {
1524
+ namespace: args.namespace,
1525
+ key: args.key,
1526
+ reason: args.reason
1527
+ });
1528
+ return toolResult(result);
1529
+ }
1530
+ },
1531
+ {
1532
+ name: "sanctuary/state_export",
1533
+ description: "Export state as an encrypted, portable bundle for migration.",
1534
+ inputSchema: {
1535
+ type: "object",
1536
+ properties: {
1537
+ namespace: { type: "string" },
1538
+ format: { type: "string", default: "sanctuary-v1" }
1539
+ }
1540
+ },
1541
+ handler: async (args) => {
1542
+ const result = await stateStore.export(
1543
+ args.namespace
1544
+ );
1545
+ auditLog?.append("l1", "state_export", "principal", {
1546
+ namespaces: result.namespaces
1547
+ });
1548
+ return toolResult(result);
1549
+ }
1550
+ },
1551
+ {
1552
+ name: "sanctuary/state_import",
1553
+ description: "Import a previously exported state bundle.",
1554
+ inputSchema: {
1555
+ type: "object",
1556
+ properties: {
1557
+ bundle: { type: "string", description: "Base64url-encoded bundle" },
1558
+ conflict_resolution: {
1559
+ type: "string",
1560
+ enum: ["skip", "overwrite", "version"],
1561
+ default: "skip"
1562
+ }
1563
+ },
1564
+ required: ["bundle"]
1565
+ },
1566
+ handler: async (args) => {
1567
+ const result = await stateStore.import(
1568
+ args.bundle,
1569
+ args.conflict_resolution ?? "skip"
1570
+ );
1571
+ auditLog?.append("l1", "state_import", "principal", {
1572
+ imported_keys: result.imported_keys
1573
+ });
1574
+ return toolResult(result);
1575
+ }
1576
+ }
1577
+ ];
1578
+ return { tools, identityManager: identityMgr };
1579
+ }
1580
+
1581
+ // src/l2-operational/audit-log.ts
1582
+ init_encoding();
1583
+ var AuditLog = class {
1584
+ storage;
1585
+ encryptionKey;
1586
+ entries = [];
1587
+ counter = 0;
1588
+ constructor(storage, masterKey) {
1589
+ this.storage = storage;
1590
+ this.encryptionKey = derivePurposeKey(masterKey, "audit-log");
1591
+ }
1592
+ /**
1593
+ * Append an audit entry.
1594
+ */
1595
+ append(layer, operation, identityId, details, result = "success") {
1596
+ const entry = {
1597
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1598
+ layer,
1599
+ operation,
1600
+ identity_id: identityId,
1601
+ result,
1602
+ details
1603
+ };
1604
+ this.entries.push(entry);
1605
+ this.persistEntry(entry).catch(() => {
1606
+ });
1607
+ }
1608
+ async persistEntry(entry) {
1609
+ const key = `${Date.now()}-${this.counter++}`;
1610
+ const serialized = stringToBytes(JSON.stringify(entry));
1611
+ const encrypted = encrypt(serialized, this.encryptionKey);
1612
+ await this.storage.write(
1613
+ "_audit",
1614
+ key,
1615
+ stringToBytes(JSON.stringify(encrypted))
1616
+ );
1617
+ }
1618
+ /**
1619
+ * Query the audit log with filtering.
1620
+ */
1621
+ async query(options) {
1622
+ await this.loadPersistedEntries();
1623
+ let filtered = this.entries;
1624
+ if (options.since) {
1625
+ const sinceDate = new Date(options.since);
1626
+ filtered = filtered.filter(
1627
+ (e) => new Date(e.timestamp) >= sinceDate
1628
+ );
1629
+ }
1630
+ if (options.layer) {
1631
+ filtered = filtered.filter((e) => e.layer === options.layer);
1632
+ }
1633
+ if (options.operation_type) {
1634
+ filtered = filtered.filter(
1635
+ (e) => e.operation === options.operation_type
1636
+ );
1637
+ }
1638
+ const total = filtered.length;
1639
+ const limit = options.limit ?? 50;
1640
+ const entries = filtered.slice(-limit);
1641
+ return { entries, total };
1642
+ }
1643
+ async loadPersistedEntries() {
1644
+ try {
1645
+ const storedEntries = await this.storage.list("_audit");
1646
+ for (const meta of storedEntries) {
1647
+ const raw = await this.storage.read("_audit", meta.key);
1648
+ if (!raw) continue;
1649
+ try {
1650
+ const encrypted = JSON.parse(bytesToString(raw));
1651
+ const decrypted = decrypt(encrypted, this.encryptionKey);
1652
+ const entry = JSON.parse(bytesToString(decrypted));
1653
+ const isDuplicate = this.entries.some(
1654
+ (e) => e.timestamp === entry.timestamp && e.operation === entry.operation && e.identity_id === entry.identity_id
1655
+ );
1656
+ if (!isDuplicate) {
1657
+ this.entries.push(entry);
1658
+ }
1659
+ } catch {
1660
+ }
1661
+ }
1662
+ this.entries.sort(
1663
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
1664
+ );
1665
+ } catch {
1666
+ }
1667
+ }
1668
+ /**
1669
+ * Get total number of entries.
1670
+ */
1671
+ get size() {
1672
+ return this.entries.length;
1673
+ }
1674
+ };
1675
+
1676
+ // src/l3-disclosure/commitments.ts
1677
+ init_hashing();
1678
+ init_encoding();
1679
+ init_encoding();
1680
+ function createCommitment(value, blindingFactor) {
1681
+ const blindingBytes = blindingFactor ? fromBase64url(blindingFactor) : randomBytes(32);
1682
+ const valueBytes = stringToBytes(value);
1683
+ const combined = concatBytes(valueBytes, blindingBytes);
1684
+ const commitmentHash = hash(combined);
1685
+ return {
1686
+ commitment: toBase64url(commitmentHash),
1687
+ blinding_factor: toBase64url(blindingBytes),
1688
+ committed_at: (/* @__PURE__ */ new Date()).toISOString()
1689
+ };
1690
+ }
1691
+ function verifyCommitment(commitment, value, blindingFactor) {
1692
+ const blindingBytes = fromBase64url(blindingFactor);
1693
+ const valueBytes = stringToBytes(value);
1694
+ const combined = concatBytes(valueBytes, blindingBytes);
1695
+ const expectedHash = toBase64url(hash(combined));
1696
+ return commitment === expectedHash;
1697
+ }
1698
+ var CommitmentStore = class {
1699
+ storage;
1700
+ encryptionKey;
1701
+ constructor(storage, masterKey) {
1702
+ this.storage = storage;
1703
+ this.encryptionKey = derivePurposeKey(masterKey, "l3-commitments");
1704
+ }
1705
+ /**
1706
+ * Store a commitment (encrypted) for later reference.
1707
+ */
1708
+ async store(commitment, value) {
1709
+ const id = `cmt-${Date.now()}-${toBase64url(randomBytes(8))}`;
1710
+ const stored = {
1711
+ commitment: commitment.commitment,
1712
+ blinding_factor: commitment.blinding_factor,
1713
+ value,
1714
+ committed_at: commitment.committed_at,
1715
+ revealed: false
1716
+ };
1717
+ const serialized = stringToBytes(JSON.stringify(stored));
1718
+ const encrypted = encrypt(serialized, this.encryptionKey);
1719
+ await this.storage.write(
1720
+ "_commitments",
1721
+ id,
1722
+ stringToBytes(JSON.stringify(encrypted))
1723
+ );
1724
+ return id;
1725
+ }
1726
+ /**
1727
+ * Retrieve a stored commitment by ID.
1728
+ */
1729
+ async get(id) {
1730
+ const raw = await this.storage.read("_commitments", id);
1731
+ if (!raw) return null;
1732
+ try {
1733
+ const encrypted = JSON.parse(bytesToString(raw));
1734
+ const decrypted = decrypt(encrypted, this.encryptionKey);
1735
+ return JSON.parse(bytesToString(decrypted));
1736
+ } catch {
1737
+ return null;
1738
+ }
1739
+ }
1740
+ /**
1741
+ * Mark a commitment as revealed.
1742
+ */
1743
+ async markRevealed(id) {
1744
+ const stored = await this.get(id);
1745
+ if (!stored) return;
1746
+ stored.revealed = true;
1747
+ stored.revealed_at = (/* @__PURE__ */ new Date()).toISOString();
1748
+ const serialized = stringToBytes(JSON.stringify(stored));
1749
+ const encrypted = encrypt(serialized, this.encryptionKey);
1750
+ await this.storage.write(
1751
+ "_commitments",
1752
+ id,
1753
+ stringToBytes(JSON.stringify(encrypted))
1754
+ );
1755
+ }
1756
+ };
1757
+
1758
+ // src/l3-disclosure/policies.ts
1759
+ init_encoding();
1760
+ function evaluateDisclosure(policy, context, requestedFields) {
1761
+ return requestedFields.map((field) => {
1762
+ const exactRule = policy.rules.find((r) => r.context === context);
1763
+ const wildcardRule = policy.rules.find((r) => r.context === "*");
1764
+ const matchedRule = exactRule ?? wildcardRule;
1765
+ if (!matchedRule) {
1766
+ return {
1767
+ field,
1768
+ action: policy.default_action,
1769
+ reason: `No rule matches context "${context}"`,
1770
+ applicable_rule: "default"
1771
+ };
1772
+ }
1773
+ const ruleName = `${matchedRule.context}`;
1774
+ if (matchedRule.withhold.includes(field)) {
1775
+ return {
1776
+ field,
1777
+ action: "withhold",
1778
+ reason: `Field "${field}" is explicitly withheld in ${ruleName} context`,
1779
+ applicable_rule: ruleName
1780
+ };
1781
+ }
1782
+ if (matchedRule.proof_required.includes(field)) {
1783
+ return {
1784
+ field,
1785
+ action: "proof",
1786
+ reason: `Field "${field}" requires cryptographic proof in ${ruleName} context`,
1787
+ applicable_rule: ruleName
1788
+ };
1789
+ }
1790
+ if (matchedRule.disclose.includes(field)) {
1791
+ return {
1792
+ field,
1793
+ action: "disclose",
1794
+ reason: `Field "${field}" is permitted for disclosure in ${ruleName} context`,
1795
+ applicable_rule: ruleName
1796
+ };
1797
+ }
1798
+ return {
1799
+ field,
1800
+ action: policy.default_action,
1801
+ reason: `Field "${field}" not addressed in ${ruleName} rule; applying default`,
1802
+ applicable_rule: ruleName
1803
+ };
1804
+ });
1805
+ }
1806
+ var PolicyStore = class {
1807
+ storage;
1808
+ encryptionKey;
1809
+ policies = /* @__PURE__ */ new Map();
1810
+ constructor(storage, masterKey) {
1811
+ this.storage = storage;
1812
+ this.encryptionKey = derivePurposeKey(masterKey, "l3-policies");
1813
+ }
1814
+ /**
1815
+ * Create and store a new disclosure policy.
1816
+ */
1817
+ async create(policyName, rules, defaultAction, identityId) {
1818
+ const policyId = `pol-${Date.now()}-${toBase64url(randomBytes(8))}`;
1819
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1820
+ const policy = {
1821
+ policy_id: policyId,
1822
+ policy_name: policyName,
1823
+ rules,
1824
+ default_action: defaultAction,
1825
+ identity_id: identityId,
1826
+ created_at: now,
1827
+ updated_at: now
1828
+ };
1829
+ await this.persist(policy);
1830
+ this.policies.set(policyId, policy);
1831
+ return policy;
1832
+ }
1833
+ /**
1834
+ * Get a policy by ID.
1835
+ */
1836
+ async get(policyId) {
1837
+ if (this.policies.has(policyId)) {
1838
+ return this.policies.get(policyId);
1839
+ }
1840
+ const raw = await this.storage.read("_policies", policyId);
1841
+ if (!raw) return null;
1842
+ try {
1843
+ const encrypted = JSON.parse(bytesToString(raw));
1844
+ const decrypted = decrypt(encrypted, this.encryptionKey);
1845
+ const policy = JSON.parse(bytesToString(decrypted));
1846
+ this.policies.set(policyId, policy);
1847
+ return policy;
1848
+ } catch {
1849
+ return null;
1850
+ }
1851
+ }
1852
+ /**
1853
+ * List all policies.
1854
+ */
1855
+ async list() {
1856
+ await this.loadAll();
1857
+ return Array.from(this.policies.values());
1858
+ }
1859
+ /**
1860
+ * Load all persisted policies into memory.
1861
+ */
1862
+ async loadAll() {
1863
+ try {
1864
+ const entries = await this.storage.list("_policies");
1865
+ for (const meta of entries) {
1866
+ if (this.policies.has(meta.key)) continue;
1867
+ const raw = await this.storage.read("_policies", meta.key);
1868
+ if (!raw) continue;
1869
+ try {
1870
+ const encrypted = JSON.parse(bytesToString(raw));
1871
+ const decrypted = decrypt(encrypted, this.encryptionKey);
1872
+ const policy = JSON.parse(bytesToString(decrypted));
1873
+ this.policies.set(policy.policy_id, policy);
1874
+ } catch {
1875
+ }
1876
+ }
1877
+ } catch {
1878
+ }
1879
+ }
1880
+ async persist(policy) {
1881
+ const serialized = stringToBytes(JSON.stringify(policy));
1882
+ const encrypted = encrypt(serialized, this.encryptionKey);
1883
+ await this.storage.write(
1884
+ "_policies",
1885
+ policy.policy_id,
1886
+ stringToBytes(JSON.stringify(encrypted))
1887
+ );
1888
+ }
1889
+ };
1890
+
1891
+ // src/l3-disclosure/tools.ts
1892
+ function createL3Tools(storage, masterKey, auditLog) {
1893
+ const commitmentStore = new CommitmentStore(storage, masterKey);
1894
+ const policyStore = new PolicyStore(storage, masterKey);
1895
+ const tools = [
1896
+ // ─── Commitment Schemes ───────────────────────────────────────────────
1897
+ {
1898
+ name: "sanctuary/proof_commitment",
1899
+ description: "Create a cryptographic commitment to a value. The commitment hides the value until you choose to reveal it. Returns the commitment hash and a blinding factor (store securely).",
1900
+ inputSchema: {
1901
+ type: "object",
1902
+ properties: {
1903
+ value: {
1904
+ type: "string",
1905
+ description: "The value to commit to"
1906
+ },
1907
+ blinding_factor: {
1908
+ type: "string",
1909
+ description: "Optional base64url blinding factor (auto-generated if omitted)"
1910
+ }
1911
+ },
1912
+ required: ["value"]
1913
+ },
1914
+ handler: async (args) => {
1915
+ const value = args.value;
1916
+ const blindingFactor = args.blinding_factor;
1917
+ const commitment = createCommitment(value, blindingFactor);
1918
+ const commitmentId = await commitmentStore.store(commitment, value);
1919
+ auditLog.append("l3", "proof_commitment", "system", {
1920
+ commitment_id: commitmentId,
1921
+ commitment_hash: commitment.commitment
1922
+ });
1923
+ return toolResult({
1924
+ commitment_id: commitmentId,
1925
+ commitment: commitment.commitment,
1926
+ blinding_factor: commitment.blinding_factor,
1927
+ committed_at: commitment.committed_at,
1928
+ note: "Store the blinding_factor securely. You will need it to reveal the committed value."
1929
+ });
1930
+ }
1931
+ },
1932
+ {
1933
+ name: "sanctuary/proof_reveal",
1934
+ description: "Verify a previously committed value by revealing it with the blinding factor. Returns whether the revealed value matches the commitment.",
1935
+ inputSchema: {
1936
+ type: "object",
1937
+ properties: {
1938
+ commitment: {
1939
+ type: "string",
1940
+ description: "The original commitment hash"
1941
+ },
1942
+ value: {
1943
+ type: "string",
1944
+ description: "The value being revealed"
1945
+ },
1946
+ blinding_factor: {
1947
+ type: "string",
1948
+ description: "The blinding factor from the original commitment"
1949
+ }
1950
+ },
1951
+ required: ["commitment", "value", "blinding_factor"]
1952
+ },
1953
+ handler: async (args) => {
1954
+ const commitment = args.commitment;
1955
+ const value = args.value;
1956
+ const blindingFactor = args.blinding_factor;
1957
+ const valid = verifyCommitment(commitment, value, blindingFactor);
1958
+ auditLog.append("l3", "proof_reveal", "system", {
1959
+ commitment_hash: commitment,
1960
+ valid
1961
+ });
1962
+ return toolResult({
1963
+ valid,
1964
+ commitment,
1965
+ revealed_at: (/* @__PURE__ */ new Date()).toISOString()
1966
+ });
1967
+ }
1968
+ },
1969
+ // ─── Disclosure Policies ──────────────────────────────────────────────
1970
+ {
1971
+ name: "sanctuary/disclosure_set_policy",
1972
+ description: "Define a disclosure policy that controls what an agent will and will not disclose in different interaction contexts. Rules specify which fields may be disclosed, which must be withheld, and which require cryptographic proof.",
1973
+ inputSchema: {
1974
+ type: "object",
1975
+ properties: {
1976
+ policy_name: {
1977
+ type: "string",
1978
+ description: "Human-readable policy name"
1979
+ },
1980
+ rules: {
1981
+ type: "array",
1982
+ description: "Disclosure rules for different contexts",
1983
+ items: {
1984
+ type: "object",
1985
+ properties: {
1986
+ context: {
1987
+ type: "string",
1988
+ description: 'Interaction context: "negotiation", "commerce", "identity", "*" (wildcard)'
1989
+ },
1990
+ disclose: {
1991
+ type: "array",
1992
+ items: { type: "string" },
1993
+ description: "Fields the agent MAY disclose"
1994
+ },
1995
+ withhold: {
1996
+ type: "array",
1997
+ items: { type: "string" },
1998
+ description: "Fields the agent MUST NOT disclose"
1999
+ },
2000
+ proof_required: {
2001
+ type: "array",
2002
+ items: { type: "string" },
2003
+ description: "Fields that require proof rather than plain disclosure"
2004
+ }
2005
+ },
2006
+ required: ["context", "disclose", "withhold", "proof_required"]
2007
+ }
2008
+ },
2009
+ default_action: {
2010
+ type: "string",
2011
+ enum: ["withhold", "ask-principal"],
2012
+ description: "What to do when no rule matches a field"
2013
+ },
2014
+ identity_id: {
2015
+ type: "string",
2016
+ description: "Optional identity this policy is bound to"
2017
+ }
2018
+ },
2019
+ required: ["policy_name", "rules", "default_action"]
2020
+ },
2021
+ handler: async (args) => {
2022
+ const policyName = args.policy_name;
2023
+ const rules = args.rules;
2024
+ const defaultAction = args.default_action;
2025
+ const identityId = args.identity_id;
2026
+ const policy = await policyStore.create(
2027
+ policyName,
2028
+ rules,
2029
+ defaultAction,
2030
+ identityId
2031
+ );
2032
+ auditLog.append("l3", "disclosure_set_policy", identityId ?? "system", {
2033
+ policy_id: policy.policy_id,
2034
+ policy_name: policyName,
2035
+ rules_count: rules.length
2036
+ });
2037
+ return toolResult({
2038
+ policy_id: policy.policy_id,
2039
+ policy_name: policy.policy_name,
2040
+ rules_count: policy.rules.length,
2041
+ created_at: policy.created_at
2042
+ });
2043
+ }
2044
+ },
2045
+ {
2046
+ name: "sanctuary/disclosure_evaluate",
2047
+ description: "Evaluate a disclosure request against an active policy. Returns per-field decisions: disclose, withhold, proof, or ask-principal.",
2048
+ inputSchema: {
2049
+ type: "object",
2050
+ properties: {
2051
+ context: {
2052
+ type: "string",
2053
+ description: "The interaction context"
2054
+ },
2055
+ requested_fields: {
2056
+ type: "array",
2057
+ items: { type: "string" },
2058
+ description: "Fields the counterparty is requesting"
2059
+ },
2060
+ policy_id: {
2061
+ type: "string",
2062
+ description: "Specific policy to evaluate (uses first available if omitted)"
2063
+ }
2064
+ },
2065
+ required: ["context", "requested_fields"]
2066
+ },
2067
+ handler: async (args) => {
2068
+ const context = args.context;
2069
+ const requestedFields = args.requested_fields;
2070
+ const policyId = args.policy_id;
2071
+ let policy;
2072
+ if (policyId) {
2073
+ policy = await policyStore.get(policyId);
2074
+ } else {
2075
+ const allPolicies = await policyStore.list();
2076
+ policy = allPolicies[0] ?? null;
2077
+ }
2078
+ if (!policy) {
2079
+ return toolResult({
2080
+ error: "No disclosure policy found. Create one with disclosure_set_policy first."
2081
+ });
2082
+ }
2083
+ const decisions = evaluateDisclosure(policy, context, requestedFields);
2084
+ const withholding = decisions.filter(
2085
+ (d) => d.action === "withhold"
2086
+ ).length;
2087
+ const disclosing = decisions.filter(
2088
+ (d) => d.action === "disclose"
2089
+ ).length;
2090
+ const proofRequired = decisions.filter(
2091
+ (d) => d.action === "proof"
2092
+ ).length;
2093
+ const askPrincipal = decisions.filter(
2094
+ (d) => d.action === "ask-principal"
2095
+ ).length;
2096
+ auditLog.append("l3", "disclosure_evaluate", "system", {
2097
+ policy_id: policy.policy_id,
2098
+ context,
2099
+ fields_requested: requestedFields.length,
2100
+ withholding,
2101
+ disclosing,
2102
+ proof_required: proofRequired
2103
+ });
2104
+ return toolResult({
2105
+ policy_id: policy.policy_id,
2106
+ policy_name: policy.policy_name,
2107
+ context,
2108
+ decisions,
2109
+ summary: {
2110
+ total_fields: requestedFields.length,
2111
+ disclose: disclosing,
2112
+ withhold: withholding,
2113
+ proof: proofRequired,
2114
+ ask_principal: askPrincipal
2115
+ },
2116
+ overall_recommendation: withholding > 0 ? `Withholding ${withholding} of ${requestedFields.length} requested fields per policy "${policy.policy_name}"` : `All ${requestedFields.length} fields may be disclosed per policy "${policy.policy_name}"`
2117
+ });
2118
+ }
2119
+ }
2120
+ ];
2121
+ return { tools, commitmentStore, policyStore };
2122
+ }
2123
+
2124
+ // src/l4-reputation/reputation-store.ts
2125
+ init_encoding();
2126
+ function computeMedian(values) {
2127
+ if (values.length === 0) return 0;
2128
+ const sorted = [...values].sort((a, b) => a - b);
2129
+ const mid = Math.floor(sorted.length / 2);
2130
+ return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
2131
+ }
2132
+ function aggregateMetrics(attestations, metricNames) {
2133
+ const result = {};
2134
+ const names = metricNames ?? Array.from(
2135
+ new Set(
2136
+ attestations.flatMap(
2137
+ (a) => Object.keys(a.attestation.data.metrics)
2138
+ )
2139
+ )
2140
+ );
2141
+ for (const name of names) {
2142
+ const values = attestations.map((a) => a.attestation.data.metrics[name]).filter((v) => v !== void 0);
2143
+ if (values.length === 0) {
2144
+ result[name] = { mean: 0, median: 0, min: 0, max: 0, count: 0 };
2145
+ continue;
2146
+ }
2147
+ result[name] = {
2148
+ mean: values.reduce((s, v) => s + v, 0) / values.length,
2149
+ median: computeMedian(values),
2150
+ min: Math.min(...values),
2151
+ max: Math.max(...values),
2152
+ count: values.length
2153
+ };
2154
+ }
2155
+ return result;
2156
+ }
2157
+ var ReputationStore = class {
2158
+ storage;
2159
+ encryptionKey;
2160
+ constructor(storage, masterKey) {
2161
+ this.storage = storage;
2162
+ this.encryptionKey = derivePurposeKey(masterKey, "l4-reputation");
2163
+ }
2164
+ /**
2165
+ * Record an interaction outcome as a signed attestation.
2166
+ */
2167
+ async record(interactionId, counterpartyDid, outcome, context, identity, identityEncryptionKey, counterpartyAttestation) {
2168
+ const attestationId = `att-${Date.now()}-${toBase64url(randomBytes(8))}`;
2169
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2170
+ const attestationData = {
2171
+ interaction_id: interactionId,
2172
+ participant_did: identity.did,
2173
+ counterparty_did: counterpartyDid,
2174
+ outcome_type: outcome.type,
2175
+ outcome_result: outcome.result,
2176
+ metrics: outcome.metrics ?? {},
2177
+ context,
2178
+ timestamp: now
2179
+ };
2180
+ const dataBytes = stringToBytes(JSON.stringify(attestationData));
2181
+ const signature = sign(
2182
+ dataBytes,
2183
+ identity.encrypted_private_key,
2184
+ identityEncryptionKey
2185
+ );
2186
+ const attestation = {
2187
+ attestation_id: attestationId,
2188
+ schema: "sanctuary-interaction-v1",
2189
+ data: attestationData,
2190
+ signature: toBase64url(signature),
2191
+ signer: identity.did
2192
+ };
2193
+ const stored = {
2194
+ attestation,
2195
+ counterparty_attestation: counterpartyAttestation,
2196
+ counterparty_confirmed: !!counterpartyAttestation,
2197
+ recorded_at: now
2198
+ };
2199
+ const serialized = stringToBytes(JSON.stringify(stored));
2200
+ const encrypted = encrypt(serialized, this.encryptionKey);
2201
+ await this.storage.write(
2202
+ "_reputation",
2203
+ attestationId,
2204
+ stringToBytes(JSON.stringify(encrypted))
2205
+ );
2206
+ return stored;
2207
+ }
2208
+ /**
2209
+ * Query reputation data with filtering.
2210
+ * Returns aggregates only — not raw interaction data.
2211
+ */
2212
+ async query(options) {
2213
+ const all = await this.loadAll();
2214
+ let filtered = all;
2215
+ if (options.context) {
2216
+ filtered = filtered.filter(
2217
+ (a) => a.attestation.data.context === options.context
2218
+ );
2219
+ }
2220
+ if (options.time_range) {
2221
+ const start2 = new Date(options.time_range.start).getTime();
2222
+ const end2 = new Date(options.time_range.end).getTime();
2223
+ filtered = filtered.filter((a) => {
2224
+ const t = new Date(a.attestation.data.timestamp).getTime();
2225
+ return t >= start2 && t <= end2;
2226
+ });
2227
+ }
2228
+ if (options.counterparty_did) {
2229
+ filtered = filtered.filter(
2230
+ (a) => a.attestation.data.counterparty_did === options.counterparty_did
2231
+ );
2232
+ }
2233
+ const contexts = Array.from(
2234
+ new Set(filtered.map((a) => a.attestation.data.context))
2235
+ );
2236
+ const timestamps = filtered.map(
2237
+ (a) => new Date(a.attestation.data.timestamp).getTime()
2238
+ );
2239
+ const start = timestamps.length > 0 ? new Date(Math.min(...timestamps)).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
2240
+ const end = timestamps.length > 0 ? new Date(Math.max(...timestamps)).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
2241
+ return {
2242
+ total_interactions: filtered.length,
2243
+ completed: filtered.filter(
2244
+ (a) => a.attestation.data.outcome_result === "completed"
2245
+ ).length,
2246
+ partial: filtered.filter(
2247
+ (a) => a.attestation.data.outcome_result === "partial"
2248
+ ).length,
2249
+ failed: filtered.filter(
2250
+ (a) => a.attestation.data.outcome_result === "failed"
2251
+ ).length,
2252
+ disputed: filtered.filter(
2253
+ (a) => a.attestation.data.outcome_result === "disputed"
2254
+ ).length,
2255
+ contexts,
2256
+ time_range: { start, end },
2257
+ aggregate_metrics: aggregateMetrics(filtered, options.metrics)
2258
+ };
2259
+ }
2260
+ /**
2261
+ * Export attestations as a portable reputation bundle.
2262
+ */
2263
+ async exportBundle(identity, identityEncryptionKey, context) {
2264
+ let all = await this.loadAll();
2265
+ if (context) {
2266
+ all = all.filter((a) => a.attestation.data.context === context);
2267
+ }
2268
+ const attestations = all.map((a) => a.attestation);
2269
+ const bundleData = {
2270
+ version: "SANCTUARY_REP_V1",
2271
+ attestations,
2272
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
2273
+ exporter_did: identity.did
2274
+ };
2275
+ const bundleBytes = stringToBytes(JSON.stringify(bundleData));
2276
+ const bundleSignature = sign(
2277
+ bundleBytes,
2278
+ identity.encrypted_private_key,
2279
+ identityEncryptionKey
2280
+ );
2281
+ return {
2282
+ ...bundleData,
2283
+ bundle_signature: toBase64url(bundleSignature)
2284
+ };
2285
+ }
2286
+ /**
2287
+ * Import attestations from a reputation bundle.
2288
+ * Verifies signatures if requested (default: true).
2289
+ *
2290
+ * @param publicKeys - Map of DID → public key bytes for signature verification
2291
+ */
2292
+ async importBundle(bundle, verifySignatures, publicKeys) {
2293
+ let imported = 0;
2294
+ let invalid = 0;
2295
+ const contexts = /* @__PURE__ */ new Set();
2296
+ for (const attestation of bundle.attestations) {
2297
+ if (verifySignatures) {
2298
+ const signerKey = publicKeys.get(attestation.signer);
2299
+ if (!signerKey) {
2300
+ invalid++;
2301
+ continue;
2302
+ }
2303
+ const dataBytes = stringToBytes(
2304
+ JSON.stringify(attestation.data)
2305
+ );
2306
+ const sigBytes = fromBase64url(attestation.signature);
2307
+ if (!verify(dataBytes, sigBytes, signerKey)) {
2308
+ invalid++;
2309
+ continue;
2310
+ }
2311
+ }
2312
+ const stored = {
2313
+ attestation,
2314
+ counterparty_confirmed: false,
2315
+ recorded_at: (/* @__PURE__ */ new Date()).toISOString()
2316
+ };
2317
+ const serialized = stringToBytes(JSON.stringify(stored));
2318
+ const encrypted = encrypt(serialized, this.encryptionKey);
2319
+ await this.storage.write(
2320
+ "_reputation",
2321
+ attestation.attestation_id,
2322
+ stringToBytes(JSON.stringify(encrypted))
2323
+ );
2324
+ imported++;
2325
+ contexts.add(attestation.data.context);
2326
+ }
2327
+ return {
2328
+ imported,
2329
+ invalid,
2330
+ contexts: Array.from(contexts)
2331
+ };
2332
+ }
2333
+ // ─── Escrow ───────────────────────────────────────────────────────────
2334
+ /**
2335
+ * Create an escrow for trust bootstrapping.
2336
+ */
2337
+ async createEscrow(transactionTerms, counterpartyDid, timeoutSeconds, creatorDid, collateralAmount) {
2338
+ const escrowId = `esc-${Date.now()}-${toBase64url(randomBytes(8))}`;
2339
+ const now = /* @__PURE__ */ new Date();
2340
+ const expiresAt = new Date(now.getTime() + timeoutSeconds * 1e3);
2341
+ const { hashToString: hashToString2 } = await Promise.resolve().then(() => (init_hashing(), hashing_exports));
2342
+ const termsHash = hashToString2(stringToBytes(transactionTerms));
2343
+ const escrow = {
2344
+ escrow_id: escrowId,
2345
+ transaction_terms: transactionTerms,
2346
+ terms_hash: termsHash,
2347
+ collateral_amount: collateralAmount,
2348
+ counterparty_did: counterpartyDid,
2349
+ creator_did: creatorDid,
2350
+ created_at: now.toISOString(),
2351
+ expires_at: expiresAt.toISOString(),
2352
+ status: "pending"
2353
+ };
2354
+ const serialized = stringToBytes(JSON.stringify(escrow));
2355
+ const encrypted = encrypt(serialized, this.encryptionKey);
2356
+ await this.storage.write(
2357
+ "_escrows",
2358
+ escrowId,
2359
+ stringToBytes(JSON.stringify(encrypted))
2360
+ );
2361
+ return escrow;
2362
+ }
2363
+ /**
2364
+ * Get an escrow by ID.
2365
+ */
2366
+ async getEscrow(escrowId) {
2367
+ const raw = await this.storage.read("_escrows", escrowId);
2368
+ if (!raw) return null;
2369
+ try {
2370
+ const encrypted = JSON.parse(bytesToString(raw));
2371
+ const decrypted = decrypt(encrypted, this.encryptionKey);
2372
+ return JSON.parse(bytesToString(decrypted));
2373
+ } catch {
2374
+ return null;
2375
+ }
2376
+ }
2377
+ // ─── Guarantees ─────────────────────────────────────────────────────
2378
+ /**
2379
+ * Create a principal's guarantee for a new agent.
2380
+ */
2381
+ async createGuarantee(principalIdentity, agentDid, scope, durationSeconds, identityEncryptionKey, maxLiability) {
2382
+ const guaranteeId = `guar-${Date.now()}-${toBase64url(randomBytes(8))}`;
2383
+ const now = /* @__PURE__ */ new Date();
2384
+ const validUntil = new Date(now.getTime() + durationSeconds * 1e3);
2385
+ const certificateData = {
2386
+ guarantee_id: guaranteeId,
2387
+ principal_did: principalIdentity.did,
2388
+ agent_did: agentDid,
2389
+ scope,
2390
+ max_liability: maxLiability,
2391
+ valid_until: validUntil.toISOString(),
2392
+ issued_at: now.toISOString()
2393
+ };
2394
+ const certBytes = stringToBytes(JSON.stringify(certificateData));
2395
+ const signature = sign(
2396
+ certBytes,
2397
+ principalIdentity.encrypted_private_key,
2398
+ identityEncryptionKey
2399
+ );
2400
+ const certificate = toBase64url(
2401
+ stringToBytes(
2402
+ JSON.stringify({
2403
+ ...certificateData,
2404
+ signature: toBase64url(signature)
2405
+ })
2406
+ )
2407
+ );
2408
+ const guarantee = {
2409
+ guarantee_id: guaranteeId,
2410
+ principal_did: principalIdentity.did,
2411
+ agent_did: agentDid,
2412
+ scope,
2413
+ max_liability: maxLiability,
2414
+ valid_until: validUntil.toISOString(),
2415
+ certificate,
2416
+ created_at: now.toISOString()
2417
+ };
2418
+ const serialized = stringToBytes(JSON.stringify(guarantee));
2419
+ const encrypted = encrypt(serialized, this.encryptionKey);
2420
+ await this.storage.write(
2421
+ "_guarantees",
2422
+ guaranteeId,
2423
+ stringToBytes(JSON.stringify(encrypted))
2424
+ );
2425
+ return guarantee;
2426
+ }
2427
+ // ─── Internal ─────────────────────────────────────────────────────────
2428
+ async loadAll() {
2429
+ const results = [];
2430
+ try {
2431
+ const entries = await this.storage.list("_reputation");
2432
+ for (const meta of entries) {
2433
+ const raw = await this.storage.read("_reputation", meta.key);
2434
+ if (!raw) continue;
2435
+ try {
2436
+ const encrypted = JSON.parse(bytesToString(raw));
2437
+ const decrypted = decrypt(encrypted, this.encryptionKey);
2438
+ results.push(JSON.parse(bytesToString(decrypted)));
2439
+ } catch {
2440
+ }
2441
+ }
2442
+ } catch {
2443
+ }
2444
+ return results;
2445
+ }
2446
+ };
2447
+
2448
+ // src/l4-reputation/tools.ts
2449
+ init_encoding();
2450
+ function createL4Tools(storage, masterKey, identityManager, auditLog) {
2451
+ const reputationStore = new ReputationStore(storage, masterKey);
2452
+ const identityEncryptionKey = derivePurposeKey(masterKey, "identity-encryption");
2453
+ const tools = [
2454
+ // ─── Reputation Recording ─────────────────────────────────────────
2455
+ {
2456
+ name: "sanctuary/reputation_record",
2457
+ description: "Record an interaction outcome as a signed attestation. Creates an EAS-compatible attestation signed by the specified identity.",
2458
+ inputSchema: {
2459
+ type: "object",
2460
+ properties: {
2461
+ interaction_id: {
2462
+ type: "string",
2463
+ description: "Unique interaction identifier"
2464
+ },
2465
+ counterparty_did: {
2466
+ type: "string",
2467
+ description: "Counterparty's DID"
2468
+ },
2469
+ outcome: {
2470
+ type: "object",
2471
+ description: "Interaction outcome",
2472
+ properties: {
2473
+ type: {
2474
+ type: "string",
2475
+ enum: ["transaction", "negotiation", "service", "dispute", "custom"]
2476
+ },
2477
+ result: {
2478
+ type: "string",
2479
+ enum: ["completed", "partial", "failed", "disputed"]
2480
+ },
2481
+ metrics: {
2482
+ type: "object",
2483
+ description: "Domain-specific metrics (e.g., fulfillment_rate, response_time_ms)"
2484
+ }
2485
+ },
2486
+ required: ["type", "result"]
2487
+ },
2488
+ context: {
2489
+ type: "string",
2490
+ description: "Category/domain for context-specific reputation",
2491
+ default: "general"
2492
+ },
2493
+ counterparty_attestation: {
2494
+ type: "string",
2495
+ description: "Counterparty's signed attestation of the same interaction"
2496
+ },
2497
+ identity_id: {
2498
+ type: "string",
2499
+ description: "Identity to sign with (uses default if omitted)"
2500
+ }
2501
+ },
2502
+ required: ["interaction_id", "counterparty_did", "outcome"]
2503
+ },
2504
+ handler: async (args) => {
2505
+ const identityId = args.identity_id;
2506
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
2507
+ if (!identity) {
2508
+ return toolResult({
2509
+ error: "No identity found. Create one with identity_create first."
2510
+ });
2511
+ }
2512
+ const outcome = args.outcome;
2513
+ const context = args.context ?? "general";
2514
+ const stored = await reputationStore.record(
2515
+ args.interaction_id,
2516
+ args.counterparty_did,
2517
+ outcome,
2518
+ context,
2519
+ identity,
2520
+ identityEncryptionKey,
2521
+ args.counterparty_attestation
2522
+ );
2523
+ auditLog.append("l4", "reputation_record", identity.identity_id, {
2524
+ interaction_id: args.interaction_id,
2525
+ outcome_type: outcome.type,
2526
+ outcome_result: outcome.result,
2527
+ context
2528
+ });
2529
+ return toolResult({
2530
+ attestation_id: stored.attestation.attestation_id,
2531
+ interaction_id: stored.attestation.data.interaction_id,
2532
+ self_attestation: stored.attestation.signature,
2533
+ counterparty_confirmed: stored.counterparty_confirmed,
2534
+ context,
2535
+ recorded_at: stored.recorded_at
2536
+ });
2537
+ }
2538
+ },
2539
+ // ─── Reputation Query ─────────────────────────────────────────────
2540
+ {
2541
+ name: "sanctuary/reputation_query",
2542
+ description: "Query aggregated reputation data with filtering. Returns summary statistics, never raw interaction details.",
2543
+ inputSchema: {
2544
+ type: "object",
2545
+ properties: {
2546
+ context: {
2547
+ type: "string",
2548
+ description: "Filter by context/domain"
2549
+ },
2550
+ time_range: {
2551
+ type: "object",
2552
+ description: "Filter by time range",
2553
+ properties: {
2554
+ start: { type: "string", description: "ISO 8601 start" },
2555
+ end: { type: "string", description: "ISO 8601 end" }
2556
+ }
2557
+ },
2558
+ metrics: {
2559
+ type: "array",
2560
+ items: { type: "string" },
2561
+ description: "Which metrics to aggregate"
2562
+ },
2563
+ counterparty_did: {
2564
+ type: "string",
2565
+ description: "Filter by counterparty"
2566
+ }
2567
+ }
2568
+ },
2569
+ handler: async (args) => {
2570
+ const summary = await reputationStore.query({
2571
+ context: args.context,
2572
+ time_range: args.time_range,
2573
+ metrics: args.metrics,
2574
+ counterparty_did: args.counterparty_did
2575
+ });
2576
+ auditLog.append("l4", "reputation_query", "system", {
2577
+ total_interactions: summary.total_interactions,
2578
+ contexts: summary.contexts
2579
+ });
2580
+ return toolResult({
2581
+ summary
2582
+ });
2583
+ }
2584
+ },
2585
+ // ─── Reputation Export ─────────────────────────────────────────────
2586
+ {
2587
+ name: "sanctuary/reputation_export",
2588
+ description: "Export a portable reputation bundle (SANCTUARY_REP_V1). Includes all signed attestations for independent verification.",
2589
+ inputSchema: {
2590
+ type: "object",
2591
+ properties: {
2592
+ format: {
2593
+ type: "string",
2594
+ enum: ["SANCTUARY_REP_V1"],
2595
+ default: "SANCTUARY_REP_V1"
2596
+ },
2597
+ context: {
2598
+ type: "string",
2599
+ description: "Export specific context only"
2600
+ },
2601
+ identity_id: {
2602
+ type: "string",
2603
+ description: "Identity to sign the bundle with"
2604
+ }
2605
+ }
2606
+ },
2607
+ handler: async (args) => {
2608
+ const identityId = args.identity_id;
2609
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
2610
+ if (!identity) {
2611
+ return toolResult({
2612
+ error: "No identity found. Create one with identity_create first."
2613
+ });
2614
+ }
2615
+ const context = args.context;
2616
+ const bundle = await reputationStore.exportBundle(
2617
+ identity,
2618
+ identityEncryptionKey,
2619
+ context
2620
+ );
2621
+ const bundleJson = JSON.stringify(bundle);
2622
+ const bundleBase64 = toBase64url(
2623
+ new TextEncoder().encode(bundleJson)
2624
+ );
2625
+ auditLog.append("l4", "reputation_export", identity.identity_id, {
2626
+ attestation_count: bundle.attestations.length,
2627
+ contexts: Array.from(
2628
+ new Set(bundle.attestations.map((a) => a.data.context))
2629
+ )
2630
+ });
2631
+ const { hashToString: hashToString2 } = await Promise.resolve().then(() => (init_hashing(), hashing_exports));
2632
+ const { stringToBytes: stringToBytes2 } = await Promise.resolve().then(() => (init_encoding(), encoding_exports));
2633
+ return toolResult({
2634
+ bundle: bundleBase64,
2635
+ attestation_count: bundle.attestations.length,
2636
+ contexts: Array.from(
2637
+ new Set(bundle.attestations.map((a) => a.data.context))
2638
+ ),
2639
+ bundle_hash: hashToString2(stringToBytes2(bundleJson)),
2640
+ exported_at: bundle.exported_at
2641
+ });
2642
+ }
2643
+ },
2644
+ // ─── Reputation Import ────────────────────────────────────────────
2645
+ {
2646
+ name: "sanctuary/reputation_import",
2647
+ description: "Import a reputation bundle from another Sanctuary instance. Verifies all attestation signatures by default.",
2648
+ inputSchema: {
2649
+ type: "object",
2650
+ properties: {
2651
+ bundle: {
2652
+ type: "string",
2653
+ description: "Base64url-encoded reputation bundle"
2654
+ }
2655
+ },
2656
+ required: ["bundle"]
2657
+ },
2658
+ handler: async (args) => {
2659
+ const bundleBase64 = args.bundle;
2660
+ const verifySignatures = true;
2661
+ let bundle;
2662
+ try {
2663
+ const bundleBytes = fromBase64url(bundleBase64);
2664
+ const bundleJson = new TextDecoder().decode(bundleBytes);
2665
+ bundle = JSON.parse(bundleJson);
2666
+ } catch {
2667
+ return toolResult({
2668
+ error: "Invalid bundle format. Expected base64url-encoded JSON."
2669
+ });
2670
+ }
2671
+ const publicKeys = /* @__PURE__ */ new Map();
2672
+ for (const pub of identityManager.list()) {
2673
+ const identity = identityManager.get(pub.identity_id);
2674
+ if (identity) {
2675
+ publicKeys.set(identity.did, fromBase64url(identity.public_key));
2676
+ }
2677
+ }
2678
+ const result = await reputationStore.importBundle(
2679
+ bundle,
2680
+ verifySignatures,
2681
+ publicKeys
2682
+ );
2683
+ auditLog.append("l4", "reputation_import", "system", {
2684
+ imported: result.imported,
2685
+ invalid: result.invalid,
2686
+ contexts: result.contexts
2687
+ });
2688
+ return toolResult({
2689
+ imported_attestations: result.imported,
2690
+ invalid_attestations: result.invalid,
2691
+ contexts: result.contexts,
2692
+ imported_at: (/* @__PURE__ */ new Date()).toISOString()
2693
+ });
2694
+ }
2695
+ },
2696
+ // ─── Trust Bootstrap: Escrow ──────────────────────────────────────
2697
+ {
2698
+ name: "sanctuary/bootstrap_create_escrow",
2699
+ description: "Create an escrow record for trust bootstrapping. Allows new participants with no reputation to transact safely.",
2700
+ inputSchema: {
2701
+ type: "object",
2702
+ properties: {
2703
+ transaction_terms: {
2704
+ type: "string",
2705
+ description: "Description of the transaction"
2706
+ },
2707
+ collateral_amount: {
2708
+ type: "number",
2709
+ description: "Optional stake/collateral amount"
2710
+ },
2711
+ counterparty_did: {
2712
+ type: "string",
2713
+ description: "Counterparty's DID"
2714
+ },
2715
+ timeout_seconds: {
2716
+ type: "number",
2717
+ description: "Escrow timeout in seconds"
2718
+ },
2719
+ identity_id: {
2720
+ type: "string",
2721
+ description: "Identity creating the escrow"
2722
+ }
2723
+ },
2724
+ required: ["transaction_terms", "counterparty_did", "timeout_seconds"]
2725
+ },
2726
+ handler: async (args) => {
2727
+ const identityId = args.identity_id;
2728
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
2729
+ if (!identity) {
2730
+ return toolResult({
2731
+ error: "No identity found. Create one with identity_create first."
2732
+ });
2733
+ }
2734
+ const escrow = await reputationStore.createEscrow(
2735
+ args.transaction_terms,
2736
+ args.counterparty_did,
2737
+ args.timeout_seconds,
2738
+ identity.did,
2739
+ args.collateral_amount
2740
+ );
2741
+ auditLog.append("l4", "bootstrap_create_escrow", identity.identity_id, {
2742
+ escrow_id: escrow.escrow_id,
2743
+ counterparty_did: args.counterparty_did,
2744
+ timeout_seconds: args.timeout_seconds
2745
+ });
2746
+ return toolResult({
2747
+ escrow_id: escrow.escrow_id,
2748
+ terms_hash: escrow.terms_hash,
2749
+ created_at: escrow.created_at,
2750
+ expires_at: escrow.expires_at,
2751
+ status: escrow.status
2752
+ });
2753
+ }
2754
+ },
2755
+ // ─── Trust Bootstrap: Guarantee ───────────────────────────────────
2756
+ {
2757
+ name: "sanctuary/bootstrap_provide_guarantee",
2758
+ description: "A principal provides a signed reputation guarantee for a new agent. The guarantee certificate can be presented to counterparties.",
2759
+ inputSchema: {
2760
+ type: "object",
2761
+ properties: {
2762
+ principal_identity_id: {
2763
+ type: "string",
2764
+ description: "Identity of the guarantor (principal)"
2765
+ },
2766
+ agent_identity_id: {
2767
+ type: "string",
2768
+ description: "Identity of the agent being guaranteed"
2769
+ },
2770
+ scope: {
2771
+ type: "string",
2772
+ description: "What the guarantee covers"
2773
+ },
2774
+ duration_seconds: {
2775
+ type: "number",
2776
+ description: "How long the guarantee is valid"
2777
+ },
2778
+ max_liability: {
2779
+ type: "number",
2780
+ description: "Maximum liability amount"
2781
+ }
2782
+ },
2783
+ required: [
2784
+ "principal_identity_id",
2785
+ "agent_identity_id",
2786
+ "scope",
2787
+ "duration_seconds"
2788
+ ]
2789
+ },
2790
+ handler: async (args) => {
2791
+ const principalIdentity = identityManager.get(
2792
+ args.principal_identity_id
2793
+ );
2794
+ const agentIdentity = identityManager.get(
2795
+ args.agent_identity_id
2796
+ );
2797
+ if (!principalIdentity) {
2798
+ return toolResult({
2799
+ error: `Principal identity "${args.principal_identity_id}" not found.`
2800
+ });
2801
+ }
2802
+ if (!agentIdentity) {
2803
+ return toolResult({
2804
+ error: `Agent identity "${args.agent_identity_id}" not found.`
2805
+ });
2806
+ }
2807
+ const guarantee = await reputationStore.createGuarantee(
2808
+ principalIdentity,
2809
+ agentIdentity.did,
2810
+ args.scope,
2811
+ args.duration_seconds,
2812
+ identityEncryptionKey,
2813
+ args.max_liability
2814
+ );
2815
+ auditLog.append(
2816
+ "l4",
2817
+ "bootstrap_provide_guarantee",
2818
+ principalIdentity.identity_id,
2819
+ {
2820
+ guarantee_id: guarantee.guarantee_id,
2821
+ agent_did: agentIdentity.did,
2822
+ scope: args.scope
2823
+ }
2824
+ );
2825
+ return toolResult({
2826
+ guarantee_id: guarantee.guarantee_id,
2827
+ guarantee_certificate: guarantee.certificate,
2828
+ scope: guarantee.scope,
2829
+ valid_until: guarantee.valid_until
2830
+ });
2831
+ }
2832
+ }
2833
+ ];
2834
+ return { tools, reputationStore };
2835
+ }
2836
+ var DEFAULT_TIER2 = {
2837
+ new_namespace_access: "approve",
2838
+ new_counterparty: "approve",
2839
+ frequency_spike_multiplier: 5,
2840
+ max_signs_per_minute: 10,
2841
+ bulk_read_threshold: 20,
2842
+ first_session_policy: "approve"
2843
+ };
2844
+ var DEFAULT_CHANNEL = {
2845
+ type: "stderr",
2846
+ timeout_seconds: 300,
2847
+ auto_deny: true
2848
+ };
2849
+ var DEFAULT_POLICY = {
2850
+ version: 1,
2851
+ tier1_always_approve: [
2852
+ "state_export",
2853
+ "state_import",
2854
+ "identity_rotate",
2855
+ "reputation_import",
2856
+ "bootstrap_provide_guarantee"
2857
+ ],
2858
+ tier2_anomaly: DEFAULT_TIER2,
2859
+ tier3_always_allow: [
2860
+ "state_read",
2861
+ "state_write",
2862
+ "state_list",
2863
+ "state_delete",
2864
+ "identity_create",
2865
+ "identity_list",
2866
+ "identity_sign",
2867
+ "identity_verify",
2868
+ "proof_commitment",
2869
+ "proof_reveal",
2870
+ "disclosure_set_policy",
2871
+ "disclosure_evaluate",
2872
+ "reputation_record",
2873
+ "reputation_query",
2874
+ "reputation_export",
2875
+ "bootstrap_create_escrow",
2876
+ "exec_attest",
2877
+ "monitor_health",
2878
+ "monitor_audit_log",
2879
+ "manifest",
2880
+ "principal_policy_view",
2881
+ "principal_baseline_view"
2882
+ ],
2883
+ approval_channel: DEFAULT_CHANNEL
2884
+ };
2885
+ function extractOperationName(toolName) {
2886
+ return toolName.startsWith("sanctuary/") ? toolName.slice("sanctuary/".length) : toolName;
2887
+ }
2888
+ function parsePolicy(content) {
2889
+ const trimmed = content.trim();
2890
+ if (trimmed.startsWith("{")) {
2891
+ const parsed = JSON.parse(trimmed);
2892
+ return validatePolicy(parsed);
2893
+ }
2894
+ const policy = {};
2895
+ let currentKey = null;
2896
+ let currentList = null;
2897
+ let currentObject = null;
2898
+ for (const rawLine of trimmed.split("\n")) {
2899
+ const line = rawLine.split("#")[0];
2900
+ if (line.trim() === "") continue;
2901
+ const indent = line.length - line.trimStart().length;
2902
+ const stripped = line.trim();
2903
+ if (indent === 0 && stripped.includes(":")) {
2904
+ if (currentKey && currentList) {
2905
+ policy[currentKey] = currentList;
2906
+ } else if (currentKey && currentObject) {
2907
+ policy[currentKey] = currentObject;
2908
+ }
2909
+ const colonIdx = stripped.indexOf(":");
2910
+ const key = stripped.slice(0, colonIdx).trim();
2911
+ const value = stripped.slice(colonIdx + 1).trim();
2912
+ if (value === "" || value === "|") {
2913
+ currentKey = key;
2914
+ currentList = null;
2915
+ currentObject = null;
2916
+ } else {
2917
+ policy[key] = parseScalar(value);
2918
+ currentKey = null;
2919
+ currentList = null;
2920
+ currentObject = null;
2921
+ }
2922
+ } else if (indent > 0 && stripped.startsWith("- ")) {
2923
+ if (!currentList) currentList = [];
2924
+ currentList.push(stripped.slice(2).trim().split(/\s+/)[0]);
2925
+ } else if (indent > 0 && stripped.includes(":")) {
2926
+ if (!currentObject) currentObject = {};
2927
+ const colonIdx = stripped.indexOf(":");
2928
+ const key = stripped.slice(0, colonIdx).trim();
2929
+ const value = stripped.slice(colonIdx + 1).trim();
2930
+ currentObject[key] = parseScalar(value.split(/\s+/)[0]);
2931
+ }
2932
+ }
2933
+ if (currentKey && currentList) {
2934
+ policy[currentKey] = currentList;
2935
+ } else if (currentKey && currentObject) {
2936
+ policy[currentKey] = currentObject;
2937
+ }
2938
+ return validatePolicy(policy);
2939
+ }
2940
+ function parseScalar(value) {
2941
+ if (value === "true") return true;
2942
+ if (value === "false") return false;
2943
+ const num = Number(value);
2944
+ if (!isNaN(num) && value !== "") return num;
2945
+ return value.replace(/^["']|["']$/g, "");
2946
+ }
2947
+ function validatePolicy(raw) {
2948
+ return {
2949
+ version: raw.version ?? 1,
2950
+ tier1_always_approve: raw.tier1_always_approve ?? DEFAULT_POLICY.tier1_always_approve,
2951
+ tier2_anomaly: {
2952
+ ...DEFAULT_TIER2,
2953
+ ...raw.tier2_anomaly ?? {}
2954
+ },
2955
+ tier3_always_allow: raw.tier3_always_allow ?? DEFAULT_POLICY.tier3_always_allow,
2956
+ approval_channel: {
2957
+ ...DEFAULT_CHANNEL,
2958
+ ...raw.approval_channel ?? {}
2959
+ }
2960
+ };
2961
+ }
2962
+ function generateDefaultPolicyYaml() {
2963
+ return `# Sanctuary Principal Policy v1
2964
+ # This file controls what your agent can do without asking.
2965
+ # Edit this file directly. Your agent cannot modify it.
2966
+ # Changes take effect on server restart.
2967
+
2968
+ version: 1
2969
+
2970
+ # \u2500\u2500\u2500 Tier 1: Always Requires Approval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2971
+ # These operations ALWAYS require your explicit approval.
2972
+ # They are inherently high-risk regardless of context.
2973
+ tier1_always_approve:
2974
+ - state_export
2975
+ - state_import
2976
+ - identity_rotate
2977
+ - reputation_import
2978
+ - bootstrap_provide_guarantee
2979
+
2980
+ # \u2500\u2500\u2500 Tier 2: Behavioral Anomaly Detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2981
+ # Triggers approval when agent behavior deviates from its baseline.
2982
+ # Options for each setting: approve | log | allow
2983
+ tier2_anomaly:
2984
+ new_namespace_access: approve
2985
+ new_counterparty: approve
2986
+ frequency_spike_multiplier: 5
2987
+ max_signs_per_minute: 10
2988
+ bulk_read_threshold: 20
2989
+ first_session_policy: approve
2990
+
2991
+ # \u2500\u2500\u2500 Tier 3: Always Allowed (Audit Only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2992
+ # These operations never require approval but are always logged.
2993
+ tier3_always_allow:
2994
+ - state_read
2995
+ - state_write
2996
+ - state_list
2997
+ - state_delete
2998
+ - identity_create
2999
+ - identity_list
3000
+ - identity_sign
3001
+ - identity_verify
3002
+ - proof_commitment
3003
+ - proof_reveal
3004
+ - disclosure_set_policy
3005
+ - disclosure_evaluate
3006
+ - reputation_record
3007
+ - reputation_query
3008
+ - reputation_export
3009
+ - bootstrap_create_escrow
3010
+ - exec_attest
3011
+ - monitor_health
3012
+ - monitor_audit_log
3013
+ - manifest
3014
+ - principal_policy_view
3015
+ - principal_baseline_view
3016
+
3017
+ # \u2500\u2500\u2500 Approval Channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3018
+ # How Sanctuary reaches you when approval is needed.
3019
+ approval_channel:
3020
+ type: stderr
3021
+ timeout_seconds: 300
3022
+ auto_deny: true
3023
+ `;
3024
+ }
3025
+ async function loadPrincipalPolicy(storagePath) {
3026
+ const policyPath = path.join(storagePath, "principal-policy.yaml");
3027
+ try {
3028
+ const content = await promises.readFile(policyPath, "utf-8");
3029
+ const policy = parsePolicy(content);
3030
+ return Object.freeze(policy);
3031
+ } catch {
3032
+ const defaultYaml = generateDefaultPolicyYaml();
3033
+ try {
3034
+ await promises.writeFile(policyPath, defaultYaml, "utf-8");
3035
+ await promises.chmod(policyPath, 384);
3036
+ } catch {
3037
+ }
3038
+ return Object.freeze({ ...DEFAULT_POLICY });
3039
+ }
3040
+ }
3041
+
3042
+ // src/principal-policy/baseline.ts
3043
+ init_encoding();
3044
+ var BASELINE_NAMESPACE = "_principal";
3045
+ var BASELINE_KEY = "session-baseline";
3046
+ var BaselineTracker = class {
3047
+ storage;
3048
+ encryptionKey;
3049
+ profile;
3050
+ /** Sliding window: timestamps of tool calls per tool name (last 60s) */
3051
+ callWindows = /* @__PURE__ */ new Map();
3052
+ /** Sliding window: read counts per namespace (last 60s) */
3053
+ readWindows = /* @__PURE__ */ new Map();
3054
+ /** Sliding window: sign call timestamps (last 60s) */
3055
+ signWindow = [];
3056
+ constructor(storage, masterKey) {
3057
+ this.storage = storage;
3058
+ this.encryptionKey = derivePurposeKey(masterKey, "principal-baseline");
3059
+ this.profile = {
3060
+ known_namespaces: [],
3061
+ known_counterparties: [],
3062
+ tool_call_counts: {},
3063
+ is_first_session: true,
3064
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
3065
+ };
3066
+ }
3067
+ /**
3068
+ * Load the previous session's baseline from storage.
3069
+ * If none exists, this is a first session.
3070
+ */
3071
+ async load() {
3072
+ try {
3073
+ const raw = await this.storage.read(BASELINE_NAMESPACE, BASELINE_KEY);
3074
+ if (!raw) return;
3075
+ const encrypted = JSON.parse(bytesToString(raw));
3076
+ const decrypted = decrypt(encrypted, this.encryptionKey);
3077
+ const saved = JSON.parse(bytesToString(decrypted));
3078
+ this.profile.known_namespaces = saved.known_namespaces ?? [];
3079
+ this.profile.known_counterparties = saved.known_counterparties ?? [];
3080
+ this.profile.is_first_session = false;
3081
+ } catch {
3082
+ this.profile.is_first_session = true;
3083
+ }
3084
+ }
3085
+ /**
3086
+ * Save the current baseline to storage (encrypted).
3087
+ * Called at session end or periodically.
3088
+ */
3089
+ async save() {
3090
+ this.profile.saved_at = (/* @__PURE__ */ new Date()).toISOString();
3091
+ const serialized = stringToBytes(JSON.stringify(this.profile));
3092
+ const encrypted = encrypt(serialized, this.encryptionKey);
3093
+ await this.storage.write(
3094
+ BASELINE_NAMESPACE,
3095
+ BASELINE_KEY,
3096
+ stringToBytes(JSON.stringify(encrypted))
3097
+ );
3098
+ }
3099
+ /**
3100
+ * Record a tool call for baseline tracking.
3101
+ * Returns anomaly information if applicable.
3102
+ */
3103
+ recordToolCall(toolName) {
3104
+ const now = Date.now();
3105
+ this.profile.tool_call_counts[toolName] = (this.profile.tool_call_counts[toolName] ?? 0) + 1;
3106
+ if (!this.callWindows.has(toolName)) {
3107
+ this.callWindows.set(toolName, []);
3108
+ }
3109
+ const window = this.callWindows.get(toolName);
3110
+ window.push(now);
3111
+ const cutoff = now - 6e4;
3112
+ while (window.length > 0 && window[0] < cutoff) {
3113
+ window.shift();
3114
+ }
3115
+ }
3116
+ /**
3117
+ * Record a namespace access.
3118
+ * @returns true if this is a new namespace (not in baseline)
3119
+ */
3120
+ recordNamespaceAccess(namespace) {
3121
+ if (namespace.startsWith("_")) return false;
3122
+ const isNew = !this.profile.known_namespaces.includes(namespace);
3123
+ if (isNew) {
3124
+ this.profile.known_namespaces.push(namespace);
3125
+ }
3126
+ return isNew;
3127
+ }
3128
+ /**
3129
+ * Record a namespace read for bulk-read detection.
3130
+ * @returns the number of reads in the current 60-second window
3131
+ */
3132
+ recordNamespaceRead(namespace) {
3133
+ const now = Date.now();
3134
+ if (!this.readWindows.has(namespace)) {
3135
+ this.readWindows.set(namespace, []);
3136
+ }
3137
+ const window = this.readWindows.get(namespace);
3138
+ window.push(now);
3139
+ const cutoff = now - 6e4;
3140
+ while (window.length > 0 && window[0] < cutoff) {
3141
+ window.shift();
3142
+ }
3143
+ return window.length;
3144
+ }
3145
+ /**
3146
+ * Record a counterparty DID interaction.
3147
+ * @returns true if this is a new counterparty (not in baseline)
3148
+ */
3149
+ recordCounterparty(did) {
3150
+ const isNew = !this.profile.known_counterparties.includes(did);
3151
+ if (isNew) {
3152
+ this.profile.known_counterparties.push(did);
3153
+ }
3154
+ return isNew;
3155
+ }
3156
+ /**
3157
+ * Record a signing operation.
3158
+ * @returns the number of signs in the current 60-second window
3159
+ */
3160
+ recordSign() {
3161
+ const now = Date.now();
3162
+ this.signWindow.push(now);
3163
+ const cutoff = now - 6e4;
3164
+ while (this.signWindow.length > 0 && this.signWindow[0] < cutoff) {
3165
+ this.signWindow.shift();
3166
+ }
3167
+ return this.signWindow.length;
3168
+ }
3169
+ /**
3170
+ * Get the current call rate for a tool (calls per minute).
3171
+ */
3172
+ getCallRate(toolName) {
3173
+ return this.callWindows.get(toolName)?.length ?? 0;
3174
+ }
3175
+ /**
3176
+ * Get the average call rate across all tools in the baseline.
3177
+ */
3178
+ getAverageCallRate() {
3179
+ let total = 0;
3180
+ let count = 0;
3181
+ for (const window of this.callWindows.values()) {
3182
+ total += window.length;
3183
+ count++;
3184
+ }
3185
+ return count > 0 ? total / count : 0;
3186
+ }
3187
+ /** Whether this is the first session */
3188
+ get isFirstSession() {
3189
+ return this.profile.is_first_session;
3190
+ }
3191
+ /** Get a read-only view of the current profile */
3192
+ getProfile() {
3193
+ return { ...this.profile };
3194
+ }
3195
+ };
3196
+
3197
+ // src/principal-policy/approval-channel.ts
3198
+ var StderrApprovalChannel = class {
3199
+ config;
3200
+ constructor(config) {
3201
+ this.config = config;
3202
+ }
3203
+ async requestApproval(request) {
3204
+ const prompt = this.formatPrompt(request);
3205
+ process.stderr.write(prompt + "\n");
3206
+ await new Promise((resolve) => setTimeout(resolve, 100));
3207
+ if (this.config.auto_deny) {
3208
+ return {
3209
+ decision: "deny",
3210
+ decided_at: (/* @__PURE__ */ new Date()).toISOString(),
3211
+ decided_by: "timeout"
3212
+ };
3213
+ } else {
3214
+ return {
3215
+ decision: "approve",
3216
+ decided_at: (/* @__PURE__ */ new Date()).toISOString(),
3217
+ decided_by: "auto"
3218
+ };
3219
+ }
3220
+ }
3221
+ formatPrompt(request) {
3222
+ const tierLabel = request.tier === 1 ? "Tier 1 \u2014 always requires approval" : "Tier 2 \u2014 behavioral anomaly detected";
3223
+ const contextLines = Object.entries(request.context).map(([k, v]) => ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join("\n");
3224
+ return [
3225
+ "",
3226
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
3227
+ "\u2551 SANCTUARY: Approval Required \u2551",
3228
+ "\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
3229
+ `\u2551 Operation: ${request.operation.padEnd(50)}\u2551`,
3230
+ `\u2551 ${tierLabel.padEnd(62)}\u2551`,
3231
+ `\u2551 Reason: ${request.reason.slice(0, 50).padEnd(50)}\u2551`,
3232
+ "\u2551 \u2551",
3233
+ `\u2551 Details: \u2551`,
3234
+ ...contextLines.split("\n").map(
3235
+ (line) => `\u2551 ${line.padEnd(60)}\u2551`
3236
+ ),
3237
+ "\u2551 \u2551",
3238
+ this.config.auto_deny ? "\u2551 Auto-denying (configure approval_channel.auto_deny to change) \u2551" : "\u2551 Auto-approving (informational mode) \u2551",
3239
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
3240
+ ""
3241
+ ].join("\n");
3242
+ }
3243
+ };
3244
+
3245
+ // src/principal-policy/gate.ts
3246
+ var ApprovalGate = class {
3247
+ policy;
3248
+ baseline;
3249
+ channel;
3250
+ auditLog;
3251
+ constructor(policy, baseline, channel, auditLog) {
3252
+ this.policy = policy;
3253
+ this.baseline = baseline;
3254
+ this.channel = channel;
3255
+ this.auditLog = auditLog;
3256
+ }
3257
+ /**
3258
+ * Evaluate a tool call against the Principal Policy.
3259
+ *
3260
+ * @param toolName - Full MCP tool name (e.g., "sanctuary/state_export")
3261
+ * @param args - Tool call arguments (for context extraction)
3262
+ * @returns GateResult indicating whether the call is allowed
3263
+ */
3264
+ async evaluate(toolName, args) {
3265
+ const operation = extractOperationName(toolName);
3266
+ this.baseline.recordToolCall(operation);
3267
+ if (this.policy.tier1_always_approve.includes(operation)) {
3268
+ return this.requestApproval(operation, 1, `"${operation}" is a Tier 1 operation (always requires approval)`, {
3269
+ operation,
3270
+ args_summary: this.summarizeArgs(args)
3271
+ });
3272
+ }
3273
+ const anomaly = this.detectAnomaly(operation, args);
3274
+ if (anomaly) {
3275
+ return this.requestApproval(operation, 2, anomaly.reason, anomaly.context);
3276
+ }
3277
+ this.auditLog.append("l2", `gate_allow:${operation}`, "system", {
3278
+ tier: 3,
3279
+ operation
3280
+ });
3281
+ return {
3282
+ allowed: true,
3283
+ tier: 3,
3284
+ reason: "Operation allowed (Tier 3)",
3285
+ approval_required: false
3286
+ };
3287
+ }
3288
+ /**
3289
+ * Detect Tier 2 behavioral anomalies.
3290
+ */
3291
+ detectAnomaly(operation, args) {
3292
+ const config = this.policy.tier2_anomaly;
3293
+ if (this.baseline.isFirstSession && config.first_session_policy === "approve") {
3294
+ if (!this.policy.tier3_always_allow.includes(operation)) {
3295
+ return {
3296
+ reason: `First session: "${operation}" has no established baseline`,
3297
+ context: { operation, is_first_session: true }
3298
+ };
3299
+ }
3300
+ }
3301
+ if (config.new_namespace_access === "approve") {
3302
+ const namespace = args.namespace;
3303
+ if (namespace) {
3304
+ const isNew = this.baseline.recordNamespaceAccess(namespace);
3305
+ if (isNew) {
3306
+ return {
3307
+ reason: `First access to namespace "${namespace}" (not in session baseline)`,
3308
+ context: {
3309
+ operation,
3310
+ namespace,
3311
+ known_namespaces: this.baseline.getProfile().known_namespaces
3312
+ }
3313
+ };
3314
+ }
3315
+ }
3316
+ } else if (config.new_namespace_access === "log") {
3317
+ const namespace = args.namespace;
3318
+ if (namespace) {
3319
+ this.baseline.recordNamespaceAccess(namespace);
3320
+ }
3321
+ }
3322
+ if (config.new_counterparty === "approve") {
3323
+ const counterpartyDid = args.counterparty_did ?? args.agent_identity_id;
3324
+ if (counterpartyDid) {
3325
+ const isNew = this.baseline.recordCounterparty(counterpartyDid);
3326
+ if (isNew) {
3327
+ return {
3328
+ reason: `First interaction with counterparty "${counterpartyDid}"`,
3329
+ context: {
3330
+ operation,
3331
+ counterparty_did: counterpartyDid,
3332
+ known_counterparties: this.baseline.getProfile().known_counterparties
3333
+ }
3334
+ };
3335
+ }
3336
+ }
3337
+ } else if (config.new_counterparty === "log") {
3338
+ const counterpartyDid = args.counterparty_did;
3339
+ if (counterpartyDid) {
3340
+ this.baseline.recordCounterparty(counterpartyDid);
3341
+ }
3342
+ }
3343
+ if (operation === "identity_sign") {
3344
+ const signCount = this.baseline.recordSign();
3345
+ if (signCount > config.max_signs_per_minute) {
3346
+ return {
3347
+ reason: `Signing frequency (${signCount}/min) exceeds limit (${config.max_signs_per_minute}/min)`,
3348
+ context: {
3349
+ operation,
3350
+ signs_per_minute: signCount,
3351
+ limit: config.max_signs_per_minute
3352
+ }
3353
+ };
3354
+ }
3355
+ }
3356
+ if (operation === "state_read") {
3357
+ const namespace = args.namespace;
3358
+ if (namespace) {
3359
+ const readCount = this.baseline.recordNamespaceRead(namespace);
3360
+ if (readCount > config.bulk_read_threshold) {
3361
+ return {
3362
+ reason: `Bulk read detected: ${readCount} reads from "${namespace}" in 60 seconds (threshold: ${config.bulk_read_threshold})`,
3363
+ context: {
3364
+ operation,
3365
+ namespace,
3366
+ reads_in_window: readCount,
3367
+ threshold: config.bulk_read_threshold
3368
+ }
3369
+ };
3370
+ }
3371
+ }
3372
+ }
3373
+ const callRate = this.baseline.getCallRate(operation);
3374
+ const avgRate = this.baseline.getAverageCallRate();
3375
+ if (avgRate > 0 && callRate > avgRate * config.frequency_spike_multiplier) {
3376
+ return {
3377
+ reason: `Frequency spike: "${operation}" at ${callRate}/min (${config.frequency_spike_multiplier}\xD7 above average ${avgRate.toFixed(1)}/min)`,
3378
+ context: {
3379
+ operation,
3380
+ current_rate: callRate,
3381
+ average_rate: avgRate,
3382
+ multiplier: config.frequency_spike_multiplier
3383
+ }
3384
+ };
3385
+ }
3386
+ return null;
3387
+ }
3388
+ /**
3389
+ * Request approval from the human principal.
3390
+ */
3391
+ async requestApproval(operation, tier, reason, context) {
3392
+ const request = {
3393
+ operation,
3394
+ tier,
3395
+ reason,
3396
+ context,
3397
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3398
+ };
3399
+ const response = await this.channel.requestApproval(request);
3400
+ this.auditLog.append("l2", `gate_${response.decision}:${operation}`, "system", {
3401
+ tier,
3402
+ reason,
3403
+ decided_by: response.decided_by
3404
+ });
3405
+ return {
3406
+ allowed: response.decision === "approve",
3407
+ tier,
3408
+ reason: response.decision === "approve" ? `Approved by ${response.decided_by}` : reason,
3409
+ approval_required: true,
3410
+ approval_response: response
3411
+ };
3412
+ }
3413
+ /**
3414
+ * Summarize tool arguments for the approval prompt.
3415
+ * Strips potentially large values to keep the prompt readable.
3416
+ */
3417
+ summarizeArgs(args) {
3418
+ const summary = {};
3419
+ for (const [key, value] of Object.entries(args)) {
3420
+ if (typeof value === "string" && value.length > 100) {
3421
+ summary[key] = value.slice(0, 100) + "...";
3422
+ } else {
3423
+ summary[key] = value;
3424
+ }
3425
+ }
3426
+ return summary;
3427
+ }
3428
+ /** Get the baseline tracker for saving at session end */
3429
+ getBaseline() {
3430
+ return this.baseline;
3431
+ }
3432
+ };
3433
+
3434
+ // src/principal-policy/tools.ts
3435
+ function createPrincipalPolicyTools(policy, baseline, auditLog) {
3436
+ return [
3437
+ {
3438
+ name: "sanctuary/principal_policy_view",
3439
+ description: "View the current Principal Policy \u2014 the human-controlled rules governing what operations require approval. Read-only.",
3440
+ inputSchema: {
3441
+ type: "object",
3442
+ properties: {
3443
+ include_defaults: {
3444
+ type: "boolean",
3445
+ description: "Include tier3_always_allow list (can be long)",
3446
+ default: false
3447
+ }
3448
+ }
3449
+ },
3450
+ handler: async (args) => {
3451
+ const includeDefaults = args.include_defaults ?? false;
3452
+ const view = {
3453
+ version: policy.version,
3454
+ tier1_always_approve: policy.tier1_always_approve,
3455
+ tier2_anomaly: policy.tier2_anomaly,
3456
+ approval_channel: {
3457
+ type: policy.approval_channel.type,
3458
+ timeout_seconds: policy.approval_channel.timeout_seconds,
3459
+ auto_deny: policy.approval_channel.auto_deny
3460
+ }
3461
+ };
3462
+ if (includeDefaults) {
3463
+ view.tier3_always_allow = policy.tier3_always_allow;
3464
+ } else {
3465
+ view.tier3_always_allow_count = policy.tier3_always_allow.length;
3466
+ view.note = "Pass include_defaults: true to see the full tier3_always_allow list";
3467
+ }
3468
+ auditLog.append("l2", "principal_policy_view", "system", {
3469
+ include_defaults: includeDefaults
3470
+ });
3471
+ return toolResult(view);
3472
+ }
3473
+ },
3474
+ {
3475
+ name: "sanctuary/principal_baseline_view",
3476
+ description: "View the current behavioral baseline \u2014 the session profile used for anomaly detection. Shows known namespaces, counterparties, and tool call counts. Read-only.",
3477
+ inputSchema: {
3478
+ type: "object",
3479
+ properties: {}
3480
+ },
3481
+ handler: async () => {
3482
+ const profile = baseline.getProfile();
3483
+ auditLog.append("l2", "principal_baseline_view", "system");
3484
+ return toolResult({
3485
+ is_first_session: profile.is_first_session,
3486
+ session_started_at: profile.started_at,
3487
+ known_namespaces: profile.known_namespaces,
3488
+ known_counterparties: profile.known_counterparties,
3489
+ tool_call_counts: profile.tool_call_counts,
3490
+ last_saved: profile.saved_at ?? "not yet saved"
3491
+ });
3492
+ }
3493
+ }
3494
+ ];
3495
+ }
3496
+
3497
+ // src/shr/types.ts
3498
+ function deepSortKeys(obj) {
3499
+ if (obj === null || typeof obj !== "object") return obj;
3500
+ if (Array.isArray(obj)) return obj.map(deepSortKeys);
3501
+ const sorted = {};
3502
+ for (const key of Object.keys(obj).sort()) {
3503
+ sorted[key] = deepSortKeys(obj[key]);
3504
+ }
3505
+ return sorted;
3506
+ }
3507
+ function canonicalizeForSigning(body) {
3508
+ return JSON.stringify(deepSortKeys(body));
3509
+ }
3510
+
3511
+ // src/shr/generator.ts
3512
+ init_encoding();
3513
+ var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
3514
+ function generateSHR(identityId, opts) {
3515
+ const { config, identityManager, masterKey, validityMs } = opts;
3516
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
3517
+ if (!identity) {
3518
+ return "No identity available for signing. Create an identity first.";
3519
+ }
3520
+ const now = /* @__PURE__ */ new Date();
3521
+ const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
3522
+ const degradations = [];
3523
+ if (config.execution.environment === "local-process") {
3524
+ degradations.push({
3525
+ layer: "l2",
3526
+ code: "PROCESS_ISOLATION_ONLY",
3527
+ severity: "warning",
3528
+ description: "Process-level isolation only (no TEE)",
3529
+ mitigation: "TEE support planned for v0.3.0"
3530
+ });
3531
+ degradations.push({
3532
+ layer: "l2",
3533
+ code: "SELF_REPORTED_ATTESTATION",
3534
+ severity: "warning",
3535
+ description: "Attestation is self-reported (no hardware root of trust)",
3536
+ mitigation: "TEE attestation planned for v0.3.0"
3537
+ });
3538
+ }
3539
+ if (config.disclosure.proof_system === "commitment-only") {
3540
+ degradations.push({
3541
+ layer: "l3",
3542
+ code: "COMMITMENT_ONLY",
3543
+ severity: "info",
3544
+ description: "Commitment schemes only (no ZK proofs)",
3545
+ mitigation: "ZK proof support planned for future release"
3546
+ });
3547
+ }
3548
+ const body = {
3549
+ shr_version: "1.0",
3550
+ instance_id: identity.identity_id,
3551
+ generated_at: now.toISOString(),
3552
+ expires_at: expiresAt.toISOString(),
3553
+ layers: {
3554
+ l1: {
3555
+ status: "active",
3556
+ encryption: config.state.encryption,
3557
+ key_custody: "self",
3558
+ integrity: config.state.integrity,
3559
+ identity_type: config.state.identity_provider,
3560
+ state_portable: true
3561
+ },
3562
+ l2: {
3563
+ status: config.execution.environment === "local-process" ? "degraded" : "active",
3564
+ isolation_type: config.execution.environment,
3565
+ attestation_available: config.execution.attestation
3566
+ },
3567
+ l3: {
3568
+ status: config.disclosure.proof_system === "commitment-only" ? "degraded" : "active",
3569
+ proof_system: config.disclosure.proof_system,
3570
+ selective_disclosure: config.disclosure.proof_system !== "commitment-only"
3571
+ },
3572
+ l4: {
3573
+ status: "active",
3574
+ reputation_mode: config.reputation.mode,
3575
+ attestation_format: config.reputation.attestation_format,
3576
+ reputation_portable: true
3577
+ }
3578
+ },
3579
+ capabilities: {
3580
+ handshake: true,
3581
+ shr_exchange: true,
3582
+ reputation_verify: true,
3583
+ encrypted_channel: false
3584
+ // Not yet implemented
3585
+ },
3586
+ degradations
3587
+ };
3588
+ const canonical = canonicalizeForSigning(body);
3589
+ const payload = stringToBytes(canonical);
3590
+ const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
3591
+ const signatureBytes = sign(
3592
+ payload,
3593
+ identity.encrypted_private_key,
3594
+ encryptionKey
3595
+ );
3596
+ return {
3597
+ body,
3598
+ signed_by: identity.public_key,
3599
+ signature: toBase64url(signatureBytes)
3600
+ };
3601
+ }
3602
+
3603
+ // src/shr/verifier.ts
3604
+ init_encoding();
3605
+ function verifySHR(shr, now) {
3606
+ const errors = [];
3607
+ const warnings = [];
3608
+ const currentTime = /* @__PURE__ */ new Date();
3609
+ if (!shr.body || !shr.signed_by || !shr.signature) {
3610
+ errors.push("Missing required SHR fields (body, signed_by, or signature)");
3611
+ return {
3612
+ valid: false,
3613
+ errors,
3614
+ warnings,
3615
+ sovereignty_level: "minimal",
3616
+ counterparty_id: shr.body?.instance_id ?? "unknown",
3617
+ expires_at: shr.body?.expires_at ?? "unknown"
3618
+ };
3619
+ }
3620
+ if (shr.body.shr_version !== "1.0") {
3621
+ errors.push(`Unsupported SHR version: ${shr.body.shr_version}`);
3622
+ }
3623
+ const expiresAt = new Date(shr.body.expires_at);
3624
+ if (isNaN(expiresAt.getTime())) {
3625
+ errors.push("Invalid expires_at timestamp");
3626
+ } else if (currentTime > expiresAt) {
3627
+ errors.push(`SHR expired at ${shr.body.expires_at}`);
3628
+ }
3629
+ const generatedAt = new Date(shr.body.generated_at);
3630
+ if (isNaN(generatedAt.getTime())) {
3631
+ errors.push("Invalid generated_at timestamp");
3632
+ } else if (generatedAt > currentTime) {
3633
+ warnings.push("SHR generated_at is in the future \u2014 clock skew detected");
3634
+ }
3635
+ try {
3636
+ const publicKey = fromBase64url(shr.signed_by);
3637
+ const signatureBytes = fromBase64url(shr.signature);
3638
+ const canonical = canonicalizeForSigning(shr.body);
3639
+ const payload = stringToBytes(canonical);
3640
+ const signatureValid = verify(payload, signatureBytes, publicKey);
3641
+ if (!signatureValid) {
3642
+ errors.push("Invalid signature \u2014 SHR may have been tampered with");
3643
+ }
3644
+ } catch (e) {
3645
+ errors.push(`Signature verification failed: ${e.message}`);
3646
+ }
3647
+ const { layers } = shr.body;
3648
+ if (!layers.l1 || !layers.l2 || !layers.l3 || !layers.l4) {
3649
+ errors.push("Missing one or more layer definitions");
3650
+ }
3651
+ const sovereigntyLevel = assessSovereigntyLevel(shr.body);
3652
+ for (const d of shr.body.degradations ?? []) {
3653
+ if (d.severity === "critical") {
3654
+ warnings.push(`Critical degradation in ${d.layer}: ${d.description}`);
3655
+ }
3656
+ }
3657
+ return {
3658
+ valid: errors.length === 0,
3659
+ errors,
3660
+ warnings,
3661
+ sovereignty_level: sovereigntyLevel,
3662
+ counterparty_id: shr.body.instance_id,
3663
+ expires_at: shr.body.expires_at
3664
+ };
3665
+ }
3666
+ function assessSovereigntyLevel(body) {
3667
+ const { l1, l2, l3, l4 } = body.layers;
3668
+ if (l1.status === "active" && l2.status === "active" && l3.status === "active" && l4.status === "active") {
3669
+ return "full";
3670
+ }
3671
+ if (l1.status !== "active") {
3672
+ return "minimal";
3673
+ }
3674
+ if (l4.status === "active" || l4.status === "degraded") {
3675
+ return "degraded";
3676
+ }
3677
+ return "minimal";
3678
+ }
3679
+
3680
+ // src/shr/tools.ts
3681
+ function createSHRTools(config, identityManager, masterKey, auditLog) {
3682
+ const generatorOpts = {
3683
+ config,
3684
+ identityManager,
3685
+ masterKey
3686
+ };
3687
+ const tools = [
3688
+ {
3689
+ name: "sanctuary/shr_generate",
3690
+ description: "Generate a signed Sovereignty Health Report (SHR) \u2014 a machine-readable, cryptographically signed advertisement of this instance's sovereignty posture. Present this to counterparties to prove your sovereignty capabilities.",
3691
+ inputSchema: {
3692
+ type: "object",
3693
+ properties: {
3694
+ identity_id: {
3695
+ type: "string",
3696
+ description: "Identity to sign the SHR with. Defaults to primary identity."
3697
+ },
3698
+ validity_minutes: {
3699
+ type: "number",
3700
+ description: "How long the SHR is valid (minutes). Default: 60."
3701
+ }
3702
+ }
3703
+ },
3704
+ handler: async (args) => {
3705
+ const validityMs = args.validity_minutes ? args.validity_minutes * 60 * 1e3 : void 0;
3706
+ const result = generateSHR(args.identity_id, {
3707
+ ...generatorOpts,
3708
+ validityMs
3709
+ });
3710
+ if (typeof result === "string") {
3711
+ return toolResult({ error: result });
3712
+ }
3713
+ auditLog.append("l2", "shr_generate", result.body.instance_id);
3714
+ return toolResult(result);
3715
+ }
3716
+ },
3717
+ {
3718
+ name: "sanctuary/shr_verify",
3719
+ description: "Verify a counterparty's Sovereignty Health Report (SHR). Checks signature validity, temporal validity, and assesses sovereignty level.",
3720
+ inputSchema: {
3721
+ type: "object",
3722
+ properties: {
3723
+ shr: {
3724
+ type: "object",
3725
+ description: "The signed SHR to verify (full SignedSHR object)."
3726
+ }
3727
+ },
3728
+ required: ["shr"]
3729
+ },
3730
+ handler: async (args) => {
3731
+ const shr = args.shr;
3732
+ const result = verifySHR(shr);
3733
+ auditLog.append(
3734
+ "l2",
3735
+ "shr_verify",
3736
+ result.counterparty_id,
3737
+ void 0,
3738
+ result.valid ? "success" : "failure"
3739
+ );
3740
+ return toolResult(result);
3741
+ }
3742
+ }
3743
+ ];
3744
+ return { tools };
3745
+ }
3746
+
3747
+ // src/handshake/protocol.ts
3748
+ init_encoding();
3749
+ function generateNonce() {
3750
+ return toBase64url(randomBytes(32));
3751
+ }
3752
+ function initiateHandshake(ourSHR) {
3753
+ const nonce = generateNonce();
3754
+ const sessionId = toBase64url(randomBytes(16));
3755
+ const challenge = {
3756
+ protocol_version: "1.0",
3757
+ shr: ourSHR,
3758
+ nonce,
3759
+ initiated_at: (/* @__PURE__ */ new Date()).toISOString()
3760
+ };
3761
+ const session = {
3762
+ session_id: sessionId,
3763
+ role: "initiator",
3764
+ state: "initiated",
3765
+ our_nonce: nonce,
3766
+ our_shr: ourSHR,
3767
+ initiated_at: challenge.initiated_at
3768
+ };
3769
+ return { challenge, session };
3770
+ }
3771
+ function respondToHandshake(challenge, ourSHR, identityManager, masterKey, identityId) {
3772
+ if (challenge.protocol_version !== "1.0") {
3773
+ return { error: `Unsupported protocol version: ${challenge.protocol_version}` };
3774
+ }
3775
+ const shrResult = verifySHR(challenge.shr);
3776
+ if (!shrResult.valid) {
3777
+ return { error: `Initiator SHR verification failed: ${shrResult.errors.join(", ")}` };
3778
+ }
3779
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
3780
+ if (!identity) {
3781
+ return { error: "No identity available for signing" };
3782
+ }
3783
+ const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
3784
+ const nonceBytes = stringToBytes(challenge.nonce);
3785
+ const nonceSignature = sign(
3786
+ nonceBytes,
3787
+ identity.encrypted_private_key,
3788
+ encryptionKey
3789
+ );
3790
+ const responderNonce = generateNonce();
3791
+ const response = {
3792
+ protocol_version: "1.0",
3793
+ shr: ourSHR,
3794
+ responder_nonce: responderNonce,
3795
+ initiator_nonce_signature: toBase64url(nonceSignature),
3796
+ responded_at: (/* @__PURE__ */ new Date()).toISOString()
3797
+ };
3798
+ const session = {
3799
+ session_id: toBase64url(randomBytes(16)),
3800
+ role: "responder",
3801
+ state: "responded",
3802
+ our_nonce: responderNonce,
3803
+ their_nonce: challenge.nonce,
3804
+ our_shr: ourSHR,
3805
+ their_shr: challenge.shr,
3806
+ initiated_at: challenge.initiated_at
3807
+ };
3808
+ return { response, session };
3809
+ }
3810
+ function completeHandshake(response, session, identityManager, masterKey, identityId) {
3811
+ if (response.protocol_version !== "1.0") {
3812
+ return { error: `Unsupported protocol version: ${response.protocol_version}` };
3813
+ }
3814
+ const shrResult = verifySHR(response.shr);
3815
+ if (!shrResult.valid) {
3816
+ return { error: `Responder SHR verification failed: ${shrResult.errors.join(", ")}` };
3817
+ }
3818
+ const responderPublicKey = fromBase64url(response.shr.signed_by);
3819
+ const ourNonceBytes = stringToBytes(session.our_nonce);
3820
+ const nonceSignatureBytes = fromBase64url(response.initiator_nonce_signature);
3821
+ const nonceSignatureValid = verify(
3822
+ ourNonceBytes,
3823
+ nonceSignatureBytes,
3824
+ responderPublicKey
3825
+ );
3826
+ if (!nonceSignatureValid) {
3827
+ return { error: "Responder's nonce signature is invalid \u2014 possible replay or MITM" };
3828
+ }
3829
+ const identity = identityManager.getDefault();
3830
+ if (!identity) {
3831
+ return { error: "No identity available for signing" };
3832
+ }
3833
+ const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
3834
+ const responderNonceBytes = stringToBytes(response.responder_nonce);
3835
+ const responderNonceSignature = sign(
3836
+ responderNonceBytes,
3837
+ identity.encrypted_private_key,
3838
+ encryptionKey
3839
+ );
3840
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3841
+ const completion = {
3842
+ protocol_version: "1.0",
3843
+ responder_nonce_signature: toBase64url(responderNonceSignature),
3844
+ completed_at: now
3845
+ };
3846
+ const sovereigntyLevel = shrResult.sovereignty_level;
3847
+ const trustTier = deriveTrustTier(sovereigntyLevel);
3848
+ const result = {
3849
+ counterparty_id: shrResult.counterparty_id,
3850
+ counterparty_shr: response.shr,
3851
+ verified: true,
3852
+ sovereignty_level: sovereigntyLevel,
3853
+ trust_tier: trustTier,
3854
+ completed_at: now,
3855
+ expires_at: shrResult.expires_at,
3856
+ errors: []
3857
+ };
3858
+ return { completion, result };
3859
+ }
3860
+ function verifyCompletion(completion, session) {
3861
+ const errors = [];
3862
+ if (!session.their_shr) {
3863
+ return {
3864
+ counterparty_id: "unknown",
3865
+ counterparty_shr: session.our_shr,
3866
+ // placeholder
3867
+ verified: false,
3868
+ sovereignty_level: "unverified",
3869
+ trust_tier: "unverified",
3870
+ completed_at: completion.completed_at,
3871
+ expires_at: (/* @__PURE__ */ new Date()).toISOString(),
3872
+ errors: ["No initiator SHR in session state"]
3873
+ };
3874
+ }
3875
+ const initiatorPublicKey = fromBase64url(session.their_shr.signed_by);
3876
+ const ourNonceBytes = stringToBytes(session.our_nonce);
3877
+ const nonceSignatureBytes = fromBase64url(completion.responder_nonce_signature);
3878
+ const nonceSignatureValid = verify(
3879
+ ourNonceBytes,
3880
+ nonceSignatureBytes,
3881
+ initiatorPublicKey
3882
+ );
3883
+ if (!nonceSignatureValid) {
3884
+ errors.push("Initiator's nonce signature is invalid \u2014 possible replay or MITM");
3885
+ }
3886
+ const shrResult = verifySHR(session.their_shr);
3887
+ if (!shrResult.valid) {
3888
+ errors.push(...shrResult.errors);
3889
+ }
3890
+ const verified = errors.length === 0;
3891
+ const sovereigntyLevel = verified ? shrResult.sovereignty_level : "unverified";
3892
+ return {
3893
+ counterparty_id: session.their_shr.body.instance_id,
3894
+ counterparty_shr: session.their_shr,
3895
+ verified,
3896
+ sovereignty_level: sovereigntyLevel,
3897
+ trust_tier: deriveTrustTier(sovereigntyLevel),
3898
+ completed_at: completion.completed_at,
3899
+ expires_at: session.their_shr.body.expires_at,
3900
+ errors
3901
+ };
3902
+ }
3903
+ function deriveTrustTier(level) {
3904
+ switch (level) {
3905
+ case "full":
3906
+ return "verified-sovereign";
3907
+ case "degraded":
3908
+ return "verified-degraded";
3909
+ default:
3910
+ return "unverified";
3911
+ }
3912
+ }
3913
+
3914
+ // src/handshake/tools.ts
3915
+ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
3916
+ const sessions = /* @__PURE__ */ new Map();
3917
+ const shrOpts = {
3918
+ config,
3919
+ identityManager,
3920
+ masterKey
3921
+ };
3922
+ const tools = [
3923
+ {
3924
+ name: "sanctuary/handshake_initiate",
3925
+ description: "Initiate a sovereignty handshake with a counterparty. Generates a challenge containing this instance's signed SHR and a cryptographic nonce. Send the returned challenge to the counterparty.",
3926
+ inputSchema: {
3927
+ type: "object",
3928
+ properties: {
3929
+ identity_id: {
3930
+ type: "string",
3931
+ description: "Identity to use for the handshake. Defaults to primary identity."
3932
+ }
3933
+ }
3934
+ },
3935
+ handler: async (args) => {
3936
+ const shr = generateSHR(args.identity_id, shrOpts);
3937
+ if (typeof shr === "string") {
3938
+ return toolResult({ error: shr });
3939
+ }
3940
+ const { challenge, session } = initiateHandshake(shr);
3941
+ sessions.set(session.session_id, session);
3942
+ auditLog.append("l4", "handshake_initiate", shr.body.instance_id);
3943
+ return toolResult({
3944
+ session_id: session.session_id,
3945
+ challenge,
3946
+ instructions: "Send the 'challenge' object to the counterparty's sanctuary/handshake_respond tool. When you receive their response, pass it to sanctuary/handshake_complete with this session_id."
3947
+ });
3948
+ }
3949
+ },
3950
+ {
3951
+ name: "sanctuary/handshake_respond",
3952
+ description: "Respond to an incoming sovereignty handshake challenge. Verifies the initiator's SHR, signs their nonce, and returns our SHR with a counter-nonce.",
3953
+ inputSchema: {
3954
+ type: "object",
3955
+ properties: {
3956
+ challenge: {
3957
+ type: "object",
3958
+ description: "The HandshakeChallenge received from the initiator."
3959
+ },
3960
+ identity_id: {
3961
+ type: "string",
3962
+ description: "Identity to use for the response. Defaults to primary identity."
3963
+ }
3964
+ },
3965
+ required: ["challenge"]
3966
+ },
3967
+ handler: async (args) => {
3968
+ const challenge = args.challenge;
3969
+ const shr = generateSHR(args.identity_id, shrOpts);
3970
+ if (typeof shr === "string") {
3971
+ return toolResult({ error: shr });
3972
+ }
3973
+ const result = respondToHandshake(
3974
+ challenge,
3975
+ shr,
3976
+ identityManager,
3977
+ masterKey,
3978
+ args.identity_id
3979
+ );
3980
+ if ("error" in result) {
3981
+ auditLog.append("l4", "handshake_respond", shr.body.instance_id, void 0, "failure");
3982
+ return toolResult({ error: result.error });
3983
+ }
3984
+ sessions.set(result.session.session_id, result.session);
3985
+ auditLog.append("l4", "handshake_respond", shr.body.instance_id);
3986
+ return toolResult({
3987
+ session_id: result.session.session_id,
3988
+ response: result.response,
3989
+ instructions: "Send the 'response' object back to the initiator. When you receive their completion, pass it to sanctuary/handshake_status with this session_id."
3990
+ });
3991
+ }
3992
+ },
3993
+ {
3994
+ name: "sanctuary/handshake_complete",
3995
+ description: "Complete a sovereignty handshake (initiator side). Verifies the responder's SHR and nonce signature, signs their nonce, and produces the final result.",
3996
+ inputSchema: {
3997
+ type: "object",
3998
+ properties: {
3999
+ session_id: {
4000
+ type: "string",
4001
+ description: "Session ID from handshake_initiate."
4002
+ },
4003
+ response: {
4004
+ type: "object",
4005
+ description: "The HandshakeResponse received from the responder."
4006
+ }
4007
+ },
4008
+ required: ["session_id", "response"]
4009
+ },
4010
+ handler: async (args) => {
4011
+ const sessionId = args.session_id;
4012
+ const response = args.response;
4013
+ const session = sessions.get(sessionId);
4014
+ if (!session) {
4015
+ return toolResult({ error: `No handshake session found: ${sessionId}` });
4016
+ }
4017
+ if (session.state !== "initiated") {
4018
+ return toolResult({
4019
+ error: `Session is in state '${session.state}', expected 'initiated'`
4020
+ });
4021
+ }
4022
+ const result = completeHandshake(
4023
+ response,
4024
+ session,
4025
+ identityManager,
4026
+ masterKey
4027
+ );
4028
+ if ("error" in result) {
4029
+ session.state = "failed";
4030
+ auditLog.append("l4", "handshake_complete", session.our_shr.body.instance_id, void 0, "failure");
4031
+ return toolResult({ error: result.error });
4032
+ }
4033
+ session.state = "completed";
4034
+ session.their_shr = response.shr;
4035
+ session.their_nonce = response.responder_nonce;
4036
+ session.result = result.result;
4037
+ auditLog.append("l4", "handshake_complete", session.our_shr.body.instance_id);
4038
+ return toolResult({
4039
+ completion: result.completion,
4040
+ result: result.result,
4041
+ instructions: "Send the 'completion' object to the responder so they can verify the handshake. The 'result' object contains the verified counterparty status and trust tier."
4042
+ });
4043
+ }
4044
+ },
4045
+ {
4046
+ name: "sanctuary/handshake_status",
4047
+ description: "Check the status of a handshake session, or verify a completion message (responder side).",
4048
+ inputSchema: {
4049
+ type: "object",
4050
+ properties: {
4051
+ session_id: {
4052
+ type: "string",
4053
+ description: "Session ID to check."
4054
+ },
4055
+ completion: {
4056
+ type: "object",
4057
+ description: "Optional: HandshakeCompletion from the initiator (responder-side verification)."
4058
+ }
4059
+ },
4060
+ required: ["session_id"]
4061
+ },
4062
+ handler: async (args) => {
4063
+ const sessionId = args.session_id;
4064
+ const completion = args.completion;
4065
+ const session = sessions.get(sessionId);
4066
+ if (!session) {
4067
+ return toolResult({ error: `No handshake session found: ${sessionId}` });
4068
+ }
4069
+ if (completion && session.role === "responder" && session.state === "responded") {
4070
+ const result = verifyCompletion(completion, session);
4071
+ session.state = result.verified ? "completed" : "failed";
4072
+ session.result = result;
4073
+ auditLog.append(
4074
+ "l4",
4075
+ "handshake_verify_completion",
4076
+ session.our_shr.body.instance_id,
4077
+ void 0,
4078
+ result.verified ? "success" : "failure"
4079
+ );
4080
+ return toolResult({ result });
4081
+ }
4082
+ return toolResult({
4083
+ session_id: session.session_id,
4084
+ role: session.role,
4085
+ state: session.state,
4086
+ initiated_at: session.initiated_at,
4087
+ result: session.result ?? null
4088
+ });
4089
+ }
4090
+ }
4091
+ ];
4092
+ return { tools };
4093
+ }
4094
+
4095
+ // src/index.ts
4096
+ init_encoding();
4097
+ async function createSanctuaryServer(options) {
4098
+ const config = await loadConfig(options?.configPath);
4099
+ await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
4100
+ const storage = options?.storage ?? new FilesystemStorage(
4101
+ `${config.storage_path}/state`
4102
+ );
4103
+ let masterKey;
4104
+ let keyProtection;
4105
+ let recoveryKey;
4106
+ const passphrase = options?.passphrase ?? process.env.SANCTUARY_PASSPHRASE;
4107
+ if (passphrase) {
4108
+ keyProtection = "passphrase";
4109
+ let existingParams;
4110
+ try {
4111
+ const raw = await storage.read("_meta", "key-params");
4112
+ if (raw) {
4113
+ const { bytesToString: bytesToString2 } = await Promise.resolve().then(() => (init_encoding(), encoding_exports));
4114
+ existingParams = JSON.parse(bytesToString2(raw));
4115
+ }
4116
+ } catch {
4117
+ }
4118
+ const result = await deriveMasterKey(passphrase, existingParams);
4119
+ masterKey = result.key;
4120
+ if (!existingParams) {
4121
+ const { stringToBytes: stringToBytes2 } = await Promise.resolve().then(() => (init_encoding(), encoding_exports));
4122
+ await storage.write(
4123
+ "_meta",
4124
+ "key-params",
4125
+ stringToBytes2(JSON.stringify(result.params))
4126
+ );
4127
+ }
4128
+ } else {
4129
+ keyProtection = "recovery-key";
4130
+ const existing = await storage.read("_meta", "recovery-key-hash");
4131
+ if (existing) {
4132
+ masterKey = generateRandomKey();
4133
+ recoveryKey = toBase64url(masterKey);
4134
+ } else {
4135
+ masterKey = generateRandomKey();
4136
+ recoveryKey = toBase64url(masterKey);
4137
+ const { hashToString: hashToString2 } = await Promise.resolve().then(() => (init_hashing(), hashing_exports));
4138
+ const { stringToBytes: stringToBytes2 } = await Promise.resolve().then(() => (init_encoding(), encoding_exports));
4139
+ const keyHash = hashToString2(masterKey);
4140
+ await storage.write(
4141
+ "_meta",
4142
+ "recovery-key-hash",
4143
+ stringToBytes2(keyHash)
4144
+ );
4145
+ }
4146
+ }
4147
+ const auditLog = new AuditLog(storage, masterKey);
4148
+ const stateStore = new StateStore(storage, masterKey);
4149
+ const { tools: l1Tools, identityManager } = createL1Tools(
4150
+ stateStore,
4151
+ storage,
4152
+ masterKey,
4153
+ keyProtection,
4154
+ auditLog
4155
+ );
4156
+ await identityManager.load();
4157
+ const l2Tools = [
4158
+ {
4159
+ name: "sanctuary/exec_attest",
4160
+ description: "Generate an attestation of the current execution environment, including sovereignty assessment and degradation report.",
4161
+ inputSchema: {
4162
+ type: "object",
4163
+ properties: {
4164
+ include_hardware: { type: "boolean", default: true },
4165
+ include_software: { type: "boolean", default: true },
4166
+ include_network: { type: "boolean", default: true }
4167
+ }
4168
+ },
4169
+ handler: async () => {
4170
+ const degradations = [];
4171
+ degradations.push(
4172
+ "L2 isolation is process-level only; no TEE available"
4173
+ );
4174
+ if (config.disclosure.proof_system === "commitment-only") {
4175
+ degradations.push(
4176
+ "L3 proofs are commitment-based only; ZK proofs not yet available"
4177
+ );
4178
+ }
4179
+ return toolResult({
4180
+ attestation: {
4181
+ environment_type: config.execution.environment,
4182
+ hardware: {
4183
+ cpu_vendor: process.arch,
4184
+ tee_available: false,
4185
+ tee_type: void 0
4186
+ },
4187
+ software: {
4188
+ os: `${process.platform}-${process.arch}`,
4189
+ runtime: `node-${process.version}`,
4190
+ sanctuary_version: config.version,
4191
+ mcp_sdk_version: "1.26.0"
4192
+ },
4193
+ network: {
4194
+ internet_accessible: true,
4195
+ // Conservative assumption
4196
+ listening_ports: [],
4197
+ egress_restricted: false
4198
+ },
4199
+ isolation_level: "process",
4200
+ sovereignty_assessment: {
4201
+ l1_state_encrypted: true,
4202
+ l2_execution_isolated: false,
4203
+ l2_isolation_type: "process-level",
4204
+ l3_proofs_available: config.disclosure.proof_system !== "commitment-only",
4205
+ l4_reputation_active: true,
4206
+ overall_level: "mvs",
4207
+ degradations
4208
+ }
4209
+ },
4210
+ attested_at: (/* @__PURE__ */ new Date()).toISOString()
4211
+ });
4212
+ }
4213
+ },
4214
+ {
4215
+ name: "sanctuary/monitor_health",
4216
+ description: "Sanctuary Health Report (SHR) \u2014 standardized sovereignty status.",
4217
+ inputSchema: { type: "object", properties: {} },
4218
+ handler: async () => {
4219
+ const storageSizeBytes = await storage.totalSize();
4220
+ const degradations = [];
4221
+ degradations.push({
4222
+ layer: "l2",
4223
+ description: "Process-level isolation only (no TEE)",
4224
+ severity: "warning",
4225
+ mitigation: "TEE support planned for v0.3.0"
4226
+ });
4227
+ if (config.disclosure.proof_system === "commitment-only") {
4228
+ degradations.push({
4229
+ layer: "l3",
4230
+ description: "Commitment schemes only (no ZK proofs)",
4231
+ severity: "info",
4232
+ mitigation: "ZK proof support planned for v0.2.0"
4233
+ });
4234
+ }
4235
+ return toolResult({
4236
+ status: degradations.some((d) => d.severity === "critical") ? "compromised" : degradations.some((d) => d.severity === "warning") ? "degraded" : "healthy",
4237
+ storage_bytes: storageSizeBytes,
4238
+ layers: {
4239
+ l1: {
4240
+ status: "active",
4241
+ encryption_algorithm: "aes-256-gcm",
4242
+ key_count: identityManager.list().length,
4243
+ state_integrity: "verified",
4244
+ last_integrity_check: (/* @__PURE__ */ new Date()).toISOString()
4245
+ },
4246
+ l2: {
4247
+ status: "degraded",
4248
+ isolation_type: "process-level",
4249
+ attestation_available: true,
4250
+ last_attestation: (/* @__PURE__ */ new Date()).toISOString()
4251
+ },
4252
+ l3: {
4253
+ status: config.disclosure.proof_system === "commitment-only" ? "degraded" : "active",
4254
+ proof_system: config.disclosure.proof_system,
4255
+ circuits_loaded: 0,
4256
+ proofs_generated_total: 0
4257
+ },
4258
+ l4: {
4259
+ status: "active",
4260
+ mode: config.reputation.mode,
4261
+ interaction_count: 0,
4262
+ // TODO: track from reputation store
4263
+ reputation_exportable: true
4264
+ }
4265
+ },
4266
+ degradations,
4267
+ checked_at: (/* @__PURE__ */ new Date()).toISOString()
4268
+ });
4269
+ }
4270
+ },
4271
+ {
4272
+ name: "sanctuary/monitor_audit_log",
4273
+ description: "Query the sovereignty audit log.",
4274
+ inputSchema: {
4275
+ type: "object",
4276
+ properties: {
4277
+ since: { type: "string", description: "ISO 8601 timestamp" },
4278
+ layer: {
4279
+ type: "string",
4280
+ enum: ["l1", "l2", "l3", "l4"]
4281
+ },
4282
+ operation_type: { type: "string" },
4283
+ limit: { type: "number", default: 50 }
4284
+ }
4285
+ },
4286
+ handler: async (args) => {
4287
+ const result = await auditLog.query({
4288
+ since: args.since,
4289
+ layer: args.layer,
4290
+ operation_type: args.operation_type,
4291
+ limit: args.limit ?? 50
4292
+ });
4293
+ return toolResult(result);
4294
+ }
4295
+ }
4296
+ ];
4297
+ const manifestTool = {
4298
+ name: "sanctuary/manifest",
4299
+ description: "Generate the Sanctuary Interface Manifest (SIM) \u2014 a machine-readable declaration of this server's capabilities.",
4300
+ inputSchema: { type: "object", properties: {} },
4301
+ handler: async () => {
4302
+ return toolResult({
4303
+ sanctuary_version: "0.2",
4304
+ implementation: {
4305
+ name: "@sanctuary-framework/mcp-server",
4306
+ version: config.version,
4307
+ language: "typescript",
4308
+ license: "Apache-2.0"
4309
+ },
4310
+ layers: {
4311
+ l1: {
4312
+ implemented: true,
4313
+ interfaces: ["StateStore", "IdentityRoot"],
4314
+ encryption: ["aes-256-gcm"],
4315
+ identity: ["ed25519"],
4316
+ properties: {
4317
+ "S1.1_participant_held_keys": "full",
4318
+ "S1.2_encryption_at_rest": "full",
4319
+ "S1.3_integrity_verification": "full",
4320
+ "S1.4_selective_state_sharing": "full",
4321
+ "S1.5_state_portability": "full",
4322
+ "S1.6_deletion_rights": "full",
4323
+ "S1.7_identity_anchoring": "partial"
4324
+ }
4325
+ },
4326
+ l2: {
4327
+ implemented: true,
4328
+ interfaces: ["ExecutionEnvironment", "RuntimeMonitor"],
4329
+ isolation_types: [config.execution.environment],
4330
+ properties: {
4331
+ "S2.1_execution_confidentiality": "documented",
4332
+ "S2.2_verifiable_execution": "self-reported",
4333
+ "S2.5_attestation": "self-reported"
4334
+ }
4335
+ },
4336
+ l3: {
4337
+ implemented: true,
4338
+ interfaces: ["ProofEngine", "DisclosurePolicy"],
4339
+ proof_systems: [config.disclosure.proof_system],
4340
+ properties: {
4341
+ "S3.1_minimum_disclosure": "policy-based",
4342
+ "S3.3_proof_without_revelation": "commitment"
4343
+ }
4344
+ },
4345
+ l4: {
4346
+ implemented: true,
4347
+ interfaces: ["ReputationStore", "TrustBootstrap"],
4348
+ modes: [config.reputation.mode],
4349
+ properties: {
4350
+ "S4.1_earned_reputation": "full",
4351
+ "S4.2_participant_owned": "full",
4352
+ "S4.5_sybil_resistance": "basic",
4353
+ "S4.7_trust_bootstrapping": "full"
4354
+ }
4355
+ }
4356
+ },
4357
+ composition: {
4358
+ sim_version: "1.0",
4359
+ spf_supported: false,
4360
+ shr_supported: true,
4361
+ delegation_depth: 1
4362
+ },
4363
+ limitations: [
4364
+ "L1 identity uses ed25519 only; KERI support planned for v0.2.0",
4365
+ "L2 isolation is process-level only; TEE support planned for v0.3.0",
4366
+ "L3 uses commitment schemes only; ZK proofs planned for v0.2.0",
4367
+ "L4 Sybil resistance is escrow-based only",
4368
+ "Spec license: CC-BY-4.0 | Code license: Apache-2.0"
4369
+ ]
4370
+ });
4371
+ }
4372
+ };
4373
+ const { tools: l3Tools } = createL3Tools(storage, masterKey, auditLog);
4374
+ const { tools: l4Tools } = createL4Tools(
4375
+ storage,
4376
+ masterKey,
4377
+ identityManager,
4378
+ auditLog
4379
+ );
4380
+ const policy = await loadPrincipalPolicy(config.storage_path);
4381
+ const baseline = new BaselineTracker(storage, masterKey);
4382
+ await baseline.load();
4383
+ const approvalChannel = new StderrApprovalChannel(policy.approval_channel);
4384
+ const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog);
4385
+ const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
4386
+ const { tools: shrTools } = createSHRTools(
4387
+ config,
4388
+ identityManager,
4389
+ masterKey,
4390
+ auditLog
4391
+ );
4392
+ const { tools: handshakeTools } = createHandshakeTools(
4393
+ config,
4394
+ identityManager,
4395
+ masterKey,
4396
+ auditLog
4397
+ );
4398
+ const allTools = [
4399
+ ...l1Tools,
4400
+ ...l2Tools,
4401
+ ...l3Tools,
4402
+ ...l4Tools,
4403
+ ...policyTools,
4404
+ ...shrTools,
4405
+ ...handshakeTools,
4406
+ manifestTool
4407
+ ];
4408
+ const server = createServer(allTools, { gate });
4409
+ await saveConfig(config);
4410
+ const saveBaseline = () => {
4411
+ baseline.save().catch(() => {
4412
+ });
4413
+ };
4414
+ process.on("SIGINT", saveBaseline);
4415
+ process.on("SIGTERM", saveBaseline);
4416
+ if (recoveryKey) {
4417
+ console.error(
4418
+ `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
4419
+ \u2551 SANCTUARY: First Run \u2014 Recovery Key Generated \u2551
4420
+ \u2551 \u2551
4421
+ \u2551 Recovery Key: ${recoveryKey.slice(0, 20)}... \u2551
4422
+ \u2551 \u2551
4423
+ \u2551 SAVE THIS KEY. It will not be shown again. \u2551
4424
+ \u2551 Without it, your encrypted state is unrecoverable. \u2551
4425
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`
4426
+ );
4427
+ }
4428
+ return { server, config };
4429
+ }
4430
+
4431
+ // src/cli.ts
4432
+ async function main() {
4433
+ const passphrase = process.env.SANCTUARY_PASSPHRASE;
4434
+ const { server, config } = await createSanctuaryServer({ passphrase });
4435
+ if (config.transport === "stdio") {
4436
+ const transport = new stdio_js.StdioServerTransport();
4437
+ await server.connect(transport);
4438
+ console.error(`Sanctuary MCP Server v${config.version} running (stdio)`);
4439
+ console.error(`Storage: ${config.storage_path}`);
4440
+ console.error("Tools: all registered");
4441
+ } else {
4442
+ console.error("HTTP transport not yet implemented. Use stdio.");
4443
+ process.exit(1);
4444
+ }
4445
+ }
4446
+ main().catch((err) => {
4447
+ console.error("Sanctuary MCP Server failed to start:", err);
4448
+ process.exit(1);
4449
+ });
4450
+ //# sourceMappingURL=cli.cjs.map
4451
+ //# sourceMappingURL=cli.cjs.map