@sanctuary-framework/mcp-server 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { gcm } from '@noble/ciphers/aes.js';
3
3
  import { sha256 } from '@noble/hashes/sha256';
4
4
  import { hmac } from '@noble/hashes/hmac';
5
5
  import { RistrettoPoint, ed25519 } from '@noble/curves/ed25519';
6
- import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, lstat, realpath, rm, access, constants } from 'fs/promises';
6
+ import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, lstat, access, realpath, rm, constants } from 'fs/promises';
7
7
  import { join, resolve, dirname, sep, basename } from 'path';
8
8
  import os, { platform, homedir } from 'os';
9
9
  import { createRequire } from 'module';
@@ -4436,13 +4436,23 @@ function parseScalar(value) {
4436
4436
  return value.replace(/^["']|["']$/g, "");
4437
4437
  }
4438
4438
  function validatePolicy(raw) {
4439
+ if (!("tier1_always_approve" in raw)) {
4440
+ throw new Error(
4441
+ "Policy file must include 'tier1_always_approve' as an explicit list (use [] for empty). Remove specific entries instead of removing the whole key."
4442
+ );
4443
+ }
4444
+ if (!("approval_channel" in raw)) {
4445
+ throw new Error(
4446
+ "Policy file must include 'approval_channel' as an explicit object (use {} for defaults). Remove specific entries instead of removing the whole key."
4447
+ );
4448
+ }
4439
4449
  const userTier3 = raw.tier3_always_allow ?? [];
4440
4450
  const mergedTier3 = [
4441
4451
  .../* @__PURE__ */ new Set([...userTier3, ...DEFAULT_POLICY.tier3_always_allow])
4442
4452
  ];
4443
4453
  return {
4444
4454
  version: raw.version ?? 1,
4445
- tier1_always_approve: raw.tier1_always_approve ?? DEFAULT_POLICY.tier1_always_approve,
4455
+ tier1_always_approve: raw.tier1_always_approve,
4446
4456
  tier2_anomaly: {
4447
4457
  ...DEFAULT_TIER2,
4448
4458
  ...raw.tier2_anomaly ?? {}
@@ -4463,6 +4473,11 @@ function generateDefaultPolicyYaml() {
4463
4473
  # This file controls what your agent can do without asking.
4464
4474
  # Edit this file directly. Your agent cannot modify it.
4465
4475
  # Changes take effect on server restart.
4476
+ #
4477
+ # Required keys (must be present; use [] or {} for empty):
4478
+ # tier1_always_approve, approval_channel
4479
+ # Optional keys (omit to use defaults; new defaults merge automatically):
4480
+ # tier2_anomaly, tier3_always_allow
4466
4481
 
4467
4482
  version: 1
4468
4483
 
@@ -4567,20 +4582,52 @@ approval_channel:
4567
4582
  timeout_seconds: 300
4568
4583
  `;
4569
4584
  }
4585
+ var MalformedPrincipalPolicyError = class extends Error {
4586
+ constructor(policyPath, reason) {
4587
+ super(
4588
+ `Principal policy at ${policyPath} is malformed and cannot be loaded.
4589
+ Reason: ${reason}
4590
+ Sanctuary refuses to substitute a default policy when an existing file is present, to avoid silently overriding operator intent. Fix the file or delete it to regenerate the default.`
4591
+ );
4592
+ this.policyPath = policyPath;
4593
+ this.reason = reason;
4594
+ this.name = "MalformedPrincipalPolicyError";
4595
+ }
4596
+ policyPath;
4597
+ reason;
4598
+ };
4570
4599
  async function loadPrincipalPolicy(storagePath) {
4571
4600
  const policyPath = join(storagePath, "principal-policy.yaml");
4601
+ let content;
4602
+ try {
4603
+ content = await readFile(policyPath, "utf-8");
4604
+ } catch (err) {
4605
+ const code = err?.code;
4606
+ if (code === "ENOENT") {
4607
+ const defaultYaml = generateDefaultPolicyYaml();
4608
+ try {
4609
+ await writeFile(policyPath, defaultYaml, "utf-8");
4610
+ await chmod(policyPath, 384);
4611
+ } catch (writeErr) {
4612
+ console.warn(
4613
+ `Sanctuary: could not write default principal policy to ${policyPath}: ${writeErr.message}. Continuing with in-memory default.`
4614
+ );
4615
+ }
4616
+ return Object.freeze({ ...DEFAULT_POLICY });
4617
+ }
4618
+ throw new MalformedPrincipalPolicyError(
4619
+ policyPath,
4620
+ `read failed: ${err.message}`
4621
+ );
4622
+ }
4572
4623
  try {
4573
- const content = await readFile(policyPath, "utf-8");
4574
4624
  const policy = parsePolicy(content);
4575
4625
  return Object.freeze(policy);
4576
- } catch {
4577
- const defaultYaml = generateDefaultPolicyYaml();
4578
- try {
4579
- await writeFile(policyPath, defaultYaml, "utf-8");
4580
- await chmod(policyPath, 384);
4581
- } catch {
4582
- }
4583
- return Object.freeze({ ...DEFAULT_POLICY });
4626
+ } catch (parseErr) {
4627
+ throw new MalformedPrincipalPolicyError(
4628
+ policyPath,
4629
+ parseErr.message
4630
+ );
4584
4631
  }
4585
4632
  }
4586
4633
 
@@ -4807,7 +4854,7 @@ function deepSortKeys(obj) {
4807
4854
  return sorted;
4808
4855
  }
4809
4856
  function canonicalizeForSigning(body) {
4810
- return JSON.stringify(deepSortKeys(body));
4857
+ return JSON.stringify(deepSortKeys(body)).normalize("NFC");
4811
4858
  }
4812
4859
 
4813
4860
  // src/shr/generator.ts
@@ -11113,10 +11160,11 @@ function initTemplate(params) {
11113
11160
  var DEFAULT_STORAGE_DIR = ".sanctuary";
11114
11161
  var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
11115
11162
  function keychainServiceFor(storagePath, home = homedir()) {
11116
- const defaultPath = join(home, DEFAULT_STORAGE_DIR);
11117
- if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
11118
- const digest = sha256(Buffer.from(storagePath, "utf-8"));
11119
- const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
11163
+ const defaultPath = resolve(join(home, DEFAULT_STORAGE_DIR));
11164
+ const canonicalStorage = resolve(storagePath);
11165
+ if (canonicalStorage === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
11166
+ const digest = sha256(Buffer.from(canonicalStorage, "utf-8"));
11167
+ const suffix = Buffer.from(digest).toString("hex").slice(0, 16);
11120
11168
  return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
11121
11169
  }
11122
11170
  var RUNTIME_FILE_NAME = "runtime.json";
@@ -11257,7 +11305,7 @@ async function discoverTenants(options = {}) {
11257
11305
  for (const child of children) {
11258
11306
  const childPath = join(root, child);
11259
11307
  if (child.startsWith(".")) continue;
11260
- if (child === "state" || child === "backup" || child === "config") continue;
11308
+ if (child === "state" || child === "backup" || child === "config" || child === "default") continue;
11261
11309
  const s = await stat(childPath).catch(() => null);
11262
11310
  if (!s || !s.isDirectory()) continue;
11263
11311
  const desc = await describeTenant(child, childPath, home);
@@ -11269,6 +11317,17 @@ async function discoverTenants(options = {}) {
11269
11317
  const desc = await describeTenant(basename(extra), extra, home);
11270
11318
  if (desc) tenants.push(desc);
11271
11319
  }
11320
+ const seen = /* @__PURE__ */ new Map();
11321
+ for (const t of tenants) {
11322
+ seen.set(t.name, (seen.get(t.name) ?? 0) + 1);
11323
+ }
11324
+ for (const [name, count] of seen) {
11325
+ if (count > 1) {
11326
+ console.error(
11327
+ `[sanctuary] warning: ${count} tenants share the name "${name}". Use --tenant with a unique name or storage path to disambiguate.`
11328
+ );
11329
+ }
11330
+ }
11272
11331
  tenants.sort((a, b) => {
11273
11332
  if (a.name === "default") return -1;
11274
11333
  if (b.name === "default") return 1;
@@ -11617,6 +11676,16 @@ var HUB_ROUTES = {
11617
11676
  */
11618
11677
  CHAT_CONCIERGE_SEND: "/api/hub/chat/concierge",
11619
11678
  CHAT_CONCIERGE_HISTORY: "/api/hub/chat/concierge/history",
11679
+ /**
11680
+ * Concierge memory thread routes (WP-V1.3-9 Tau-1). Thread enumeration,
11681
+ * scrollback, and operator-initiated thread delete. Distinct from the
11682
+ * v1.2 `/history` route, which surfaces the active in-session thread
11683
+ * shape; the new routes target persisted multi-thread memory used by
11684
+ * v1.3 conversational sovereignty depth.
11685
+ */
11686
+ CHAT_CONCIERGE_THREADS_LIST: "/api/hub/chat/concierge/threads",
11687
+ CHAT_CONCIERGE_THREAD_READ: "/api/hub/chat/concierge/threads/:thread_id",
11688
+ CHAT_CONCIERGE_THREAD_DELETE: "/api/hub/chat/concierge/threads/:thread_id",
11620
11689
  /**
11621
11690
  * Click-to-inspect panel (WP-V1.2 reshape). Returns the agent's
11622
11691
  * recent activity feed, pending Tier 1 approvals routed through this
@@ -11640,6 +11709,10 @@ var HUB_TIER_1_AGENT_CONTROL_ACTIONS = [
11640
11709
  ];
11641
11710
  var HUB_ACTIVITY_DEFAULT_LIMIT = 50;
11642
11711
  var HUB_ACTIVITY_MAX_LIMIT = 500;
11712
+ var HUB_CHAT_THREADS_DEFAULT_LIMIT = 50;
11713
+ var HUB_CHAT_THREADS_MAX_LIMIT = 500;
11714
+ var HUB_CHAT_TURNS_DEFAULT_LIMIT = 200;
11715
+ var HUB_CHAT_TURNS_MAX_LIMIT = 1e3;
11643
11716
  var HUB_INBOX_DEFAULT_LIMIT = 100;
11644
11717
  var HUB_INBOX_MAX_LIMIT = 500;
11645
11718
  var HUB_AGENTS_DEFAULT_LIMIT = 100;
@@ -11811,6 +11884,23 @@ function checkChatMessage(value) {
11811
11884
  }
11812
11885
  return trimmed;
11813
11886
  }
11887
+ function matchConciergeThreadRoute(path) {
11888
+ const prefix = `${HUB_API_PREFIX}/chat/concierge/threads/`;
11889
+ if (!path.startsWith(prefix)) return null;
11890
+ const rest = path.slice(prefix.length);
11891
+ if (rest.length === 0 || rest.includes("/")) return null;
11892
+ const decoded = decodeURIComponent(rest);
11893
+ if (decoded.length === 0) return null;
11894
+ return { threadId: decoded };
11895
+ }
11896
+ function parseSince(raw) {
11897
+ if (raw === null || raw === "") return void 0;
11898
+ const parsed = Number.parseInt(raw, 10);
11899
+ if (Number.isNaN(parsed) || parsed < 0) {
11900
+ throw new HubValidationError("since must be a non-negative integer");
11901
+ }
11902
+ return parsed;
11903
+ }
11814
11904
  function matchInboxRoute(path) {
11815
11905
  const prefix = `${HUB_API_PREFIX}/inbox/`;
11816
11906
  if (!path.startsWith(prefix)) return null;
@@ -11994,6 +12084,47 @@ async function handleHubRoute(deps, req, res) {
11994
12084
  writeJSON2(res, 200, { ok: true, data: { messages } });
11995
12085
  return true;
11996
12086
  }
12087
+ if (method === "GET" && path === HUB_ROUTES.CHAT_CONCIERGE_THREADS_LIST) {
12088
+ const limit = parseLimit(
12089
+ url.searchParams.get("limit"),
12090
+ HUB_CHAT_THREADS_DEFAULT_LIMIT,
12091
+ HUB_CHAT_THREADS_MAX_LIMIT
12092
+ );
12093
+ const threads = await deps.service.listConciergeMemoryThreads({ limit });
12094
+ writeJSON2(res, 200, { ok: true, data: { threads } });
12095
+ return true;
12096
+ }
12097
+ {
12098
+ const threadMatch = matchConciergeThreadRoute(path);
12099
+ if (threadMatch) {
12100
+ if (method === "GET") {
12101
+ const since = parseSince(url.searchParams.get("since"));
12102
+ const limit = parseLimit(
12103
+ url.searchParams.get("limit"),
12104
+ HUB_CHAT_TURNS_DEFAULT_LIMIT,
12105
+ HUB_CHAT_TURNS_MAX_LIMIT
12106
+ );
12107
+ const readOpts = { limit };
12108
+ if (since !== void 0) readOpts.sinceTurnId = since;
12109
+ const turns = await deps.service.readConciergeMemoryThread(
12110
+ threadMatch.threadId,
12111
+ readOpts
12112
+ );
12113
+ writeJSON2(res, 200, { ok: true, data: { turns } });
12114
+ return true;
12115
+ }
12116
+ if (method === "DELETE") {
12117
+ const removed = await deps.service.deleteConciergeMemoryThread(
12118
+ threadMatch.threadId
12119
+ );
12120
+ writeJSON2(res, removed ? 200 : 404, {
12121
+ ok: removed,
12122
+ data: { thread_id: threadMatch.threadId, removed }
12123
+ });
12124
+ return true;
12125
+ }
12126
+ }
12127
+ }
11997
12128
  writeJSON2(res, 404, { ok: false, error: "not_found", path });
11998
12129
  return true;
11999
12130
  } catch (err) {
@@ -15637,6 +15768,8 @@ var IntelligenceRouterError = class extends Error {
15637
15768
  this.code = code;
15638
15769
  this.name = "IntelligenceRouterError";
15639
15770
  }
15771
+ statusCode;
15772
+ code;
15640
15773
  };
15641
15774
  function writeJSON3(res, status, payload) {
15642
15775
  res.writeHead(status, {
@@ -16041,6 +16174,162 @@ async function dispatchV11Request(inputs, req, res, url, method) {
16041
16174
  return false;
16042
16175
  }
16043
16176
 
16177
+ // src/principal-policy/approval-aggregator-routes.ts
16178
+ var APPROVAL_INBOX_API_PREFIX = "/api/approval-inbox";
16179
+ var APPROVAL_INBOX_OPERATOR_DEFAULT = "operator_dashboard";
16180
+ var APPROVAL_INBOX_DEFAULT_LIMIT = 50;
16181
+ var APPROVAL_INBOX_MAX_LIMIT = 200;
16182
+ function writeJSON4(res, status, payload) {
16183
+ res.writeHead(status, {
16184
+ "Content-Type": "application/json",
16185
+ "Cache-Control": "no-store"
16186
+ });
16187
+ res.end(JSON.stringify(payload));
16188
+ }
16189
+ function parseLimit2(raw, defaultValue, max) {
16190
+ if (raw === null || raw === "") return defaultValue;
16191
+ const parsed = Number.parseInt(raw, 10);
16192
+ if (Number.isNaN(parsed) || parsed < 0) {
16193
+ return defaultValue;
16194
+ }
16195
+ return Math.min(parsed, max);
16196
+ }
16197
+ function isStatusFilter(value) {
16198
+ return value === "pending" || value === "approved" || value === "denied" || value === "timeout" || value === "expired";
16199
+ }
16200
+ function matchEntryRoute(path) {
16201
+ const prefix = `${APPROVAL_INBOX_API_PREFIX}/`;
16202
+ if (!path.startsWith(prefix)) return null;
16203
+ const rest = path.slice(prefix.length);
16204
+ if (rest.length === 0) return null;
16205
+ const slash = rest.indexOf("/");
16206
+ if (slash === -1) {
16207
+ return { aggregatorId: decodeURIComponent(rest), action: null };
16208
+ }
16209
+ return {
16210
+ aggregatorId: decodeURIComponent(rest.slice(0, slash)),
16211
+ action: rest.slice(slash + 1)
16212
+ };
16213
+ }
16214
+ async function handleStream2(deps, res) {
16215
+ res.writeHead(200, {
16216
+ "Content-Type": "text/event-stream",
16217
+ "Cache-Control": "no-cache, no-transform",
16218
+ Connection: "keep-alive",
16219
+ "X-Accel-Buffering": "no"
16220
+ });
16221
+ const initial = await deps.aggregator.list({ status: "pending" });
16222
+ res.write(
16223
+ `event: approval_inbox_snapshot
16224
+ data: ${JSON.stringify({ entries: initial })}
16225
+
16226
+ `
16227
+ );
16228
+ const unsubscribe = deps.aggregator.onEvent((event) => {
16229
+ try {
16230
+ res.write(
16231
+ `event: approval_inbox_${event.type}
16232
+ data: ${JSON.stringify(event.entry)}
16233
+
16234
+ `
16235
+ );
16236
+ } catch {
16237
+ }
16238
+ });
16239
+ const keepAlive = setInterval(() => {
16240
+ try {
16241
+ res.write(": keepalive\n\n");
16242
+ } catch {
16243
+ }
16244
+ }, 25e3);
16245
+ const cleanup = () => {
16246
+ clearInterval(keepAlive);
16247
+ unsubscribe();
16248
+ };
16249
+ res.on("close", cleanup);
16250
+ res.on("error", cleanup);
16251
+ }
16252
+ async function handleApprovalInboxRoute(deps, req, res) {
16253
+ const host = req.headers.host || "localhost";
16254
+ const url = new URL(req.url ?? "/", `http://${host}`);
16255
+ const method = (req.method ?? "GET").toUpperCase();
16256
+ const path = url.pathname;
16257
+ if (path !== APPROVAL_INBOX_API_PREFIX && !path.startsWith(`${APPROVAL_INBOX_API_PREFIX}/`)) {
16258
+ return false;
16259
+ }
16260
+ const checkAuth = authMiddleware(deps.authConfig);
16261
+ if (!checkAuth(req, res, url)) return true;
16262
+ try {
16263
+ if (method === "GET" && path === `${APPROVAL_INBOX_API_PREFIX}/stream`) {
16264
+ await handleStream2(deps, res);
16265
+ return true;
16266
+ }
16267
+ if (method === "GET" && path === APPROVAL_INBOX_API_PREFIX) {
16268
+ const limit = parseLimit2(
16269
+ url.searchParams.get("limit"),
16270
+ APPROVAL_INBOX_DEFAULT_LIMIT,
16271
+ APPROVAL_INBOX_MAX_LIMIT
16272
+ );
16273
+ const statusRaw = url.searchParams.get("status");
16274
+ const status = statusRaw && isStatusFilter(statusRaw) ? statusRaw : "pending";
16275
+ const sinceTs = url.searchParams.get("since") ?? void 0;
16276
+ const entries = await deps.aggregator.list({
16277
+ status,
16278
+ limit,
16279
+ ...sinceTs !== void 0 ? { sinceTs } : {}
16280
+ });
16281
+ writeJSON4(res, 200, { ok: true, data: { entries } });
16282
+ return true;
16283
+ }
16284
+ const entryMatch = matchEntryRoute(path);
16285
+ if (entryMatch === null) {
16286
+ writeJSON4(res, 404, { ok: false, error: "not_found", path });
16287
+ return true;
16288
+ }
16289
+ if (method === "GET" && entryMatch.action === null) {
16290
+ const entries = await deps.aggregator.list({ limit: APPROVAL_INBOX_MAX_LIMIT });
16291
+ const entry = entries.find(
16292
+ (e) => e.aggregator_id === entryMatch.aggregatorId
16293
+ );
16294
+ if (!entry) {
16295
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
16296
+ return true;
16297
+ }
16298
+ const payload = await deps.aggregator.getFullPayload(
16299
+ entryMatch.aggregatorId
16300
+ );
16301
+ writeJSON4(res, 200, { ok: true, data: { entry, request_payload: payload } });
16302
+ return true;
16303
+ }
16304
+ if (method === "POST" && (entryMatch.action === "approve" || entryMatch.action === "deny")) {
16305
+ const decision = entryMatch.action === "approve" ? "approved" : "denied";
16306
+ const operatorId = deps.operatorId ?? APPROVAL_INBOX_OPERATOR_DEFAULT;
16307
+ try {
16308
+ const entry = await deps.aggregator.resolve(
16309
+ entryMatch.aggregatorId,
16310
+ decision,
16311
+ operatorId
16312
+ );
16313
+ writeJSON4(res, 200, { ok: true, data: { entry } });
16314
+ } catch (err) {
16315
+ const msg = err instanceof Error ? err.message : String(err);
16316
+ if (msg === "approval-aggregator: not_found") {
16317
+ writeJSON4(res, 404, { ok: false, error: "not_found" });
16318
+ } else {
16319
+ writeJSON4(res, 500, { ok: false, error: "internal", detail: msg });
16320
+ }
16321
+ }
16322
+ return true;
16323
+ }
16324
+ writeJSON4(res, 404, { ok: false, error: "not_found", path });
16325
+ return true;
16326
+ } catch (err) {
16327
+ const msg = err instanceof Error ? err.message : String(err);
16328
+ writeJSON4(res, 500, { ok: false, error: "internal", detail: msg });
16329
+ return true;
16330
+ }
16331
+ }
16332
+
16044
16333
  // src/principal-policy/dashboard.ts
16045
16334
  var SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
16046
16335
  var SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
@@ -16105,6 +16394,14 @@ var DashboardApprovalChannel = class {
16105
16394
  * regardless. Default route flip is deferred to v1.2.
16106
16395
  */
16107
16396
  v11Bindings = null;
16397
+ /**
16398
+ * v1.3 WP-V1.3-10 Cross-Harness Approval Inbox aggregator. Mounted
16399
+ * additively at `/api/approval-inbox/*` when set. Legacy approval
16400
+ * routes at `/api/approvals/:id/(allow|deny)` continue to serve. The
16401
+ * aggregator is a passive subscriber to the gate; the routes here are
16402
+ * the operator-facing query / decision surface.
16403
+ */
16404
+ approvalAggregator = null;
16108
16405
  constructor(config) {
16109
16406
  this.config = config;
16110
16407
  this.authToken = config.auth_token;
@@ -16155,6 +16452,34 @@ var DashboardApprovalChannel = class {
16155
16452
  setV11Bindings(bindings) {
16156
16453
  this.v11Bindings = bindings;
16157
16454
  }
16455
+ /**
16456
+ * v1.3 WP-V1.3-10 Upsilon-1: bind the cross-harness approval inbox
16457
+ * aggregator. Once set, requests to `/api/approval-inbox/*` route
16458
+ * through `handleApprovalInboxRoute`. Pass `null` to detach (used by
16459
+ * tests + during shutdown).
16460
+ */
16461
+ setApprovalAggregator(aggregator) {
16462
+ this.approvalAggregator = aggregator;
16463
+ }
16464
+ /**
16465
+ * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
16466
+ * before the legacy approval route table. Returns true when served.
16467
+ */
16468
+ async dispatchApprovalInbox(req, res) {
16469
+ if (!this.approvalAggregator) return false;
16470
+ return handleApprovalInboxRoute(
16471
+ {
16472
+ authConfig: {
16473
+ loopbackAutoAuth: this._autoAuthLocalhost,
16474
+ ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
16475
+ },
16476
+ aggregator: this.approvalAggregator,
16477
+ operatorId: this.identityManager?.getPrimaryIdentityId() ?? void 0
16478
+ },
16479
+ req,
16480
+ res
16481
+ );
16482
+ }
16158
16483
  /**
16159
16484
  * v1.1 dispatch entry point. Called from `handleRequest` before the
16160
16485
  * legacy route table. Returns true when the request was served by v1.1
@@ -16220,7 +16545,7 @@ var DashboardApprovalChannel = class {
16220
16545
  server = createServer$2(handler);
16221
16546
  }
16222
16547
  this.httpServer = server;
16223
- return new Promise((resolve4, reject) => {
16548
+ return new Promise((resolve6, reject) => {
16224
16549
  const protocol = this.useTLS ? "https" : "http";
16225
16550
  const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
16226
16551
  server.listen(this.config.port, this.config.host, () => {
@@ -16245,7 +16570,7 @@ var DashboardApprovalChannel = class {
16245
16570
  if (shouldAutoOpen) {
16246
16571
  this.openInBrowser(sessionUrl);
16247
16572
  }
16248
- resolve4();
16573
+ resolve6();
16249
16574
  });
16250
16575
  server.on("error", (err) => {
16251
16576
  if (err.code === "EADDRINUSE") {
@@ -16291,8 +16616,8 @@ var DashboardApprovalChannel = class {
16291
16616
  }
16292
16617
  this.rateLimits.clear();
16293
16618
  if (this.httpServer) {
16294
- return new Promise((resolve4) => {
16295
- this.httpServer.close(() => resolve4());
16619
+ return new Promise((resolve6) => {
16620
+ this.httpServer.close(() => resolve6());
16296
16621
  });
16297
16622
  }
16298
16623
  }
@@ -16306,7 +16631,7 @@ var DashboardApprovalChannel = class {
16306
16631
  `[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
16307
16632
  `
16308
16633
  );
16309
- return new Promise((resolve4) => {
16634
+ return new Promise((resolve6) => {
16310
16635
  const timer = setTimeout(() => {
16311
16636
  this.pending.delete(id);
16312
16637
  const response = {
@@ -16320,12 +16645,12 @@ var DashboardApprovalChannel = class {
16320
16645
  decision: response.decision,
16321
16646
  decided_by: "timeout"
16322
16647
  });
16323
- resolve4(response);
16648
+ resolve6(response);
16324
16649
  }, this.config.timeout_seconds * 1e3);
16325
16650
  const pending = {
16326
16651
  id,
16327
16652
  request,
16328
- resolve: resolve4,
16653
+ resolve: resolve6,
16329
16654
  timer,
16330
16655
  created_at: (/* @__PURE__ */ new Date()).toISOString()
16331
16656
  };
@@ -16530,6 +16855,18 @@ var DashboardApprovalChannel = class {
16530
16855
  res.end();
16531
16856
  return;
16532
16857
  }
16858
+ if (this.approvalAggregator && url.pathname.startsWith(APPROVAL_INBOX_API_PREFIX)) {
16859
+ this.dispatchApprovalInbox(req, res).then((handled) => {
16860
+ if (handled) return;
16861
+ this.handleLegacyRequest(req, res, url, method);
16862
+ }).catch(() => {
16863
+ if (!res.headersSent) {
16864
+ res.writeHead(500, { "Content-Type": "application/json" });
16865
+ res.end(JSON.stringify({ error: "Internal server error" }));
16866
+ }
16867
+ });
16868
+ return;
16869
+ }
16533
16870
  if (this.v11Bindings) {
16534
16871
  this.dispatchV11(req, res, url, method).then((handled) => {
16535
16872
  if (handled) return;
@@ -17186,7 +17523,7 @@ var WebhookApprovalChannel = class {
17186
17523
  * Start the callback listener server.
17187
17524
  */
17188
17525
  async start() {
17189
- return new Promise((resolve4, reject) => {
17526
+ return new Promise((resolve6, reject) => {
17190
17527
  this.callbackServer = createServer$2(
17191
17528
  (req, res) => this.handleCallback(req, res)
17192
17529
  );
@@ -17201,7 +17538,7 @@ var WebhookApprovalChannel = class {
17201
17538
 
17202
17539
  `
17203
17540
  );
17204
- resolve4();
17541
+ resolve6();
17205
17542
  }
17206
17543
  );
17207
17544
  this.callbackServer.on("error", reject);
@@ -17221,8 +17558,8 @@ var WebhookApprovalChannel = class {
17221
17558
  }
17222
17559
  this.pending.clear();
17223
17560
  if (this.callbackServer) {
17224
- return new Promise((resolve4) => {
17225
- this.callbackServer.close(() => resolve4());
17561
+ return new Promise((resolve6) => {
17562
+ this.callbackServer.close(() => resolve6());
17226
17563
  });
17227
17564
  }
17228
17565
  }
@@ -17235,7 +17572,7 @@ var WebhookApprovalChannel = class {
17235
17572
  `[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
17236
17573
  `
17237
17574
  );
17238
- return new Promise((resolve4) => {
17575
+ return new Promise((resolve6) => {
17239
17576
  const timer = setTimeout(() => {
17240
17577
  this.pending.delete(id);
17241
17578
  const response = {
@@ -17244,12 +17581,12 @@ var WebhookApprovalChannel = class {
17244
17581
  decided_at: (/* @__PURE__ */ new Date()).toISOString(),
17245
17582
  decided_by: "timeout"
17246
17583
  };
17247
- resolve4(response);
17584
+ resolve6(response);
17248
17585
  }, this.config.timeout_seconds * 1e3);
17249
17586
  const pending = {
17250
17587
  id,
17251
17588
  request,
17252
- resolve: resolve4,
17589
+ resolve: resolve6,
17253
17590
  timer,
17254
17591
  created_at: (/* @__PURE__ */ new Date()).toISOString()
17255
17592
  };
@@ -18535,6 +18872,11 @@ var InjectionDetector = class {
18535
18872
  }
18536
18873
  };
18537
18874
 
18875
+ // src/principal-policy/deny-vocabulary.ts
18876
+ var AGENT_VISIBLE_DENY_REASONS = {
18877
+ REQUIRES_APPROVAL: "operation requires operator approval",
18878
+ NOT_PERMITTED: "operation not permitted"};
18879
+
18538
18880
  // src/principal-policy/gate.ts
18539
18881
  var ApprovalGate = class {
18540
18882
  policy;
@@ -18543,14 +18885,25 @@ var ApprovalGate = class {
18543
18885
  auditLog;
18544
18886
  injectionDetector;
18545
18887
  onInjectionAlert;
18888
+ onApprovalEvent;
18546
18889
  proxyTierResolver;
18547
- constructor(policy, baseline, channel, auditLog, injectionDetector, onInjectionAlert) {
18890
+ constructor(policy, baseline, channel, auditLog, injectionDetector, onInjectionAlert, onApprovalEvent) {
18548
18891
  this.policy = policy;
18549
18892
  this.baseline = baseline;
18550
18893
  this.channel = channel;
18551
18894
  this.auditLog = auditLog;
18552
18895
  this.injectionDetector = injectionDetector ?? new InjectionDetector();
18553
18896
  this.onInjectionAlert = onInjectionAlert;
18897
+ this.onApprovalEvent = onApprovalEvent;
18898
+ }
18899
+ /**
18900
+ * Set the approval-event callback after construction. Used by the
18901
+ * Upsilon-1 wire-up when the aggregator is constructed alongside the
18902
+ * gate. The aggregator subscribes through this setter rather than the
18903
+ * constructor so existing call sites continue to work unchanged.
18904
+ */
18905
+ setApprovalEventCallback(cb) {
18906
+ this.onApprovalEvent = cb;
18554
18907
  }
18555
18908
  /**
18556
18909
  * Set the proxy tier resolver. Called after the proxy router is initialized.
@@ -18587,10 +18940,16 @@ var ApprovalGate = class {
18587
18940
  });
18588
18941
  }
18589
18942
  if (injectionResult.recommendation === "block") {
18943
+ this.auditLog.append("l2", `gate_injection_block:${operation}`, "system", {
18944
+ tier: 1,
18945
+ operation,
18946
+ injection_confidence: injectionResult.confidence,
18947
+ signal_count: injectionResult.signals.length
18948
+ });
18590
18949
  return {
18591
18950
  allowed: false,
18592
18951
  tier: 1,
18593
- reason: `Blocked: prompt injection detected in "${operation}" (confidence: ${(injectionResult.confidence * 100).toFixed(0)}%)`,
18952
+ reason: AGENT_VISIBLE_DENY_REASONS.NOT_PERMITTED,
18594
18953
  approval_required: false
18595
18954
  };
18596
18955
  }
@@ -18667,7 +19026,7 @@ var ApprovalGate = class {
18667
19026
  this.auditLog.append("l2", `gate_unclassified:${operation}`, "system", {
18668
19027
  tier: 1,
18669
19028
  operation,
18670
- warning: "Operation is not classified in any policy tier \u2014 defaulting to Tier 1 (require approval)"
19029
+ warning: "Operation is not classified in any policy tier, defaulting to Tier 1 (require approval)"
18671
19030
  });
18672
19031
  return this.requestApproval(
18673
19032
  operation,
@@ -18778,25 +19137,109 @@ var ApprovalGate = class {
18778
19137
  }
18779
19138
  /**
18780
19139
  * Request approval from the human principal.
19140
+ *
19141
+ * Fail-closed contract (full-sweep #49): if the channel throws (network
19142
+ * down, callback unreachable, dashboard SSE peer dropped, webhook DNS
19143
+ * failure, etc.), the gate denies the operation and audit-logs the cause.
19144
+ * Channel-internal timeouts already resolve with decision: "deny" per
19145
+ * SEC-002; this catch covers the remaining "channel raised" path so an
19146
+ * unhandled rejection cannot turn into an indeterminate state at the gate.
18781
19147
  */
18782
19148
  async requestApproval(operation, tier, reason, context) {
19149
+ const requestTimestamp = (/* @__PURE__ */ new Date()).toISOString();
18783
19150
  const request = {
18784
19151
  operation,
18785
19152
  tier,
18786
19153
  reason,
18787
19154
  context,
18788
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
19155
+ timestamp: requestTimestamp
18789
19156
  };
18790
- const response = await this.channel.requestApproval(request);
19157
+ const correlationId = `${requestTimestamp}:${operation}:${Math.random().toString(16).slice(2, 6)}`;
19158
+ if (this.onApprovalEvent) {
19159
+ try {
19160
+ this.onApprovalEvent({
19161
+ phase: "requested",
19162
+ operation,
19163
+ tier,
19164
+ reason,
19165
+ context,
19166
+ request_timestamp: requestTimestamp,
19167
+ correlation_id: correlationId
19168
+ });
19169
+ } catch {
19170
+ }
19171
+ }
19172
+ let response;
19173
+ try {
19174
+ response = await this.channel.requestApproval(request);
19175
+ } catch (err) {
19176
+ const errMessage = err instanceof Error ? err.message : String(err);
19177
+ const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
19178
+ this.auditLog.append("l2", `gate_deny:${operation}`, "system", {
19179
+ tier,
19180
+ reason,
19181
+ decided_by: "channel_failure",
19182
+ channel_error: errMessage
19183
+ });
19184
+ if (this.onApprovalEvent) {
19185
+ try {
19186
+ this.onApprovalEvent({
19187
+ phase: "resolved",
19188
+ operation,
19189
+ tier,
19190
+ reason,
19191
+ context,
19192
+ request_timestamp: requestTimestamp,
19193
+ resolution: {
19194
+ decision: "deny",
19195
+ decided_at: decidedAt,
19196
+ decided_by: "channel_failure"
19197
+ },
19198
+ correlation_id: correlationId
19199
+ });
19200
+ } catch {
19201
+ }
19202
+ }
19203
+ return {
19204
+ allowed: false,
19205
+ tier,
19206
+ reason: AGENT_VISIBLE_DENY_REASONS.REQUIRES_APPROVAL,
19207
+ approval_required: true,
19208
+ approval_response: {
19209
+ decision: "deny",
19210
+ decided_at: decidedAt,
19211
+ decided_by: "channel_failure"
19212
+ }
19213
+ };
19214
+ }
18791
19215
  this.auditLog.append("l2", `gate_${response.decision}:${operation}`, "system", {
18792
19216
  tier,
18793
19217
  reason,
18794
19218
  decided_by: response.decided_by
18795
19219
  });
19220
+ if (this.onApprovalEvent) {
19221
+ try {
19222
+ this.onApprovalEvent({
19223
+ phase: "resolved",
19224
+ operation,
19225
+ tier,
19226
+ reason,
19227
+ context,
19228
+ request_timestamp: requestTimestamp,
19229
+ resolution: {
19230
+ decision: response.decision,
19231
+ decided_at: response.decided_at,
19232
+ decided_by: response.decided_by
19233
+ },
19234
+ correlation_id: correlationId
19235
+ });
19236
+ } catch {
19237
+ }
19238
+ }
18796
19239
  return {
18797
19240
  allowed: response.decision === "approve",
18798
19241
  tier,
18799
- reason: response.decision === "approve" ? `Approved by ${response.decided_by}` : `Tier ${tier} operation requires approval`,
19242
+ reason: response.decision === "approve" ? `Approved by ${response.decided_by}` : AGENT_VISIBLE_DENY_REASONS.REQUIRES_APPROVAL,
18800
19243
  approval_required: true,
18801
19244
  approval_response: response
18802
19245
  };
@@ -18826,6 +19269,352 @@ var ApprovalGate = class {
18826
19269
  }
18827
19270
  };
18828
19271
 
19272
+ // src/principal-policy/approval-aggregator.ts
19273
+ init_encryption();
19274
+ init_encoding();
19275
+ var APPROVAL_AGGREGATOR_NAMESPACE = "_approval_aggregator";
19276
+ var APPROVAL_AGGREGATOR_HKDF_INFO = "l2-approval-aggregator-v1";
19277
+ var APPROVAL_AGGREGATOR_AUDIT_OPS = {
19278
+ AGGREGATED: "cross_harness_approval_aggregated",
19279
+ RESOLVED: "cross_harness_approval_resolved",
19280
+ DEDUPED: "cross_harness_approval_deduped"
19281
+ };
19282
+ var DEFAULT_PENDING_TTL_MS = 5 * 60 * 1e3;
19283
+ var DEFAULT_MAX_LIST_LIMIT = 200;
19284
+ var DEFAULT_LIST_PAGE_SIZE = 50;
19285
+ var ApprovalAggregator = class {
19286
+ storage;
19287
+ encryptionKey;
19288
+ auditLog;
19289
+ identityId;
19290
+ fortressId;
19291
+ pendingTtlMs;
19292
+ maxListLimit;
19293
+ now;
19294
+ resolveSourceContext;
19295
+ resolveHubInboxItemId;
19296
+ /** Cached entries by `aggregator_id`. */
19297
+ entries = /* @__PURE__ */ new Map();
19298
+ /** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
19299
+ dedupIndex = /* @__PURE__ */ new Map();
19300
+ /** Correlation index: gate `correlation_id` -> aggregator_id. */
19301
+ correlationIndex = /* @__PURE__ */ new Map();
19302
+ /** Original request payloads kept in-memory for `getFullPayload()`. */
19303
+ fullPayloads = /* @__PURE__ */ new Map();
19304
+ /** Has the aggregator hydrated persisted entries on this process? */
19305
+ hydrated = false;
19306
+ /** Active SSE listeners. */
19307
+ listeners = /* @__PURE__ */ new Set();
19308
+ constructor(deps) {
19309
+ this.storage = deps.storage;
19310
+ this.encryptionKey = derivePurposeKey(
19311
+ deps.masterKey,
19312
+ APPROVAL_AGGREGATOR_HKDF_INFO
19313
+ );
19314
+ this.auditLog = deps.auditLog;
19315
+ this.identityId = deps.identityId;
19316
+ this.fortressId = deps.fortressId;
19317
+ this.pendingTtlMs = deps.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS;
19318
+ this.maxListLimit = deps.maxListLimit ?? DEFAULT_MAX_LIST_LIMIT;
19319
+ this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
19320
+ this.resolveSourceContext = deps.resolveSourceContext ?? ((_event) => ({
19321
+ source_harness: this.fortressId,
19322
+ source_agent_id: this.fortressId
19323
+ }));
19324
+ this.resolveHubInboxItemId = deps.resolveHubInboxItemId ?? ((_event) => void 0);
19325
+ }
19326
+ /**
19327
+ * Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
19328
+ * use this to forward aggregator emissions to the dashboard.
19329
+ */
19330
+ onEvent(listener) {
19331
+ this.listeners.add(listener);
19332
+ return () => this.listeners.delete(listener);
19333
+ }
19334
+ /**
19335
+ * Ingest a gate event. Returns the aggregator entry on first sight,
19336
+ * `null` when deduped. Resolution events update the existing record;
19337
+ * unmatched resolutions are dropped silently (caller's gate emitted a
19338
+ * resolved-without-requested pair, which the aggregator does not invent
19339
+ * a record for).
19340
+ */
19341
+ async ingest(event) {
19342
+ await this.hydrate();
19343
+ if (event.phase === "requested") {
19344
+ return this.ingestRequested(event);
19345
+ }
19346
+ if (event.phase === "resolved") {
19347
+ return this.ingestResolved(event);
19348
+ }
19349
+ return null;
19350
+ }
19351
+ /**
19352
+ * List pending or recently resolved entries. Pending entries past TTL
19353
+ * are lazily transitioned to `expired` and persisted before the list
19354
+ * snapshot is returned.
19355
+ */
19356
+ async list(opts) {
19357
+ await this.hydrate();
19358
+ await this.expireStale();
19359
+ const limit = Math.min(
19360
+ opts?.limit ?? DEFAULT_LIST_PAGE_SIZE,
19361
+ this.maxListLimit
19362
+ );
19363
+ const sinceMs = opts?.sinceTs ? Date.parse(opts.sinceTs) : Number.NEGATIVE_INFINITY;
19364
+ const matching = [];
19365
+ for (const entry of this.entries.values()) {
19366
+ if (opts?.status && entry.status !== opts.status) continue;
19367
+ if (Date.parse(entry.created_at) < sinceMs) continue;
19368
+ matching.push(entry);
19369
+ }
19370
+ matching.sort((a, b) => b.created_at.localeCompare(a.created_at));
19371
+ return matching.slice(0, limit);
19372
+ }
19373
+ /**
19374
+ * Return the original (unhashed) request payload for the entry. Returns
19375
+ * `null` when the entry is unknown or the payload was evicted (e.g. the
19376
+ * process restarted; payloads are in-memory only at v1.3 Upsilon-1).
19377
+ */
19378
+ async getFullPayload(aggregatorId) {
19379
+ await this.hydrate();
19380
+ if (!this.entries.has(aggregatorId)) return null;
19381
+ return this.fullPayloads.get(aggregatorId) ?? null;
19382
+ }
19383
+ /**
19384
+ * Resolve an entry. Used by both:
19385
+ * 1. The gate wire-up on channel-decision return.
19386
+ * 2. The HTTP `approve`/`deny` routes when an operator clicks.
19387
+ *
19388
+ * Idempotent: resolving an already-resolved entry is a no-op (the record
19389
+ * keeps its first decision and the audit log is not double-fired).
19390
+ * Unknown ids throw `Error("approval-aggregator: not_found")` so HTTP
19391
+ * routes return 404.
19392
+ */
19393
+ async resolve(aggregatorId, decision, operatorId) {
19394
+ await this.hydrate();
19395
+ const entry = this.entries.get(aggregatorId);
19396
+ if (!entry) {
19397
+ throw new Error("approval-aggregator: not_found");
19398
+ }
19399
+ if (entry.status !== "pending") {
19400
+ return entry;
19401
+ }
19402
+ entry.status = decision;
19403
+ entry.resolved_at = this.now().toISOString();
19404
+ entry.resolved_by = operatorId;
19405
+ await this.persist(entry);
19406
+ this.auditLog.append(
19407
+ "l2",
19408
+ APPROVAL_AGGREGATOR_AUDIT_OPS.RESOLVED,
19409
+ this.identityId,
19410
+ {
19411
+ aggregator_id: entry.aggregator_id,
19412
+ source_harness: entry.source_harness,
19413
+ source_agent_id: entry.source_agent_id,
19414
+ audit_log_entry_id: entry.audit_log_entry_id,
19415
+ policy_rule_id: entry.policy_rule_id,
19416
+ decision,
19417
+ decided_by: operatorId,
19418
+ decided_at: entry.resolved_at
19419
+ }
19420
+ );
19421
+ this.emit({ type: "resolved", entry: { ...entry } });
19422
+ return entry;
19423
+ }
19424
+ // ── Internal: ingest paths ─────────────────────────────────────────────
19425
+ async ingestRequested(event) {
19426
+ const ctx = this.resolveSourceContext(event);
19427
+ const auditId = this.auditEntryIdForEvent(event);
19428
+ const dedupKey = `${ctx.source_harness}|${ctx.source_agent_id}|${auditId}`;
19429
+ const existing = this.dedupIndex.get(dedupKey);
19430
+ if (existing) {
19431
+ const existingEntry = this.entries.get(existing);
19432
+ if (existingEntry) {
19433
+ this.correlationIndex.set(event.correlation_id, existing);
19434
+ this.auditLog.append(
19435
+ "l2",
19436
+ APPROVAL_AGGREGATOR_AUDIT_OPS.DEDUPED,
19437
+ this.identityId,
19438
+ {
19439
+ aggregator_id: existing,
19440
+ source_harness: ctx.source_harness,
19441
+ source_agent_id: ctx.source_agent_id,
19442
+ audit_log_entry_id: auditId,
19443
+ policy_rule_id: this.derivePolicyRuleId(event),
19444
+ correlation_id: event.correlation_id
19445
+ }
19446
+ );
19447
+ this.emit({ type: "deduped", entry: { ...existingEntry } });
19448
+ return null;
19449
+ }
19450
+ }
19451
+ const id = randomUUID();
19452
+ const now = this.now();
19453
+ const expires = new Date(now.getTime() + this.pendingTtlMs);
19454
+ const hubInboxId = this.resolveHubInboxItemId(event);
19455
+ const entry = {
19456
+ aggregator_id: id,
19457
+ source_harness: ctx.source_harness,
19458
+ source_agent_id: ctx.source_agent_id,
19459
+ audit_log_entry_id: auditId,
19460
+ policy_rule_id: this.derivePolicyRuleId(event),
19461
+ action_summary: this.deriveActionSummary(event),
19462
+ request_payload_hash: this.hashPayload(event.context),
19463
+ status: "pending",
19464
+ created_at: now.toISOString(),
19465
+ expires_at: expires.toISOString(),
19466
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
19467
+ };
19468
+ this.entries.set(id, entry);
19469
+ this.dedupIndex.set(dedupKey, id);
19470
+ this.correlationIndex.set(event.correlation_id, id);
19471
+ this.fullPayloads.set(id, event.context);
19472
+ await this.persist(entry);
19473
+ this.auditLog.append(
19474
+ "l2",
19475
+ APPROVAL_AGGREGATOR_AUDIT_OPS.AGGREGATED,
19476
+ this.identityId,
19477
+ {
19478
+ aggregator_id: id,
19479
+ source_harness: ctx.source_harness,
19480
+ source_agent_id: ctx.source_agent_id,
19481
+ audit_log_entry_id: auditId,
19482
+ policy_rule_id: entry.policy_rule_id,
19483
+ ...hubInboxId !== void 0 ? { hub_inbox_item_id: hubInboxId } : {}
19484
+ }
19485
+ );
19486
+ this.emit({ type: "aggregated", entry: { ...entry } });
19487
+ return entry;
19488
+ }
19489
+ async ingestResolved(event) {
19490
+ const id = this.correlationIndex.get(event.correlation_id);
19491
+ if (!id) return null;
19492
+ const entry = this.entries.get(id);
19493
+ if (!entry) return null;
19494
+ if (entry.status !== "pending") return entry;
19495
+ if (!event.resolution) return entry;
19496
+ const failClosed = event.resolution.decision === "deny" && event.resolution.decided_by === "channel_failure";
19497
+ const status = failClosed ? "timeout" : event.resolution.decision === "approve" ? "approved" : "denied";
19498
+ entry.status = status;
19499
+ entry.resolved_at = event.resolution.decided_at;
19500
+ entry.resolved_by = event.resolution.decided_by;
19501
+ await this.persist(entry);
19502
+ this.auditLog.append(
19503
+ "l2",
19504
+ APPROVAL_AGGREGATOR_AUDIT_OPS.RESOLVED,
19505
+ this.identityId,
19506
+ {
19507
+ aggregator_id: id,
19508
+ source_harness: entry.source_harness,
19509
+ source_agent_id: entry.source_agent_id,
19510
+ audit_log_entry_id: entry.audit_log_entry_id,
19511
+ policy_rule_id: entry.policy_rule_id,
19512
+ decision: status,
19513
+ decided_by: entry.resolved_by,
19514
+ decided_at: entry.resolved_at,
19515
+ fail_closed: failClosed
19516
+ }
19517
+ );
19518
+ this.emit({ type: "resolved", entry: { ...entry } });
19519
+ return entry;
19520
+ }
19521
+ // ── Internal: helpers ──────────────────────────────────────────────────
19522
+ /**
19523
+ * Audit-log entry id for the dedup tuple. The audit log itself does not
19524
+ * surface a stable per-entry id (counter-prefixed keys are internal); the
19525
+ * aggregator uses the request timestamp + operation, which together pin
19526
+ * the audit entry the gate appended on the same call.
19527
+ */
19528
+ auditEntryIdForEvent(event) {
19529
+ return `${event.request_timestamp}:${event.operation}`;
19530
+ }
19531
+ derivePolicyRuleId(event) {
19532
+ return `tier${event.tier}:${event.operation}`;
19533
+ }
19534
+ deriveActionSummary(event) {
19535
+ return `${event.operation} (tier ${event.tier})`;
19536
+ }
19537
+ /**
19538
+ * Canonical SHA-256 of the request context. Sorted-keys serialization so
19539
+ * identical payloads always hash the same, even when key insertion order
19540
+ * varies. Defends against payload-replay smuggling (the aggregator can
19541
+ * tell the same payload was seen twice without storing it cleartext).
19542
+ */
19543
+ hashPayload(payload) {
19544
+ const canonical = JSON.stringify(payload, Object.keys(payload).sort());
19545
+ return createHash("sha256").update(canonical).digest("hex");
19546
+ }
19547
+ emit(event) {
19548
+ for (const listener of this.listeners) {
19549
+ try {
19550
+ listener(event);
19551
+ } catch {
19552
+ }
19553
+ }
19554
+ }
19555
+ async expireStale() {
19556
+ const nowMs = this.now().getTime();
19557
+ for (const entry of this.entries.values()) {
19558
+ if (entry.status !== "pending") continue;
19559
+ if (Date.parse(entry.expires_at) > nowMs) continue;
19560
+ entry.status = "expired";
19561
+ entry.resolved_at = this.now().toISOString();
19562
+ entry.resolved_by = "system_ttl";
19563
+ await this.persist(entry);
19564
+ this.auditLog.append(
19565
+ "l2",
19566
+ APPROVAL_AGGREGATOR_AUDIT_OPS.RESOLVED,
19567
+ this.identityId,
19568
+ {
19569
+ aggregator_id: entry.aggregator_id,
19570
+ source_harness: entry.source_harness,
19571
+ source_agent_id: entry.source_agent_id,
19572
+ audit_log_entry_id: entry.audit_log_entry_id,
19573
+ policy_rule_id: entry.policy_rule_id,
19574
+ decision: "expired",
19575
+ decided_by: "system_ttl",
19576
+ decided_at: entry.resolved_at
19577
+ }
19578
+ );
19579
+ this.emit({ type: "resolved", entry: { ...entry } });
19580
+ }
19581
+ }
19582
+ async persist(entry) {
19583
+ const serialized = stringToBytes(JSON.stringify(entry));
19584
+ const encrypted = encrypt(serialized, this.encryptionKey);
19585
+ await this.storage.write(
19586
+ APPROVAL_AGGREGATOR_NAMESPACE,
19587
+ entry.aggregator_id,
19588
+ stringToBytes(JSON.stringify(encrypted))
19589
+ );
19590
+ }
19591
+ async hydrate() {
19592
+ if (this.hydrated) return;
19593
+ this.hydrated = true;
19594
+ try {
19595
+ const metas = await this.storage.list(APPROVAL_AGGREGATOR_NAMESPACE);
19596
+ for (const meta of metas) {
19597
+ const raw = await this.storage.read(
19598
+ APPROVAL_AGGREGATOR_NAMESPACE,
19599
+ meta.key
19600
+ );
19601
+ if (!raw) continue;
19602
+ try {
19603
+ const encrypted = JSON.parse(bytesToString(raw));
19604
+ const decrypted = decrypt(encrypted, this.encryptionKey);
19605
+ const entry = JSON.parse(bytesToString(decrypted));
19606
+ this.entries.set(entry.aggregator_id, entry);
19607
+ const dedupKey = `${entry.source_harness}|${entry.source_agent_id}|${entry.audit_log_entry_id}`;
19608
+ this.dedupIndex.set(dedupKey, entry.aggregator_id);
19609
+ } catch {
19610
+ }
19611
+ }
19612
+ } catch {
19613
+ this.hydrated = false;
19614
+ }
19615
+ }
19616
+ };
19617
+
18829
19618
  // src/principal-policy/tools.ts
18830
19619
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
18831
19620
  return [
@@ -19366,7 +20155,11 @@ init_identity();
19366
20155
  init_encoding();
19367
20156
  init_random();
19368
20157
  function generateNonce() {
19369
- return toBase64url(randomBytes(32));
20158
+ const nonce = randomBytes(32);
20159
+ if (!nonce || nonce.length !== 32) {
20160
+ throw new Error("Nonce generation failed: randomBytes returned unexpected length");
20161
+ }
20162
+ return toBase64url(nonce);
19370
20163
  }
19371
20164
  function initiateHandshake(ourSHR) {
19372
20165
  const nonce = generateNonce();
@@ -19477,6 +20270,18 @@ function completeHandshake(response, session, identityManager, masterKey, identi
19477
20270
  return { completion, result };
19478
20271
  }
19479
20272
  function verifyCompletion(completion, session) {
20273
+ if (completion.protocol_version !== "1.0") {
20274
+ return {
20275
+ counterparty_id: "unknown",
20276
+ counterparty_shr: session.our_shr,
20277
+ verified: false,
20278
+ sovereignty_level: "unverified",
20279
+ trust_tier: "unverified",
20280
+ completed_at: completion.completed_at,
20281
+ expires_at: (/* @__PURE__ */ new Date()).toISOString(),
20282
+ errors: [`Unsupported protocol version: ${completion.protocol_version}`]
20283
+ };
20284
+ }
19480
20285
  const errors = [];
19481
20286
  if (!session.their_shr) {
19482
20287
  return {
@@ -21578,6 +22383,9 @@ Inspect the file and either correct the JSON or delete it manually before re-run
21578
22383
  this.cause = cause;
21579
22384
  this.name = "ResetHistoryMalformedError";
21580
22385
  }
22386
+ markerPath;
22387
+ lineNumber;
22388
+ cause;
21581
22389
  };
21582
22390
  function parseResetHistory(content, markerPath) {
21583
22391
  const markerHash = hashToString(stringToBytes(content));
@@ -21659,6 +22467,12 @@ function typed(markerPath, lineNumber, field, expected) {
21659
22467
  }
21660
22468
  async function consumeResetHistoryMarker(options) {
21661
22469
  const markerPath = join(options.storagePath, RESET_HISTORY_FILENAME);
22470
+ const consumedPath = markerPath + ".consumed";
22471
+ if (await fileExists3(consumedPath)) {
22472
+ await rm(markerPath, { force: true });
22473
+ await rm(consumedPath, { force: true });
22474
+ return { emitted: 0, markerPath };
22475
+ }
21662
22476
  if (!await fileExists3(markerPath)) {
21663
22477
  return { emitted: 0, markerPath };
21664
22478
  }
@@ -21683,7 +22497,9 @@ async function consumeResetHistoryMarker(options) {
21683
22497
  });
21684
22498
  }
21685
22499
  await options.auditLog.flush();
22500
+ await writeFile(consumedPath, "", "utf-8");
21686
22501
  await rm(markerPath, { force: true });
22502
+ await rm(consumedPath, { force: true });
21687
22503
  return { emitted: markers.length, markerHash, markerPath };
21688
22504
  }
21689
22505
  async function fileExists3(path) {
@@ -23819,7 +24635,7 @@ async function runOpenAIPrivacyFilter(text, config) {
23819
24635
  return parsed;
23820
24636
  }
23821
24637
  function runCommand(command, input, timeoutMs) {
23822
- return new Promise((resolve4, reject) => {
24638
+ return new Promise((resolve6, reject) => {
23823
24639
  const child = spawn(command, [], {
23824
24640
  stdio: ["pipe", "pipe", "pipe"],
23825
24641
  shell: false
@@ -23850,7 +24666,7 @@ function runCommand(command, input, timeoutMs) {
23850
24666
  ));
23851
24667
  return;
23852
24668
  }
23853
- resolve4(stdout);
24669
+ resolve6(stdout);
23854
24670
  });
23855
24671
  child.stdin.end(input);
23856
24672
  });
@@ -25671,13 +26487,13 @@ var ProxyRouter = class {
25671
26487
  * Call an upstream tool with a timeout.
25672
26488
  */
25673
26489
  async callWithTimeout(serverName, toolName, args, timeoutMs) {
25674
- return new Promise((resolve4, reject) => {
26490
+ return new Promise((resolve6, reject) => {
25675
26491
  const timer = setTimeout(() => {
25676
26492
  reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
25677
26493
  }, timeoutMs);
25678
26494
  this.clientManager.callTool(serverName, toolName, args).then((result) => {
25679
26495
  clearTimeout(timer);
25680
- resolve4(result);
26496
+ resolve6(result);
25681
26497
  }).catch((err) => {
25682
26498
  clearTimeout(timer);
25683
26499
  reject(err);
@@ -30629,6 +31445,36 @@ var HubService = class {
30629
31445
  const chat = this.requireOperatorChat();
30630
31446
  return chat.getConciergeHistory();
30631
31447
  }
31448
+ // ── Concierge memory threads (WP-V1.3-9 Tau-1) ─────────────────────
31449
+ /**
31450
+ * Whether the operator-chat service has the WP-V1.3-9 memory store
31451
+ * wired. Routes use this to 503 cleanly when the foundation memory
31452
+ * surface is unavailable on a given fortress.
31453
+ */
31454
+ hasConciergeMemory() {
31455
+ return Boolean(this.deps.operatorChat?.hasConciergeMemory());
31456
+ }
31457
+ async listConciergeMemoryThreads(opts) {
31458
+ const chat = this.requireOperatorChat();
31459
+ if (!chat.hasConciergeMemory()) {
31460
+ throw new HubCapabilityError("concierge_memory_not_wired");
31461
+ }
31462
+ return chat.listConciergeMemoryThreads(opts);
31463
+ }
31464
+ async readConciergeMemoryThread(threadId, opts) {
31465
+ const chat = this.requireOperatorChat();
31466
+ if (!chat.hasConciergeMemory()) {
31467
+ throw new HubCapabilityError("concierge_memory_not_wired");
31468
+ }
31469
+ return chat.readConciergeMemoryThread(threadId, opts);
31470
+ }
31471
+ async deleteConciergeMemoryThread(threadId) {
31472
+ const chat = this.requireOperatorChat();
31473
+ if (!chat.hasConciergeMemory()) {
31474
+ throw new HubCapabilityError("concierge_memory_not_wired");
31475
+ }
31476
+ return chat.deleteConciergeMemoryThread(threadId);
31477
+ }
30632
31478
  /**
30633
31479
  * Open the click-to-inspect/approve panel for a wrapped agent. The
30634
31480
  * panel surfaces recent activity routed through this agent, pending
@@ -30716,7 +31562,21 @@ init_encoding();
30716
31562
 
30717
31563
  // src/chat/operator-chat-audit-events.ts
30718
31564
  var OPERATOR_CHAT_OPS = {
30719
- CONCIERGE_CHAT: "operator_concierge_chat"};
31565
+ CONCIERGE_CHAT: "operator_concierge_chat",
31566
+ /**
31567
+ * Operator viewed concierge thread history (WP-V1.3-9 Tau-1). Emitted
31568
+ * when the operator hits the list-threads or read-thread route. Body
31569
+ * carries the thread_id (or `*` for the list endpoint) and a count;
31570
+ * raw turn content never crosses the audit surface.
31571
+ */
31572
+ CONCIERGE_HISTORY_READ: "operator_concierge_history_read",
31573
+ /**
31574
+ * Operator deleted a concierge thread (WP-V1.3-9 Tau-1). Emitted on
31575
+ * successful thread removal. Body carries thread_id + turn_count of
31576
+ * the deleted bundle.
31577
+ */
31578
+ CONCIERGE_THREAD_DELETED: "operator_concierge_thread_deleted"
31579
+ };
30720
31580
 
30721
31581
  // src/chat/operator-chat-types.ts
30722
31582
  var OPERATOR_CHAT_MAX_THREAD_LENGTH = 500;
@@ -30724,6 +31584,33 @@ var CONCIERGE_THREAD_KEY = "_fortress";
30724
31584
 
30725
31585
  // src/chat/operator-chat-service.ts
30726
31586
  var DEFAULT_CONCIERGE_MAX_TOKENS = 512;
31587
+ var SANCTUARY_DOMAIN_REFERENCE = `Castle Architecture (four enforcement layers):
31588
+ 1. Castle Wall: OS-boundary egress filter enforced at the kernel level. Blocks unauthorized outbound calls even from prompt-injected agents.
31589
+ 2. Sentinels: internal observation via process introspection. Surfaces anomalies to the operator; does not enforce.
31590
+ 3. Charter (Cooperative MCP): the sovereignty surface for compliant agents. Policy gates, approval tiers, audit logging, and encrypted state all live here.
31591
+ 4. Heralds: Concordia receipts and Verascore reputation. Cross-fortress accountability after an action completes.
31592
+
31593
+ Five channel templates (canonical names):
31594
+ - request-approve-act: agent proposes an action, operator approves or denies before execution.
31595
+ - read-then-report: agent reads outputs from a data source and reports summaries to the operator.
31596
+ - scheduled-digest: agent runs on a schedule and delivers a periodic digest.
31597
+ - plan-draft-only: agent drafts plans; operator reviews before any execution step.
31598
+ - fortress-relay: agent relays messages between fortresses under operator-scoped policy.
31599
+
31600
+ Four canonical policy slots:
31601
+ - memory: governs what the agent may persist and retrieve from encrypted state.
31602
+ - credentials: governs access to secrets, API keys, and tokens held in the broker.
31603
+ - plans: governs the agent's ability to create, modify, or execute plans.
31604
+ - outputs: governs what the agent may emit to external surfaces (files, APIs, messages).
31605
+
31606
+ Key concepts:
31607
+ - Fortress: the operator-owned sovereignty harness. All state is encrypted at rest under the cocoon.
31608
+ - Cocoon: master-key-wrapped storage derived from the operator's passphrase via Argon2id.
31609
+ - Identity: Ed25519 keypair with a DID, owned by the operator. Private keys never leave the cocoon.
31610
+ - Audit log: append-only encrypted blobs, sequential, recording every gate decision and tool call.
31611
+ - Wrapped agent: any agent runtime that connects to Sanctuary as an MCP client. Tier A (native), Tier B (adapter-wrapped), Tier C (escape hatch).
31612
+
31613
+ Note: this is a static reference block (v1.2.x). Dynamic context injection (live template list, policy schema) ships in v1.3.`;
30727
31614
  var OperatorChatService = class {
30728
31615
  store;
30729
31616
  auditLog;
@@ -30732,6 +31619,14 @@ var OperatorChatService = class {
30732
31619
  contextProviders;
30733
31620
  piiFilter;
30734
31621
  conciergeMaxTokens;
31622
+ memory;
31623
+ /**
31624
+ * In-memory thread_id assigned to the active concierge session.
31625
+ * The first sendConcierge call after construction allocates a fresh
31626
+ * UUID; subsequent calls reuse it so multi-turn coherence (Tau-2)
31627
+ * folds the prior turns into context. Cleared by `resetConciergeMemoryThread`.
31628
+ */
31629
+ activeMemoryThreadId;
30735
31630
  constructor(deps) {
30736
31631
  this.store = deps.store;
30737
31632
  this.auditLog = deps.auditLog;
@@ -30742,6 +31637,7 @@ var OperatorChatService = class {
30742
31637
  }
30743
31638
  if (deps.conciergePiiFilter) this.piiFilter = deps.conciergePiiFilter;
30744
31639
  this.conciergeMaxTokens = deps.conciergeMaxTokens ?? DEFAULT_CONCIERGE_MAX_TOKENS;
31640
+ if (deps.conciergeMemory) this.memory = deps.conciergeMemory;
30745
31641
  }
30746
31642
  // ── Concierge ─────────────────────────────────────────────────────────
30747
31643
  /**
@@ -30772,6 +31668,11 @@ var OperatorChatService = class {
30772
31668
  CONCIERGE_THREAD_KEY,
30773
31669
  operatorMessage
30774
31670
  );
31671
+ if (this.memory) {
31672
+ const threadId = this.ensureActiveMemoryThread();
31673
+ await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
31674
+ });
31675
+ }
30775
31676
  const start = Date.now();
30776
31677
  let conciergeBody;
30777
31678
  let servedBy = "disabled";
@@ -30827,6 +31728,11 @@ var OperatorChatService = class {
30827
31728
  CONCIERGE_THREAD_KEY,
30828
31729
  responseMessage
30829
31730
  );
31731
+ if (this.memory) {
31732
+ const threadId = this.ensureActiveMemoryThread();
31733
+ await this.memory.appendTurn(threadId, "assistant", conciergeBody).catch(() => {
31734
+ });
31735
+ }
30830
31736
  const payload = {
30831
31737
  version: "1.2",
30832
31738
  event_id: makeEventId("conc"),
@@ -30859,6 +31765,105 @@ var OperatorChatService = class {
30859
31765
  );
30860
31766
  return thread ? thread.messages : [];
30861
31767
  }
31768
+ // ── WP-V1.3-9 Tau-1 memory accessors ─────────────────────────────────
31769
+ /**
31770
+ * Whether the foundation memory store is wired. Routes use this to
31771
+ * 503 cleanly when called against an unwired service.
31772
+ */
31773
+ hasConciergeMemory() {
31774
+ return this.memory !== void 0;
31775
+ }
31776
+ /**
31777
+ * List concierge memory threads, newest-first. Emits the
31778
+ * `operator_concierge_history_read` audit event with `thread_id="*"`.
31779
+ */
31780
+ async listConciergeMemoryThreads(opts) {
31781
+ if (!this.memory) {
31782
+ throw new Error("concierge memory store not configured");
31783
+ }
31784
+ const summaries = await this.memory.listThreads(opts);
31785
+ const totalTurns = summaries.reduce((acc, s) => acc + s.turn_count, 0);
31786
+ const payload = {
31787
+ version: "1.2",
31788
+ event_id: makeEventId("conc-hist"),
31789
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
31790
+ identity_id: this.identityId,
31791
+ kind: "operator_concierge_history_read",
31792
+ surface: "concierge",
31793
+ thread_id: "*",
31794
+ turn_count: totalTurns
31795
+ };
31796
+ this.emit(OPERATOR_CHAT_OPS.CONCIERGE_HISTORY_READ, payload, "success");
31797
+ return summaries;
31798
+ }
31799
+ /**
31800
+ * Read a concierge memory thread, oldest turn first. Emits the
31801
+ * `operator_concierge_history_read` audit event with the named
31802
+ * thread_id and the count of turns surfaced.
31803
+ */
31804
+ async readConciergeMemoryThread(threadId, opts) {
31805
+ if (!this.memory) {
31806
+ throw new Error("concierge memory store not configured");
31807
+ }
31808
+ const turns = await this.memory.readThread(threadId, opts);
31809
+ const payload = {
31810
+ version: "1.2",
31811
+ event_id: makeEventId("conc-hist"),
31812
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
31813
+ identity_id: this.identityId,
31814
+ kind: "operator_concierge_history_read",
31815
+ surface: "concierge",
31816
+ thread_id: threadId,
31817
+ turn_count: turns.length
31818
+ };
31819
+ this.emit(OPERATOR_CHAT_OPS.CONCIERGE_HISTORY_READ, payload, "success");
31820
+ return turns;
31821
+ }
31822
+ /**
31823
+ * Delete a concierge memory thread. Emits
31824
+ * `operator_concierge_thread_deleted` only when a bundle was actually
31825
+ * removed; absent threads return false without an audit event.
31826
+ */
31827
+ async deleteConciergeMemoryThread(threadId) {
31828
+ if (!this.memory) {
31829
+ throw new Error("concierge memory store not configured");
31830
+ }
31831
+ const turnsBefore = await this.memory.readThread(threadId);
31832
+ if (turnsBefore.length === 0) {
31833
+ return await this.memory.deleteThread(threadId);
31834
+ }
31835
+ const removed = await this.memory.deleteThread(threadId);
31836
+ if (!removed) return false;
31837
+ if (this.activeMemoryThreadId === threadId) {
31838
+ this.activeMemoryThreadId = void 0;
31839
+ }
31840
+ const payload = {
31841
+ version: "1.2",
31842
+ event_id: makeEventId("conc-del"),
31843
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
31844
+ identity_id: this.identityId,
31845
+ kind: "operator_concierge_thread_deleted",
31846
+ surface: "concierge",
31847
+ thread_id: threadId,
31848
+ turn_count: turnsBefore.length
31849
+ };
31850
+ this.emit(OPERATOR_CHAT_OPS.CONCIERGE_THREAD_DELETED, payload, "success");
31851
+ return true;
31852
+ }
31853
+ /**
31854
+ * Reset the active session memory thread. Subsequent sendConcierge
31855
+ * calls allocate a fresh thread_id. Surfaced for tests + future "new
31856
+ * conversation" affordance; not currently called by the dashboard.
31857
+ */
31858
+ resetConciergeMemoryThread() {
31859
+ this.activeMemoryThreadId = void 0;
31860
+ }
31861
+ ensureActiveMemoryThread() {
31862
+ if (!this.activeMemoryThreadId) {
31863
+ this.activeMemoryThreadId = randomUUID();
31864
+ }
31865
+ return this.activeMemoryThreadId;
31866
+ }
30862
31867
  /**
30863
31868
  * Stitch fortress state into a single context blob the substrate
30864
31869
  * folds into its summarization prompt.
@@ -30868,6 +31873,9 @@ var OperatorChatService = class {
30868
31873
  * than nested structures. Format:
30869
31874
  *
30870
31875
  * ```
31876
+ * ## Sanctuary reference
31877
+ * <static domain reference block>
31878
+ *
30871
31879
  * ## Recent activity
30872
31880
  * <recentActivity output>
30873
31881
  *
@@ -30879,15 +31887,28 @@ var OperatorChatService = class {
30879
31887
  * ```
30880
31888
  */
30881
31889
  async assembleConciergeContext() {
31890
+ const ref = `## Sanctuary reference
31891
+ ${SANCTUARY_DOMAIN_REFERENCE}`;
30882
31892
  if (!this.contextProviders) {
30883
- return "## Recent activity\n(no providers wired)\n\n## Wrapped agents\n(no providers wired)\n\n## Open inbox\n(no providers wired)";
31893
+ return `${ref}
31894
+
31895
+ ## Recent activity
31896
+ (no providers wired)
31897
+
31898
+ ## Wrapped agents
31899
+ (no providers wired)
31900
+
31901
+ ## Open inbox
31902
+ (no providers wired)`;
30884
31903
  }
30885
31904
  const [activity, agents, inbox] = await Promise.all([
30886
31905
  this.contextProviders.recentActivity(),
30887
31906
  this.contextProviders.agentInventory(),
30888
31907
  this.contextProviders.openInbox()
30889
31908
  ]);
30890
- return `## Recent activity
31909
+ return `${ref}
31910
+
31911
+ ## Recent activity
30891
31912
  ${activity}
30892
31913
 
30893
31914
  ## Wrapped agents
@@ -31006,6 +32027,238 @@ var OperatorChatStore = class {
31006
32027
  }
31007
32028
  };
31008
32029
 
32030
+ // src/chat/concierge-memory-store.ts
32031
+ init_encryption();
32032
+ init_encoding();
32033
+ var CONCIERGE_MEMORY_NAMESPACE = "_chat";
32034
+ var CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
32035
+ var HKDF_INFO2 = "concierge-memory-store-v1";
32036
+ var DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
32037
+ var MAX_BUNDLE_BYTES2 = 4 * 1024 * 1024;
32038
+ var ConciergeMemoryStore = class {
32039
+ storage;
32040
+ encryptionKey;
32041
+ fortressId;
32042
+ retentionDays;
32043
+ locks;
32044
+ constructor(opts) {
32045
+ this.storage = opts.storage;
32046
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO2);
32047
+ this.fortressId = opts.fortressId;
32048
+ this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
32049
+ this.locks = /* @__PURE__ */ new Map();
32050
+ }
32051
+ /**
32052
+ * Append a turn to the named thread, creating the bundle if no record
32053
+ * exists. Returns the persisted turn (with assigned turn_id +
32054
+ * retention_until). Per-thread serialisation guarantees turn_id
32055
+ * monotonicity even under concurrent callers.
32056
+ */
32057
+ async appendTurn(threadId, role, content) {
32058
+ return this.withLock(threadId, async () => {
32059
+ const bundle = await this.loadBundle(threadId) ?? null;
32060
+ const now = /* @__PURE__ */ new Date();
32061
+ const retentionMs = this.retentionDays * 24 * 60 * 60 * 1e3;
32062
+ const retentionUntil = new Date(now.getTime() + retentionMs);
32063
+ const nextTurnId = bundle ? lastTurnId(bundle) + 1 : 1;
32064
+ const turn = {
32065
+ thread_id: threadId,
32066
+ fortress_id: this.fortressId,
32067
+ turn_id: nextTurnId,
32068
+ role,
32069
+ content,
32070
+ created_at: now.toISOString(),
32071
+ retention_until: retentionUntil.toISOString()
32072
+ };
32073
+ const next = bundle ? { ...bundle, turns: [...bundle.turns, turn] } : {
32074
+ version: 1,
32075
+ thread_id: threadId,
32076
+ fortress_id: this.fortressId,
32077
+ created_at: now.toISOString(),
32078
+ turns: [turn]
32079
+ };
32080
+ await this.saveBundle(next);
32081
+ return turn;
32082
+ });
32083
+ }
32084
+ /**
32085
+ * Read turns from a thread, oldest-first. Returns an empty array if
32086
+ * the thread does not exist or its bundle is corrupt. Does not emit
32087
+ * audit events; the caller (HTTP route handler) owns audit semantics.
32088
+ */
32089
+ async readThread(threadId, opts) {
32090
+ const bundle = await this.loadBundle(threadId);
32091
+ if (!bundle) return [];
32092
+ let turns = bundle.turns;
32093
+ if (opts?.sinceTurnId !== void 0) {
32094
+ const cutoff = opts.sinceTurnId;
32095
+ turns = turns.filter((t) => t.turn_id > cutoff);
32096
+ }
32097
+ if (opts?.limit !== void 0) {
32098
+ turns = turns.slice(0, opts.limit);
32099
+ }
32100
+ return turns;
32101
+ }
32102
+ /**
32103
+ * Enumerate concierge threads in this fortress with summary metadata.
32104
+ * Sorted newest-first by last_turn_at.
32105
+ */
32106
+ async listThreads(opts) {
32107
+ const entries = await this.storage.list(
32108
+ CONCIERGE_MEMORY_NAMESPACE,
32109
+ CONCIERGE_MEMORY_KEY_PREFIX
32110
+ );
32111
+ const summaries = [];
32112
+ for (const meta of entries) {
32113
+ const threadId = stripKeyPrefix(meta.key);
32114
+ if (threadId === null) continue;
32115
+ const bundle = await this.loadBundle(threadId);
32116
+ if (!bundle || bundle.turns.length === 0) continue;
32117
+ const last = bundle.turns[bundle.turns.length - 1];
32118
+ summaries.push({
32119
+ thread_id: bundle.thread_id,
32120
+ created_at: bundle.created_at,
32121
+ last_turn_at: last ? last.created_at : bundle.created_at,
32122
+ turn_count: bundle.turns.length
32123
+ });
32124
+ }
32125
+ summaries.sort(
32126
+ (a, b) => a.last_turn_at < b.last_turn_at ? 1 : a.last_turn_at > b.last_turn_at ? -1 : 0
32127
+ );
32128
+ if (opts?.limit !== void 0) {
32129
+ return summaries.slice(0, opts.limit);
32130
+ }
32131
+ return summaries;
32132
+ }
32133
+ /**
32134
+ * Delete a thread's bundle. Returns true if the bundle existed and
32135
+ * was removed; false if no bundle was present. Audit emission is the
32136
+ * caller's responsibility.
32137
+ */
32138
+ async deleteThread(threadId) {
32139
+ const key = bundleKey(threadId);
32140
+ return this.withLock(threadId, async () => {
32141
+ const existed = await this.storage.exists(
32142
+ CONCIERGE_MEMORY_NAMESPACE,
32143
+ key
32144
+ );
32145
+ if (!existed) return false;
32146
+ try {
32147
+ await this.storage.delete(CONCIERGE_MEMORY_NAMESPACE, key);
32148
+ } catch {
32149
+ return false;
32150
+ }
32151
+ return true;
32152
+ });
32153
+ }
32154
+ /**
32155
+ * Drop expired turns across all threads. Threads emptied by pruning
32156
+ * are removed entirely. Returns the count of turns pruned.
32157
+ */
32158
+ async pruneExpired(now) {
32159
+ const cutoff = (now ?? /* @__PURE__ */ new Date()).toISOString();
32160
+ const entries = await this.storage.list(
32161
+ CONCIERGE_MEMORY_NAMESPACE,
32162
+ CONCIERGE_MEMORY_KEY_PREFIX
32163
+ );
32164
+ let pruned = 0;
32165
+ for (const meta of entries) {
32166
+ const threadId = stripKeyPrefix(meta.key);
32167
+ if (threadId === null) continue;
32168
+ pruned += await this.withLock(threadId, async () => {
32169
+ const bundle = await this.loadBundle(threadId);
32170
+ if (!bundle) return 0;
32171
+ const kept = bundle.turns.filter((t) => t.retention_until > cutoff);
32172
+ const dropped = bundle.turns.length - kept.length;
32173
+ if (dropped === 0) return 0;
32174
+ if (kept.length === 0) {
32175
+ await this.storage.delete(
32176
+ CONCIERGE_MEMORY_NAMESPACE,
32177
+ bundleKey(threadId)
32178
+ );
32179
+ } else {
32180
+ await this.saveBundle({ ...bundle, turns: kept });
32181
+ }
32182
+ return dropped;
32183
+ });
32184
+ }
32185
+ return { pruned };
32186
+ }
32187
+ // ── internals ────────────────────────────────────────────────────────
32188
+ async loadBundle(threadId) {
32189
+ const key = bundleKey(threadId);
32190
+ let raw;
32191
+ try {
32192
+ raw = await this.storage.read(CONCIERGE_MEMORY_NAMESPACE, key);
32193
+ } catch {
32194
+ return null;
32195
+ }
32196
+ if (!raw) return null;
32197
+ if (raw.length > MAX_BUNDLE_BYTES2) return null;
32198
+ try {
32199
+ const envelope = JSON.parse(bytesToString(raw));
32200
+ const aad = stringToBytes(threadId);
32201
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
32202
+ const parsed = JSON.parse(
32203
+ bytesToString(plaintext)
32204
+ );
32205
+ if (parsed.version !== 1) return null;
32206
+ if (parsed.thread_id !== threadId) return null;
32207
+ return parsed;
32208
+ } catch {
32209
+ return null;
32210
+ }
32211
+ }
32212
+ async saveBundle(bundle) {
32213
+ const key = bundleKey(bundle.thread_id);
32214
+ const aad = stringToBytes(bundle.thread_id);
32215
+ const plaintext = stringToBytes(JSON.stringify(bundle));
32216
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
32217
+ await this.storage.write(
32218
+ CONCIERGE_MEMORY_NAMESPACE,
32219
+ key,
32220
+ stringToBytes(JSON.stringify(envelope))
32221
+ );
32222
+ }
32223
+ /**
32224
+ * Run `task` while holding the per-thread async lock. Lock is released
32225
+ * once the task settles (success or failure). Generic helper so
32226
+ * appendTurn / deleteThread / pruneExpired share serialisation.
32227
+ */
32228
+ async withLock(threadId, task) {
32229
+ const previous = this.locks.get(threadId) ?? Promise.resolve();
32230
+ let release;
32231
+ const next = new Promise((resolve6) => {
32232
+ release = resolve6;
32233
+ });
32234
+ const chained = previous.then(() => next);
32235
+ this.locks.set(threadId, chained);
32236
+ try {
32237
+ await previous;
32238
+ return await task();
32239
+ } finally {
32240
+ release();
32241
+ if (this.locks.get(threadId) === chained) {
32242
+ this.locks.delete(threadId);
32243
+ }
32244
+ }
32245
+ }
32246
+ };
32247
+ function bundleKey(threadId) {
32248
+ return `${CONCIERGE_MEMORY_KEY_PREFIX}${threadId}`;
32249
+ }
32250
+ function stripKeyPrefix(key) {
32251
+ if (!key.startsWith(CONCIERGE_MEMORY_KEY_PREFIX)) return null;
32252
+ return key.slice(CONCIERGE_MEMORY_KEY_PREFIX.length);
32253
+ }
32254
+ function lastTurnId(bundle) {
32255
+ let max = 0;
32256
+ for (const t of bundle.turns) {
32257
+ if (t.turn_id > max) max = t.turn_id;
32258
+ }
32259
+ return max;
32260
+ }
32261
+
31009
32262
  // src/dashboard/v1_1/wiring.ts
31010
32263
  var CapabilityErrorAgentController = class {
31011
32264
  fail(action) {
@@ -31044,6 +32297,14 @@ function buildV11Bindings(inputs) {
31044
32297
  let operatorChatService;
31045
32298
  if (inputs.storage && inputs.masterKey) {
31046
32299
  const chatStore = new OperatorChatStore(inputs.storage, inputs.masterKey);
32300
+ const conciergeMemory = new ConciergeMemoryStore({
32301
+ storage: inputs.storage,
32302
+ masterKey: inputs.masterKey,
32303
+ fortressId: inputs.fortressId,
32304
+ ...inputs.conciergeMemoryRetentionDays !== void 0 ? { retentionDays: inputs.conciergeMemoryRetentionDays } : {}
32305
+ });
32306
+ void conciergeMemory.pruneExpired().catch(() => {
32307
+ });
31047
32308
  operatorChatService = new OperatorChatService({
31048
32309
  store: chatStore,
31049
32310
  auditLog: inputs.auditLog,
@@ -31054,7 +32315,8 @@ function buildV11Bindings(inputs) {
31054
32315
  identityId: inputs.identityId,
31055
32316
  registry
31056
32317
  }),
31057
- conciergePiiFilter: buildConciergePiiFilter()
32318
+ conciergePiiFilter: buildConciergePiiFilter(),
32319
+ conciergeMemory
31058
32320
  });
31059
32321
  }
31060
32322
  const hubService = new HubService({
@@ -31246,13 +32508,13 @@ init_encryption();
31246
32508
  init_encoding();
31247
32509
  var INTELLIGENCE_NAMESPACE = "_intelligence";
31248
32510
  var SUBSTRATE_CONFIG_KEY = "substrate-config";
31249
- var HKDF_INFO2 = "intelligence-substrate-config";
32511
+ var HKDF_INFO3 = "intelligence-substrate-config";
31250
32512
  var IntelligenceConfigStore = class {
31251
32513
  storage;
31252
32514
  encryptionKey;
31253
32515
  constructor(storage, masterKey) {
31254
32516
  this.storage = storage;
31255
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
32517
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
31256
32518
  }
31257
32519
  /**
31258
32520
  * Load the operator's substrate config from disk. Returns the config
@@ -33391,7 +34653,9 @@ async function verifyExitBundle(bundleDir, options = {}) {
33391
34653
  );
33392
34654
  }
33393
34655
  }
33394
- const reputationFailed = reputation?.bundle_signature_valid === false || (reputation?.invalid_attestations ?? 0) > 0;
34656
+ const reputationBundleFailed = reputation?.bundle_signature_valid === false;
34657
+ const reputationAttestationFailed = (reputation?.invalid_attestations ?? 0) > 0;
34658
+ const reputationFailed = reputationBundleFailed || reputationAttestationFailed;
33395
34659
  const identityFailed = identity ? !identity.signature_valid : false;
33396
34660
  const unverifiableCount = reputation?.unverifiable_attestations ?? 0;
33397
34661
  const unverifiableFailed = unverifiableCount > 0 && !options.acceptUnverifiableAttestations;
@@ -33400,6 +34664,16 @@ async function verifyExitBundle(bundleDir, options = {}) {
33400
34664
  `${unverifiableCount} reputation attestation(s) have unknown signer public keys; pass --accept-unverifiable-attestations to import anyway`
33401
34665
  );
33402
34666
  }
34667
+ let detailedFailureClass;
34668
+ if (identityFailed) {
34669
+ detailedFailureClass = "identity_signature_invalid";
34670
+ } else if (reputationBundleFailed) {
34671
+ detailedFailureClass = "reputation_bundle_signature_invalid";
34672
+ } else if (reputationAttestationFailed) {
34673
+ detailedFailureClass = "reputation_attestation_signature_invalid";
34674
+ } else if (unverifiableFailed) {
34675
+ detailedFailureClass = "reputation_unverifiable_attestations";
34676
+ }
33403
34677
  return {
33404
34678
  version: "1.1",
33405
34679
  passed: !reputationFailed && !identityFailed && !unverifiableFailed,
@@ -33419,7 +34693,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
33419
34693
  identity,
33420
34694
  audit,
33421
34695
  reputation,
33422
- failure_class: reputationFailed || identityFailed || unverifiableFailed ? "other" : void 0
34696
+ failure_class: detailedFailureClass
33423
34697
  };
33424
34698
  }
33425
34699
 
@@ -33992,6 +35266,9 @@ async function importExitBundle(opts) {
33992
35266
  reputationArtifact?.json ?? null,
33993
35267
  manifest
33994
35268
  );
35269
+ if (!conflicts.public_identity_exists && identityArtifact?.json && opts.identityManager.getPrimaryIdentityId() !== null && opts.identityManager.getPrimaryIdentityId() !== identityArtifact.json.bundle.identity_id) {
35270
+ conflicts.public_identity_exists = true;
35271
+ }
33995
35272
  if (!opts.activate) {
33996
35273
  return {
33997
35274
  verified: true,
@@ -34018,7 +35295,7 @@ async function importExitBundle(opts) {
34018
35295
  if (conflicts.public_identity_exists && !opts.forceRebind) {
34019
35296
  throw new ExitBundleImportError(
34020
35297
  "IDENTITY_OVERWRITE_REFUSED",
34021
- "Importing this bundle would overwrite an existing fortress public identity. Pass forceRebind: true (CLI: --force-rebind) to confirm explicit replacement."
35298
+ "Importing this exit bundle would overwrite an existing fortress public identity (either the same identity already imported, or a different identity is currently active). Pass forceRebind: true (CLI: --force-rebind) to confirm explicit replacement."
34022
35299
  );
34023
35300
  }
34024
35301
  if (conflicts.public_identity_exists && opts.forceRebind && identityArtifact) {
@@ -34343,7 +35620,19 @@ async function runExitCommand(args) {
34343
35620
  }
34344
35621
  const config = await loadConfig();
34345
35622
  const ctx = await openExitContext(argv, env);
34346
- const policy = await loadPrincipalPolicy(ctx.storagePath);
35623
+ let policy;
35624
+ try {
35625
+ policy = await loadPrincipalPolicy(ctx.storagePath);
35626
+ } catch (policyErr) {
35627
+ if (policyErr instanceof MalformedPrincipalPolicyError) {
35628
+ write(err, `
35629
+ Sanctuary cannot proceed.
35630
+ ${policyErr.message}
35631
+ `);
35632
+ return 1;
35633
+ }
35634
+ throw policyErr;
35635
+ }
34347
35636
  const result = await exportExitBundle({
34348
35637
  bundleDir: outDir,
34349
35638
  storage: ctx.storage,
@@ -34376,6 +35665,26 @@ async function runExitCommand(args) {
34376
35665
  write(err, "Usage: sanctuary exit import <dir> [--activate]\n");
34377
35666
  return 2;
34378
35667
  }
35668
+ const bundleRoot = resolve(dir);
35669
+ try {
35670
+ await access(bundleRoot);
35671
+ } catch {
35672
+ write(err, `Error: bundle directory not found: ${bundleRoot}
35673
+ `);
35674
+ return 1;
35675
+ }
35676
+ const manifestPath = join(bundleRoot, "manifest.json");
35677
+ try {
35678
+ const raw = await readFile(manifestPath, "utf8");
35679
+ JSON.parse(raw);
35680
+ } catch {
35681
+ write(
35682
+ err,
35683
+ `Error: bundle manifest missing or malformed at ${manifestPath}
35684
+ `
35685
+ );
35686
+ return 1;
35687
+ }
34379
35688
  const activate = hasFlag(argv, "--activate");
34380
35689
  const forceRebind = hasFlag(argv, "--force-rebind");
34381
35690
  const acceptUnverifiableAttestations = hasFlag(
@@ -34516,11 +35825,11 @@ async function startDashboardServer(options) {
34516
35825
  }
34517
35826
  }
34518
35827
  });
34519
- await new Promise((resolve4, reject) => {
35828
+ await new Promise((resolve6, reject) => {
34520
35829
  server.once("error", reject);
34521
35830
  server.listen(port, host, () => {
34522
35831
  server.off("error", reject);
34523
- resolve4();
35832
+ resolve6();
34524
35833
  });
34525
35834
  });
34526
35835
  const actualPort = (() => {
@@ -34533,8 +35842,8 @@ async function startDashboardServer(options) {
34533
35842
  url,
34534
35843
  port: actualPort,
34535
35844
  host,
34536
- stop: () => new Promise((resolve4, reject) => {
34537
- server.close((err) => err ? reject(err) : resolve4());
35845
+ stop: () => new Promise((resolve6, reject) => {
35846
+ server.close((err) => err ? reject(err) : resolve6());
34538
35847
  }),
34539
35848
  publish,
34540
35849
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
@@ -34975,7 +36284,19 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
34975
36284
  const profileStore = new SovereigntyProfileStore(storage, masterKey);
34976
36285
  await profileStore.load();
34977
36286
  const { tools: profileTools } = createSovereigntyProfileTools(profileStore, auditLog);
34978
- const policy = await loadPrincipalPolicy(config.storage_path);
36287
+ let policy;
36288
+ try {
36289
+ policy = await loadPrincipalPolicy(config.storage_path);
36290
+ } catch (err) {
36291
+ if (err instanceof MalformedPrincipalPolicyError) {
36292
+ console.error(`
36293
+ Sanctuary cannot start.
36294
+ ${err.message}
36295
+ `);
36296
+ process.exit(1);
36297
+ }
36298
+ throw err;
36299
+ }
34979
36300
  const baseline = new BaselineTracker(storage, masterKey);
34980
36301
  await baseline.load();
34981
36302
  let approvalChannel;
@@ -35073,6 +36394,21 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35073
36394
  });
35074
36395
  } : void 0;
35075
36396
  const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog, injectionDetector, onInjectionAlert);
36397
+ const fortressIdForAggregator = fortressIdFromStoragePath(config.storage_path);
36398
+ const aggregatorIdentityId = identityManager.getPrimaryIdentityId() ?? `fortress:${config.storage_path}`;
36399
+ const approvalAggregator = new ApprovalAggregator({
36400
+ storage,
36401
+ masterKey,
36402
+ auditLog,
36403
+ identityId: aggregatorIdentityId,
36404
+ fortressId: fortressIdForAggregator
36405
+ });
36406
+ gate.setApprovalEventCallback((event) => {
36407
+ void approvalAggregator.ingest(event);
36408
+ });
36409
+ if (dashboard) {
36410
+ dashboard.setApprovalAggregator(approvalAggregator);
36411
+ }
35076
36412
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
35077
36413
  const { tools: sanctuaryMetaTools } = createSanctuaryTools({
35078
36414
  config,
@@ -35192,7 +36528,7 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35192
36528
  clientManager.configure(enabledServers).catch((err) => {
35193
36529
  console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
35194
36530
  });
35195
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
36531
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
35196
36532
  const proxiedTools = proxyRouter.getProxiedTools();
35197
36533
  if (proxiedTools.length > 0) {
35198
36534
  allTools.push(...proxiedTools);
@@ -35248,6 +36584,6 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35248
36584
  };
35249
36585
  }
35250
36586
 
35251
- export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, ExitBundleImportError, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize2 as canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate2 as getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
36587
+ export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, ExitBundleImportError, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MalformedPrincipalPolicyError, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize2 as canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate2 as getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
35252
36588
  //# sourceMappingURL=index.js.map
35253
36589
  //# sourceMappingURL=index.js.map