@sanctuary-framework/mcp-server 1.2.3 → 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
@@ -4582,20 +4582,52 @@ approval_channel:
4582
4582
  timeout_seconds: 300
4583
4583
  `;
4584
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
+ };
4585
4599
  async function loadPrincipalPolicy(storagePath) {
4586
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
+ }
4587
4623
  try {
4588
- const content = await readFile(policyPath, "utf-8");
4589
4624
  const policy = parsePolicy(content);
4590
4625
  return Object.freeze(policy);
4591
- } catch {
4592
- const defaultYaml = generateDefaultPolicyYaml();
4593
- try {
4594
- await writeFile(policyPath, defaultYaml, "utf-8");
4595
- await chmod(policyPath, 384);
4596
- } catch {
4597
- }
4598
- return Object.freeze({ ...DEFAULT_POLICY });
4626
+ } catch (parseErr) {
4627
+ throw new MalformedPrincipalPolicyError(
4628
+ policyPath,
4629
+ parseErr.message
4630
+ );
4599
4631
  }
4600
4632
  }
4601
4633
 
@@ -4822,7 +4854,7 @@ function deepSortKeys(obj) {
4822
4854
  return sorted;
4823
4855
  }
4824
4856
  function canonicalizeForSigning(body) {
4825
- return JSON.stringify(deepSortKeys(body));
4857
+ return JSON.stringify(deepSortKeys(body)).normalize("NFC");
4826
4858
  }
4827
4859
 
4828
4860
  // src/shr/generator.ts
@@ -11644,6 +11676,16 @@ var HUB_ROUTES = {
11644
11676
  */
11645
11677
  CHAT_CONCIERGE_SEND: "/api/hub/chat/concierge",
11646
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",
11647
11689
  /**
11648
11690
  * Click-to-inspect panel (WP-V1.2 reshape). Returns the agent's
11649
11691
  * recent activity feed, pending Tier 1 approvals routed through this
@@ -11667,6 +11709,10 @@ var HUB_TIER_1_AGENT_CONTROL_ACTIONS = [
11667
11709
  ];
11668
11710
  var HUB_ACTIVITY_DEFAULT_LIMIT = 50;
11669
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;
11670
11716
  var HUB_INBOX_DEFAULT_LIMIT = 100;
11671
11717
  var HUB_INBOX_MAX_LIMIT = 500;
11672
11718
  var HUB_AGENTS_DEFAULT_LIMIT = 100;
@@ -11838,6 +11884,23 @@ function checkChatMessage(value) {
11838
11884
  }
11839
11885
  return trimmed;
11840
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
+ }
11841
11904
  function matchInboxRoute(path) {
11842
11905
  const prefix = `${HUB_API_PREFIX}/inbox/`;
11843
11906
  if (!path.startsWith(prefix)) return null;
@@ -12021,6 +12084,47 @@ async function handleHubRoute(deps, req, res) {
12021
12084
  writeJSON2(res, 200, { ok: true, data: { messages } });
12022
12085
  return true;
12023
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
+ }
12024
12128
  writeJSON2(res, 404, { ok: false, error: "not_found", path });
12025
12129
  return true;
12026
12130
  } catch (err) {
@@ -16070,6 +16174,162 @@ async function dispatchV11Request(inputs, req, res, url, method) {
16070
16174
  return false;
16071
16175
  }
16072
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
+
16073
16333
  // src/principal-policy/dashboard.ts
16074
16334
  var SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
16075
16335
  var SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
@@ -16134,6 +16394,14 @@ var DashboardApprovalChannel = class {
16134
16394
  * regardless. Default route flip is deferred to v1.2.
16135
16395
  */
16136
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;
16137
16405
  constructor(config) {
16138
16406
  this.config = config;
16139
16407
  this.authToken = config.auth_token;
@@ -16184,6 +16452,34 @@ var DashboardApprovalChannel = class {
16184
16452
  setV11Bindings(bindings) {
16185
16453
  this.v11Bindings = bindings;
16186
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
+ }
16187
16483
  /**
16188
16484
  * v1.1 dispatch entry point. Called from `handleRequest` before the
16189
16485
  * legacy route table. Returns true when the request was served by v1.1
@@ -16559,6 +16855,18 @@ var DashboardApprovalChannel = class {
16559
16855
  res.end();
16560
16856
  return;
16561
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
+ }
16562
16870
  if (this.v11Bindings) {
16563
16871
  this.dispatchV11(req, res, url, method).then((handled) => {
16564
16872
  if (handled) return;
@@ -18577,14 +18885,25 @@ var ApprovalGate = class {
18577
18885
  auditLog;
18578
18886
  injectionDetector;
18579
18887
  onInjectionAlert;
18888
+ onApprovalEvent;
18580
18889
  proxyTierResolver;
18581
- constructor(policy, baseline, channel, auditLog, injectionDetector, onInjectionAlert) {
18890
+ constructor(policy, baseline, channel, auditLog, injectionDetector, onInjectionAlert, onApprovalEvent) {
18582
18891
  this.policy = policy;
18583
18892
  this.baseline = baseline;
18584
18893
  this.channel = channel;
18585
18894
  this.auditLog = auditLog;
18586
18895
  this.injectionDetector = injectionDetector ?? new InjectionDetector();
18587
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;
18588
18907
  }
18589
18908
  /**
18590
18909
  * Set the proxy tier resolver. Called after the proxy router is initialized.
@@ -18818,21 +19137,105 @@ var ApprovalGate = class {
18818
19137
  }
18819
19138
  /**
18820
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.
18821
19147
  */
18822
19148
  async requestApproval(operation, tier, reason, context) {
19149
+ const requestTimestamp = (/* @__PURE__ */ new Date()).toISOString();
18823
19150
  const request = {
18824
19151
  operation,
18825
19152
  tier,
18826
19153
  reason,
18827
19154
  context,
18828
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
19155
+ timestamp: requestTimestamp
18829
19156
  };
18830
- 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
+ }
18831
19215
  this.auditLog.append("l2", `gate_${response.decision}:${operation}`, "system", {
18832
19216
  tier,
18833
19217
  reason,
18834
19218
  decided_by: response.decided_by
18835
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
+ }
18836
19239
  return {
18837
19240
  allowed: response.decision === "approve",
18838
19241
  tier,
@@ -18866,6 +19269,352 @@ var ApprovalGate = class {
18866
19269
  }
18867
19270
  };
18868
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
+
18869
19618
  // src/principal-policy/tools.ts
18870
19619
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
18871
19620
  return [
@@ -21718,6 +22467,12 @@ function typed(markerPath, lineNumber, field, expected) {
21718
22467
  }
21719
22468
  async function consumeResetHistoryMarker(options) {
21720
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
+ }
21721
22476
  if (!await fileExists3(markerPath)) {
21722
22477
  return { emitted: 0, markerPath };
21723
22478
  }
@@ -21742,7 +22497,9 @@ async function consumeResetHistoryMarker(options) {
21742
22497
  });
21743
22498
  }
21744
22499
  await options.auditLog.flush();
22500
+ await writeFile(consumedPath, "", "utf-8");
21745
22501
  await rm(markerPath, { force: true });
22502
+ await rm(consumedPath, { force: true });
21746
22503
  return { emitted: markers.length, markerHash, markerPath };
21747
22504
  }
21748
22505
  async function fileExists3(path) {
@@ -30688,6 +31445,36 @@ var HubService = class {
30688
31445
  const chat = this.requireOperatorChat();
30689
31446
  return chat.getConciergeHistory();
30690
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
+ }
30691
31478
  /**
30692
31479
  * Open the click-to-inspect/approve panel for a wrapped agent. The
30693
31480
  * panel surfaces recent activity routed through this agent, pending
@@ -30775,7 +31562,21 @@ init_encoding();
30775
31562
 
30776
31563
  // src/chat/operator-chat-audit-events.ts
30777
31564
  var OPERATOR_CHAT_OPS = {
30778
- 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
+ };
30779
31580
 
30780
31581
  // src/chat/operator-chat-types.ts
30781
31582
  var OPERATOR_CHAT_MAX_THREAD_LENGTH = 500;
@@ -30818,6 +31619,14 @@ var OperatorChatService = class {
30818
31619
  contextProviders;
30819
31620
  piiFilter;
30820
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;
30821
31630
  constructor(deps) {
30822
31631
  this.store = deps.store;
30823
31632
  this.auditLog = deps.auditLog;
@@ -30828,6 +31637,7 @@ var OperatorChatService = class {
30828
31637
  }
30829
31638
  if (deps.conciergePiiFilter) this.piiFilter = deps.conciergePiiFilter;
30830
31639
  this.conciergeMaxTokens = deps.conciergeMaxTokens ?? DEFAULT_CONCIERGE_MAX_TOKENS;
31640
+ if (deps.conciergeMemory) this.memory = deps.conciergeMemory;
30831
31641
  }
30832
31642
  // ── Concierge ─────────────────────────────────────────────────────────
30833
31643
  /**
@@ -30858,6 +31668,11 @@ var OperatorChatService = class {
30858
31668
  CONCIERGE_THREAD_KEY,
30859
31669
  operatorMessage
30860
31670
  );
31671
+ if (this.memory) {
31672
+ const threadId = this.ensureActiveMemoryThread();
31673
+ await this.memory.appendTurn(threadId, "user", trimmed).catch(() => {
31674
+ });
31675
+ }
30861
31676
  const start = Date.now();
30862
31677
  let conciergeBody;
30863
31678
  let servedBy = "disabled";
@@ -30913,6 +31728,11 @@ var OperatorChatService = class {
30913
31728
  CONCIERGE_THREAD_KEY,
30914
31729
  responseMessage
30915
31730
  );
31731
+ if (this.memory) {
31732
+ const threadId = this.ensureActiveMemoryThread();
31733
+ await this.memory.appendTurn(threadId, "assistant", conciergeBody).catch(() => {
31734
+ });
31735
+ }
30916
31736
  const payload = {
30917
31737
  version: "1.2",
30918
31738
  event_id: makeEventId("conc"),
@@ -30945,6 +31765,105 @@ var OperatorChatService = class {
30945
31765
  );
30946
31766
  return thread ? thread.messages : [];
30947
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
+ }
30948
31867
  /**
30949
31868
  * Stitch fortress state into a single context blob the substrate
30950
31869
  * folds into its summarization prompt.
@@ -31108,6 +32027,238 @@ var OperatorChatStore = class {
31108
32027
  }
31109
32028
  };
31110
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
+
31111
32262
  // src/dashboard/v1_1/wiring.ts
31112
32263
  var CapabilityErrorAgentController = class {
31113
32264
  fail(action) {
@@ -31146,6 +32297,14 @@ function buildV11Bindings(inputs) {
31146
32297
  let operatorChatService;
31147
32298
  if (inputs.storage && inputs.masterKey) {
31148
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
+ });
31149
32308
  operatorChatService = new OperatorChatService({
31150
32309
  store: chatStore,
31151
32310
  auditLog: inputs.auditLog,
@@ -31156,7 +32315,8 @@ function buildV11Bindings(inputs) {
31156
32315
  identityId: inputs.identityId,
31157
32316
  registry
31158
32317
  }),
31159
- conciergePiiFilter: buildConciergePiiFilter()
32318
+ conciergePiiFilter: buildConciergePiiFilter(),
32319
+ conciergeMemory
31160
32320
  });
31161
32321
  }
31162
32322
  const hubService = new HubService({
@@ -31348,13 +32508,13 @@ init_encryption();
31348
32508
  init_encoding();
31349
32509
  var INTELLIGENCE_NAMESPACE = "_intelligence";
31350
32510
  var SUBSTRATE_CONFIG_KEY = "substrate-config";
31351
- var HKDF_INFO2 = "intelligence-substrate-config";
32511
+ var HKDF_INFO3 = "intelligence-substrate-config";
31352
32512
  var IntelligenceConfigStore = class {
31353
32513
  storage;
31354
32514
  encryptionKey;
31355
32515
  constructor(storage, masterKey) {
31356
32516
  this.storage = storage;
31357
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO2);
32517
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
31358
32518
  }
31359
32519
  /**
31360
32520
  * Load the operator's substrate config from disk. Returns the config
@@ -33493,7 +34653,9 @@ async function verifyExitBundle(bundleDir, options = {}) {
33493
34653
  );
33494
34654
  }
33495
34655
  }
33496
- 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;
33497
34659
  const identityFailed = identity ? !identity.signature_valid : false;
33498
34660
  const unverifiableCount = reputation?.unverifiable_attestations ?? 0;
33499
34661
  const unverifiableFailed = unverifiableCount > 0 && !options.acceptUnverifiableAttestations;
@@ -33502,6 +34664,16 @@ async function verifyExitBundle(bundleDir, options = {}) {
33502
34664
  `${unverifiableCount} reputation attestation(s) have unknown signer public keys; pass --accept-unverifiable-attestations to import anyway`
33503
34665
  );
33504
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
+ }
33505
34677
  return {
33506
34678
  version: "1.1",
33507
34679
  passed: !reputationFailed && !identityFailed && !unverifiableFailed,
@@ -33521,7 +34693,7 @@ async function verifyExitBundle(bundleDir, options = {}) {
33521
34693
  identity,
33522
34694
  audit,
33523
34695
  reputation,
33524
- failure_class: reputationFailed || identityFailed || unverifiableFailed ? "other" : void 0
34696
+ failure_class: detailedFailureClass
33525
34697
  };
33526
34698
  }
33527
34699
 
@@ -34448,7 +35620,19 @@ async function runExitCommand(args) {
34448
35620
  }
34449
35621
  const config = await loadConfig();
34450
35622
  const ctx = await openExitContext(argv, env);
34451
- 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
+ }
34452
35636
  const result = await exportExitBundle({
34453
35637
  bundleDir: outDir,
34454
35638
  storage: ctx.storage,
@@ -35100,7 +36284,19 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35100
36284
  const profileStore = new SovereigntyProfileStore(storage, masterKey);
35101
36285
  await profileStore.load();
35102
36286
  const { tools: profileTools } = createSovereigntyProfileTools(profileStore, auditLog);
35103
- 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
+ }
35104
36300
  const baseline = new BaselineTracker(storage, masterKey);
35105
36301
  await baseline.load();
35106
36302
  let approvalChannel;
@@ -35198,6 +36394,21 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35198
36394
  });
35199
36395
  } : void 0;
35200
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
+ }
35201
36412
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
35202
36413
  const { tools: sanctuaryMetaTools } = createSanctuaryTools({
35203
36414
  config,
@@ -35373,6 +36584,6 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35373
36584
  };
35374
36585
  }
35375
36586
 
35376
- 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 };
35377
36588
  //# sourceMappingURL=index.js.map
35378
36589
  //# sourceMappingURL=index.js.map