@sanctuary-framework/mcp-server 0.10.6 → 1.0.0-rc.2

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.cjs CHANGED
@@ -20,6 +20,7 @@ var fs = require('fs');
20
20
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
21
21
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
22
22
  var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
23
+ var url = require('url');
23
24
 
24
25
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
25
26
  var __defProp = Object.defineProperty;
@@ -816,7 +817,8 @@ var RESERVED_NAMESPACE_PREFIXES = [
816
817
  "_handshake",
817
818
  "_shr",
818
819
  "_sovereignty_profile",
819
- "_context_gate_policies"
820
+ "_context_gate_policies",
821
+ "_fortress_mode"
820
822
  ];
821
823
  var StateStore = class _StateStore {
822
824
  storage;
@@ -1390,7 +1392,8 @@ var RESERVED_NAMESPACE_PREFIXES2 = [
1390
1392
  "_handshake",
1391
1393
  "_shr",
1392
1394
  "_sovereignty_profile",
1393
- "_context_gate_policies"
1395
+ "_context_gate_policies",
1396
+ "_fortress_mode"
1394
1397
  ];
1395
1398
  function getReservedNamespaceViolation(namespace) {
1396
1399
  for (const prefix of RESERVED_NAMESPACE_PREFIXES2) {
@@ -4143,8 +4146,12 @@ var DEFAULT_POLICY = {
4143
4146
  // Clears all runtime governance state — always requires approval
4144
4147
  "sanctuary_bootstrap",
4145
4148
  // Creates new Ed25519 identity + publishes — always requires approval
4146
- "sanctuary_export_identity_bundle"
4149
+ "sanctuary_export_identity_bundle",
4147
4150
  // Exports portable identity — always requires approval
4151
+ // WP-MVP-2 Operator Console: federation-node-join requires explicit
4152
+ // operator confirmation per Key 8. No auto-approve path. The console's
4153
+ // JoinApprover drives this gate via `MeshConsoleClient.makeJoinApprover`.
4154
+ "federation_node_join"
4148
4155
  ],
4149
4156
  tier2_anomaly: DEFAULT_TIER2,
4150
4157
  tier3_always_allow: [
@@ -6403,6 +6410,28 @@ function generateDashboardHTML(options) {
6403
6410
  </div>
6404
6411
  </div>
6405
6412
 
6413
+ <!--
6414
+ Mesh Health panel (WP-MVP-3 Follow-up #3).
6415
+ Subscribes to the existing /events SSE channel \u2014 no new transport.
6416
+ Render shape: per-node row with presence + flags + rollup.
6417
+ On open alert: list inline with operator-decision CTAs (rollback /
6418
+ split-brain / canonical-audit promotion).
6419
+ On post-recovery prompt: render rotation-prompt overlay with
6420
+ broker-credential list + "rotate now" buttons (rotation flow itself
6421
+ is the existing v0.10.x broker rotation surface).
6422
+ -->
6423
+ <div class="mesh-health-panel" id="mesh-health-panel">
6424
+ <div class="panel-header">
6425
+ <div class="panel-title">Mesh Health</div>
6426
+ <span class="card-value" id="mesh-health-updated-at" style="font-size: 11px; color: var(--text-secondary);">\u2014</span>
6427
+ </div>
6428
+ <div id="mesh-health-rows" class="mesh-health-rows">
6429
+ <div class="empty-state">Waiting for mesh health data\u2026</div>
6430
+ </div>
6431
+ <div id="mesh-health-alerts" class="mesh-health-alerts" style="margin-top: 12px;"></div>
6432
+ <div id="mesh-post-recovery-prompt" class="mesh-post-recovery-prompt" style="margin-top: 12px; display: none;"></div>
6433
+ </div>
6434
+
6406
6435
  <!-- Sovereignty Profile Panel -->
6407
6436
  <div class="profile-panel" id="sovereignty-profile-panel">
6408
6437
  <div class="panel-header">
@@ -7112,12 +7141,96 @@ function generateDashboardHTML(options) {
7112
7141
  loadProxyServers();
7113
7142
  });
7114
7143
 
7144
+ // \u2500\u2500 Mesh Health (WP-MVP-3 Follow-up #3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7145
+ // The federation FailureModeDetector pushes per-tick snapshots and
7146
+ // per-detection alerts via the existing /events channel. Renders are
7147
+ // best-effort \u2014 if the panel DOM is absent (older HTML cache), we
7148
+ // silently skip rather than crashing.
7149
+ eventSource.addEventListener('mesh-health', (e) => {
7150
+ try {
7151
+ const snap = JSON.parse(e.data);
7152
+ renderMeshHealth(snap);
7153
+ } catch (err) { console.error('mesh-health render failed', err); }
7154
+ });
7155
+ eventSource.addEventListener('mesh-failure-mode-alert', (e) => {
7156
+ try {
7157
+ const alert = JSON.parse(e.data);
7158
+ appendMeshAlert(alert);
7159
+ } catch (err) { console.error('mesh alert render failed', err); }
7160
+ });
7161
+ eventSource.addEventListener('mesh-post-recovery-prompt', (e) => {
7162
+ try {
7163
+ const prompt = JSON.parse(e.data);
7164
+ renderMeshPostRecoveryPrompt(prompt);
7165
+ } catch (err) { console.error('mesh post-recovery render failed', err); }
7166
+ });
7167
+
7115
7168
  eventSource.onerror = () => {
7116
7169
  console.error('SSE error');
7117
7170
  setTimeout(setupSSE, 5000);
7118
7171
  };
7119
7172
  }
7120
7173
 
7174
+ // \u2500\u2500 Mesh Health rendering (WP-MVP-3 Follow-up #3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7175
+ function renderMeshHealth(snap) {
7176
+ const updatedAt = document.getElementById('mesh-health-updated-at');
7177
+ const rows = document.getElementById('mesh-health-rows');
7178
+ if (!rows || !snap) return;
7179
+ if (updatedAt) updatedAt.textContent = formatTime(snap.generated_at);
7180
+ const nodes = Array.isArray(snap.nodes) ? snap.nodes : [];
7181
+ if (nodes.length === 0) {
7182
+ rows.innerHTML = '<div class="empty-state">No mesh nodes seen yet.</div>';
7183
+ return;
7184
+ }
7185
+ rows.innerHTML = nodes.map((n) => {
7186
+ const flags = (n.flags || []).map((f) => '<span class="mesh-flag">' + esc(f) + '</span>').join(' ');
7187
+ return '<div class="mesh-row" data-rollup="' + esc(n.rollup) + '">' +
7188
+ '<span class="mesh-node-id">' + esc(n.node_id) + '</span>' +
7189
+ '<span class="mesh-presence">' + esc(n.presence) + '</span>' +
7190
+ '<span class="mesh-rollup">' + esc(n.rollup) + '</span>' +
7191
+ '<span class="mesh-flags">' + flags + '</span>' +
7192
+ '</div>';
7193
+ }).join('');
7194
+ const alertsBox = document.getElementById('mesh-health-alerts');
7195
+ if (alertsBox && Array.isArray(snap.open_alerts)) {
7196
+ alertsBox.innerHTML = snap.open_alerts.map((a) =>
7197
+ '<div class="mesh-alert" data-mode="' + esc(a.mode) + '">' +
7198
+ '<div class="mesh-alert-mode">' + esc(a.mode) + ' \u2014 ' + esc(a.target_node) + '</div>' +
7199
+ '<div class="mesh-alert-message">' + esc(a.message) + '</div>' +
7200
+ '</div>'
7201
+ ).join('');
7202
+ }
7203
+ }
7204
+
7205
+ function appendMeshAlert(alert) {
7206
+ const alertsBox = document.getElementById('mesh-health-alerts');
7207
+ if (!alertsBox || !alert) return;
7208
+ const html = '<div class="mesh-alert" data-mode="' + esc(alert.mode) + '">' +
7209
+ '<div class="mesh-alert-mode">' + esc(alert.mode) + ' \u2014 ' + esc(alert.target_node) + '</div>' +
7210
+ '<div class="mesh-alert-message">' + esc(alert.message) + '</div>' +
7211
+ '</div>';
7212
+ alertsBox.insertAdjacentHTML('afterbegin', html);
7213
+ }
7214
+
7215
+ function renderMeshPostRecoveryPrompt(prompt) {
7216
+ const box = document.getElementById('mesh-post-recovery-prompt');
7217
+ if (!box || !prompt) return;
7218
+ box.style.display = 'block';
7219
+ const creds = Array.isArray(prompt.credentials) ? prompt.credentials : [];
7220
+ box.innerHTML = '<div class="mesh-rotation-banner">' +
7221
+ '<strong>Post-rotation hygiene:</strong> we just rotated your fortress root key. ' +
7222
+ 'Rotate externally-scoped broker credentials that third parties may still associate with ' +
7223
+ 'the pre-rotation key material.' +
7224
+ '</div>' +
7225
+ '<ul class="mesh-rotation-list">' +
7226
+ creds.map((c) => '<li>' +
7227
+ '<span class="mesh-cred-name">' + esc(c.secret_name) + '</span>' +
7228
+ '<button class="mesh-cred-rotate" data-secret="' + esc(c.secret_name) + '">' +
7229
+ 'rotate now</button>' +
7230
+ '</li>').join('') +
7231
+ '</ul>';
7232
+ }
7233
+
7121
7234
  // Activity Feed
7122
7235
  function addActivityItem(item) {
7123
7236
  activityLog.unshift(item);
@@ -8821,7 +8934,7 @@ var DashboardApprovalChannel = class {
8821
8934
  server = http.createServer(handler);
8822
8935
  }
8823
8936
  this.httpServer = server;
8824
- return new Promise((resolve, reject) => {
8937
+ return new Promise((resolve2, reject) => {
8825
8938
  const protocol = this.useTLS ? "https" : "http";
8826
8939
  const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
8827
8940
  server.listen(this.config.port, this.config.host, () => {
@@ -8846,7 +8959,7 @@ var DashboardApprovalChannel = class {
8846
8959
  if (shouldAutoOpen) {
8847
8960
  this.openInBrowser(sessionUrl);
8848
8961
  }
8849
- resolve();
8962
+ resolve2();
8850
8963
  });
8851
8964
  server.on("error", (err) => {
8852
8965
  if (err.code === "EADDRINUSE") {
@@ -8892,8 +9005,8 @@ var DashboardApprovalChannel = class {
8892
9005
  }
8893
9006
  this.rateLimits.clear();
8894
9007
  if (this.httpServer) {
8895
- return new Promise((resolve) => {
8896
- this.httpServer.close(() => resolve());
9008
+ return new Promise((resolve2) => {
9009
+ this.httpServer.close(() => resolve2());
8897
9010
  });
8898
9011
  }
8899
9012
  }
@@ -8907,7 +9020,7 @@ var DashboardApprovalChannel = class {
8907
9020
  `[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
8908
9021
  `
8909
9022
  );
8910
- return new Promise((resolve) => {
9023
+ return new Promise((resolve2) => {
8911
9024
  const timer = setTimeout(() => {
8912
9025
  this.pending.delete(id);
8913
9026
  const response = {
@@ -8921,12 +9034,12 @@ var DashboardApprovalChannel = class {
8921
9034
  decision: response.decision,
8922
9035
  decided_by: "timeout"
8923
9036
  });
8924
- resolve(response);
9037
+ resolve2(response);
8925
9038
  }, this.config.timeout_seconds * 1e3);
8926
9039
  const pending = {
8927
9040
  id,
8928
9041
  request,
8929
- resolve,
9042
+ resolve: resolve2,
8930
9043
  timer,
8931
9044
  created_at: (/* @__PURE__ */ new Date()).toISOString()
8932
9045
  };
@@ -9678,6 +9791,27 @@ data: ${JSON.stringify(data)}
9678
9791
  broadcastProtectionStatus(data) {
9679
9792
  this.broadcastSSE("protection-status", data);
9680
9793
  }
9794
+ // ── Mesh-health surface (WP-MVP-3 Follow-up #3) ─────────────────────
9795
+ //
9796
+ // The federation FailureModeDetector pushes per-tick health snapshots and
9797
+ // per-detection alerts here; the existing /events SSE channel transports
9798
+ // them to the browser. No new transport.
9799
+ //
9800
+ // Spec §8 + §9. Spawn-prompt acceptance criterion 7: "Mesh Health dashboard
9801
+ // panel renders via existing SSE /events channel — no new transport. Every
9802
+ // state transition produces an observable SSE event."
9803
+ /** Push a Mesh Health snapshot (full re-render trigger on the client). */
9804
+ broadcastMeshHealth(snapshot) {
9805
+ this.broadcastSSE("mesh-health", snapshot);
9806
+ }
9807
+ /** Push a single failure-mode alert (incremental client update). */
9808
+ broadcastMeshFailureModeAlert(alert) {
9809
+ this.broadcastSSE("mesh-failure-mode-alert", alert);
9810
+ }
9811
+ /** Push a post-recovery prompt update (master rotation hygiene flow). */
9812
+ broadcastMeshPostRecoveryPrompt(prompt) {
9813
+ this.broadcastSSE("mesh-post-recovery-prompt", prompt);
9814
+ }
9681
9815
  /**
9682
9816
  * Open a URL in the system's default browser.
9683
9817
  * Cross-platform: macOS (open), Linux (xdg-open), Windows (start).
@@ -9751,7 +9885,7 @@ var WebhookApprovalChannel = class {
9751
9885
  * Start the callback listener server.
9752
9886
  */
9753
9887
  async start() {
9754
- return new Promise((resolve, reject) => {
9888
+ return new Promise((resolve2, reject) => {
9755
9889
  this.callbackServer = http.createServer(
9756
9890
  (req, res) => this.handleCallback(req, res)
9757
9891
  );
@@ -9766,7 +9900,7 @@ var WebhookApprovalChannel = class {
9766
9900
 
9767
9901
  `
9768
9902
  );
9769
- resolve();
9903
+ resolve2();
9770
9904
  }
9771
9905
  );
9772
9906
  this.callbackServer.on("error", reject);
@@ -9786,8 +9920,8 @@ var WebhookApprovalChannel = class {
9786
9920
  }
9787
9921
  this.pending.clear();
9788
9922
  if (this.callbackServer) {
9789
- return new Promise((resolve) => {
9790
- this.callbackServer.close(() => resolve());
9923
+ return new Promise((resolve2) => {
9924
+ this.callbackServer.close(() => resolve2());
9791
9925
  });
9792
9926
  }
9793
9927
  }
@@ -9800,7 +9934,7 @@ var WebhookApprovalChannel = class {
9800
9934
  `[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
9801
9935
  `
9802
9936
  );
9803
- return new Promise((resolve) => {
9937
+ return new Promise((resolve2) => {
9804
9938
  const timer = setTimeout(() => {
9805
9939
  this.pending.delete(id);
9806
9940
  const response = {
@@ -9809,12 +9943,12 @@ var WebhookApprovalChannel = class {
9809
9943
  decided_at: (/* @__PURE__ */ new Date()).toISOString(),
9810
9944
  decided_by: "timeout"
9811
9945
  };
9812
- resolve(response);
9946
+ resolve2(response);
9813
9947
  }, this.config.timeout_seconds * 1e3);
9814
9948
  const pending = {
9815
9949
  id,
9816
9950
  request,
9817
- resolve,
9951
+ resolve: resolve2,
9818
9952
  timer,
9819
9953
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9820
9954
  };
@@ -13008,7 +13142,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13008
13142
  const now = (/* @__PURE__ */ new Date()).toISOString();
13009
13143
  const canonicalBytes = canonicalize(outcome);
13010
13144
  const canonicalString = new TextDecoder().decode(canonicalBytes);
13011
- const sha2565 = createCommitment(canonicalString);
13145
+ const sha2568 = createCommitment(canonicalString);
13012
13146
  let pedersenData;
13013
13147
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
13014
13148
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -13020,7 +13154,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13020
13154
  const commitmentPayload = {
13021
13155
  bridge_commitment_id: commitmentId,
13022
13156
  session_id: outcome.session_id,
13023
- sha256_commitment: sha2565.commitment,
13157
+ sha256_commitment: sha2568.commitment,
13024
13158
  terms_hash: outcome.terms_hash,
13025
13159
  committer_did: identity.did,
13026
13160
  committed_at: now,
@@ -13031,8 +13165,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13031
13165
  return {
13032
13166
  bridge_commitment_id: commitmentId,
13033
13167
  session_id: outcome.session_id,
13034
- sha256_commitment: sha2565.commitment,
13035
- blinding_factor: sha2565.blinding_factor,
13168
+ sha256_commitment: sha2568.commitment,
13169
+ blinding_factor: sha2568.blinding_factor,
13036
13170
  committer_did: identity.did,
13037
13171
  signature: toBase64url(signature),
13038
13172
  pedersen_commitment: pedersenData,
@@ -17199,13 +17333,13 @@ var ProxyRouter = class {
17199
17333
  * Call an upstream tool with a timeout.
17200
17334
  */
17201
17335
  async callWithTimeout(serverName, toolName, args, timeoutMs) {
17202
- return new Promise((resolve, reject) => {
17336
+ return new Promise((resolve2, reject) => {
17203
17337
  const timer = setTimeout(() => {
17204
17338
  reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
17205
17339
  }, timeoutMs);
17206
17340
  this.clientManager.callTool(serverName, toolName, args).then((result) => {
17207
17341
  clearTimeout(timer);
17208
- resolve(result);
17342
+ resolve2(result);
17209
17343
  }).catch((err) => {
17210
17344
  clearTimeout(timer);
17211
17345
  reject(err);
@@ -21556,8 +21690,15 @@ function l3Card(l3) {
21556
21690
  <div><dt>Proofs today</dt><dd>${escHtml(l3.proofs_today)}</dd></div>`
21557
21691
  );
21558
21692
  }
21693
+ var VERASCORE_CLAIM_URL = "https://www.verascore.ai";
21559
21694
  function l4Card(l4) {
21560
- const score = l4.score != null ? `<div class="score-block"><span class="score-value">${escHtml(l4.score)}</span><span class="score-label">Verascore</span></div>` : `<div class="claim-block">${escHtml(l4.claim_cta ?? "Claim your profile at verascore.ai")}</div>`;
21695
+ let score;
21696
+ if (l4.score != null) {
21697
+ score = `<div class="score-block"><span class="score-value">${escHtml(l4.score)}</span><span class="score-label">Verascore</span></div>`;
21698
+ } else {
21699
+ const claimText = l4.claim_cta ?? "Claim your profile at verascore.ai";
21700
+ score = `<div class="claim-block"><a class="claim-link" href="${VERASCORE_CLAIM_URL}" target="_blank" rel="noopener noreferrer">${escHtml(claimText)}</a></div>`;
21701
+ }
21561
21702
  return layerCard(
21562
21703
  l4,
21563
21704
  `<div class="layer-cta">${score}</div>${l4EvidenceBlock(l4)}`
@@ -22220,6 +22361,1129 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
22220
22361
  </html>`;
22221
22362
  }
22222
22363
 
22364
+ // src/policy-engine/constants.ts
22365
+ var COMPILED_POLICY_SCHEMA_VERSION = "0.1";
22366
+ var POLICY_UPDATE_EVENT_TYPE = "policy_update";
22367
+ var POLICY_SLOTS = [
22368
+ "memory",
22369
+ "credentials",
22370
+ "plans",
22371
+ "outputs"
22372
+ ];
22373
+ function isPolicySlot(value) {
22374
+ return typeof value === "string" && POLICY_SLOTS.includes(value);
22375
+ }
22376
+ var CHANNEL_TEMPLATE_IDS = [
22377
+ "read-outputs-only",
22378
+ "bidirectional-sync",
22379
+ "credential-share-scoped",
22380
+ "plan-inspect-read-only",
22381
+ "escrow-handoff"
22382
+ ];
22383
+ var BUDGET_UNITS = ["tokens", "usd"];
22384
+
22385
+ // src/templates/registry.ts
22386
+ var TEMPLATE_NAMES = [
22387
+ "research-assistant",
22388
+ "coding-assistant",
22389
+ "ops-runner",
22390
+ "planner",
22391
+ "handoff-coordinator"
22392
+ ];
22393
+ function isTemplateName(value) {
22394
+ return typeof value === "string" && TEMPLATE_NAMES.includes(value);
22395
+ }
22396
+ var TemplateValidationError = class extends Error {
22397
+ constructor(templateName, message) {
22398
+ super(`template "${templateName}": ${message}`);
22399
+ this.name = "TemplateValidationError";
22400
+ }
22401
+ };
22402
+ function validateMetadata(name, data) {
22403
+ if (typeof data !== "object" || data === null) {
22404
+ throw new TemplateValidationError(name, "template.json must be an object");
22405
+ }
22406
+ const d = data;
22407
+ if (typeof d.name !== "string" || d.name !== name) {
22408
+ throw new TemplateValidationError(name, `template.json name must be "${name}"`);
22409
+ }
22410
+ if (typeof d.version !== "string" || !/^\d+\.\d+\.\d+$/.test(d.version)) {
22411
+ throw new TemplateValidationError(name, "template.json version must be semver");
22412
+ }
22413
+ if (typeof d.channel !== "string" || !CHANNEL_TEMPLATE_IDS.includes(d.channel)) {
22414
+ throw new TemplateValidationError(
22415
+ name,
22416
+ `template.json channel must be one of: ${CHANNEL_TEMPLATE_IDS.join(", ")}`
22417
+ );
22418
+ }
22419
+ if (typeof d.tier !== "string" || !["A", "B", "C"].includes(d.tier)) {
22420
+ throw new TemplateValidationError(name, "template.json tier must be A, B, or C");
22421
+ }
22422
+ if (typeof d.target_archetype !== "string" || d.target_archetype.length === 0) {
22423
+ throw new TemplateValidationError(name, "template.json target_archetype must be a non-empty string");
22424
+ }
22425
+ if (typeof d.description !== "string" || d.description.length === 0) {
22426
+ throw new TemplateValidationError(name, "template.json description must be a non-empty string");
22427
+ }
22428
+ }
22429
+ function validateDefaults(name, data) {
22430
+ if (typeof data !== "object" || data === null) {
22431
+ throw new TemplateValidationError(name, "defaults.json must be an object");
22432
+ }
22433
+ const d = data;
22434
+ if (!Array.isArray(d.egress)) {
22435
+ throw new TemplateValidationError(name, "defaults.json egress must be an array");
22436
+ }
22437
+ for (const entry of d.egress) {
22438
+ if (typeof entry !== "object" || entry === null) {
22439
+ throw new TemplateValidationError(name, "defaults.json egress entry must be an object");
22440
+ }
22441
+ const e = entry;
22442
+ if (typeof e.destination !== "string" || e.destination.length === 0) {
22443
+ throw new TemplateValidationError(name, "egress entry destination must be a non-empty string");
22444
+ }
22445
+ if (!Array.isArray(e.methods)) {
22446
+ throw new TemplateValidationError(name, "egress entry methods must be an array");
22447
+ }
22448
+ }
22449
+ if (typeof d.budgets !== "object" || d.budgets === null) {
22450
+ throw new TemplateValidationError(name, "defaults.json budgets must be an object");
22451
+ }
22452
+ if (typeof d.retention !== "object" || d.retention === null) {
22453
+ throw new TemplateValidationError(name, "defaults.json retention must be an object");
22454
+ }
22455
+ const ret = d.retention;
22456
+ if (typeof ret.windows !== "object" || ret.windows === null) {
22457
+ throw new TemplateValidationError(name, "defaults.json retention.windows must be an object");
22458
+ }
22459
+ }
22460
+ function validateCommitments(name, data) {
22461
+ if (typeof data !== "object" || data === null) {
22462
+ throw new TemplateValidationError(name, "commitments.json must be an object");
22463
+ }
22464
+ const d = data;
22465
+ if (!Array.isArray(d.shapes)) {
22466
+ throw new TemplateValidationError(name, "commitments.json shapes must be an array");
22467
+ }
22468
+ for (const shape of d.shapes) {
22469
+ if (typeof shape !== "object" || shape === null) {
22470
+ throw new TemplateValidationError(name, "commitment shape must be an object");
22471
+ }
22472
+ const s = shape;
22473
+ if (typeof s.commitment_class !== "string" || s.commitment_class.length === 0) {
22474
+ throw new TemplateValidationError(name, "commitment shape commitment_class must be a non-empty string");
22475
+ }
22476
+ if (typeof s.example_deliverable !== "string") {
22477
+ throw new TemplateValidationError(name, "commitment shape example_deliverable must be a string");
22478
+ }
22479
+ if (typeof s.example_deadline_or_terminal !== "string") {
22480
+ throw new TemplateValidationError(name, "commitment shape example_deadline_or_terminal must be a string");
22481
+ }
22482
+ }
22483
+ }
22484
+ function lintOnboarding(_name, content) {
22485
+ const violations = [];
22486
+ if (content.includes("\u2014")) {
22487
+ violations.push("onboarding.md contains em-dashes (U+2014). Replace with commas, semicolons, colons, or parentheses.");
22488
+ }
22489
+ if (content.includes("\u2013")) {
22490
+ violations.push("onboarding.md contains en-dashes (U+2013). Replace with hyphens or other punctuation.");
22491
+ }
22492
+ return { clean: violations.length === 0, violations };
22493
+ }
22494
+ function resolveTemplatesDir() {
22495
+ const thisFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
22496
+ const thisDir = path.dirname(thisFile);
22497
+ if (thisDir.includes("/dist/")) {
22498
+ return thisDir.replace("/dist/templates", "/src/templates");
22499
+ }
22500
+ return thisDir;
22501
+ }
22502
+ function loadTemplateBundle(name, templatesDir) {
22503
+ const dir = path.join(templatesDir, name);
22504
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
22505
+ throw new TemplateValidationError(name, `template directory not found: ${dir}`);
22506
+ }
22507
+ const metadataRaw = JSON.parse(fs.readFileSync(path.join(dir, "template.json"), "utf-8"));
22508
+ validateMetadata(name, metadataRaw);
22509
+ const policyEnglish = fs.readFileSync(path.join(dir, "policy.md"), "utf-8").trim();
22510
+ if (policyEnglish.length === 0) {
22511
+ throw new TemplateValidationError(name, "policy.md must not be empty");
22512
+ }
22513
+ const defaultsRaw = JSON.parse(fs.readFileSync(path.join(dir, "defaults.json"), "utf-8"));
22514
+ validateDefaults(name, defaultsRaw);
22515
+ const commitmentsRaw = JSON.parse(fs.readFileSync(path.join(dir, "commitments.json"), "utf-8"));
22516
+ validateCommitments(name, commitmentsRaw);
22517
+ const onboarding = fs.readFileSync(path.join(dir, "onboarding.md"), "utf-8").trim();
22518
+ if (onboarding.length === 0) {
22519
+ throw new TemplateValidationError(name, "onboarding.md must not be empty");
22520
+ }
22521
+ const lint = lintOnboarding(name, onboarding);
22522
+ if (!lint.clean) {
22523
+ throw new TemplateValidationError(name, lint.violations.join("; "));
22524
+ }
22525
+ return {
22526
+ metadata: metadataRaw,
22527
+ policy_english: policyEnglish,
22528
+ defaults: defaultsRaw,
22529
+ commitments: commitmentsRaw,
22530
+ onboarding
22531
+ };
22532
+ }
22533
+ var _cache = null;
22534
+ function ensureLoaded() {
22535
+ if (_cache) return _cache;
22536
+ _cache = /* @__PURE__ */ new Map();
22537
+ const dir = resolveTemplatesDir();
22538
+ for (const name of TEMPLATE_NAMES) {
22539
+ _cache.set(name, loadTemplateBundle(name, dir));
22540
+ }
22541
+ return _cache;
22542
+ }
22543
+ function listTemplates() {
22544
+ const cache = ensureLoaded();
22545
+ return TEMPLATE_NAMES.map((name) => {
22546
+ const bundle = cache.get(name);
22547
+ return {
22548
+ metadata: bundle.metadata,
22549
+ onboarding: bundle.onboarding
22550
+ };
22551
+ });
22552
+ }
22553
+ function getTemplate2(name) {
22554
+ if (!isTemplateName(name)) return null;
22555
+ const cache = ensureLoaded();
22556
+ return cache.get(name) ?? null;
22557
+ }
22558
+ function getTemplateEntry(name) {
22559
+ const bundle = getTemplate2(name);
22560
+ if (!bundle) return null;
22561
+ return { metadata: bundle.metadata, onboarding: bundle.onboarding };
22562
+ }
22563
+
22564
+ // src/policy-engine/null-policy.ts
22565
+ function buildNullPolicy(params) {
22566
+ const denyRule = (slot) => ({
22567
+ slot,
22568
+ mode: "deny",
22569
+ grants: []
22570
+ });
22571
+ return {
22572
+ schema_version: "0.1",
22573
+ agent_id: params.agent_id,
22574
+ fortress_id: params.fortress_id,
22575
+ policy_version: 0,
22576
+ slots: {
22577
+ memory: denyRule("memory"),
22578
+ credentials: denyRule("credentials"),
22579
+ plans: denyRule("plans"),
22580
+ outputs: denyRule("outputs")
22581
+ },
22582
+ capabilities: {
22583
+ concordia_commitment_classes: [],
22584
+ honeypot_skill_ids: [],
22585
+ is_sentinel: false
22586
+ },
22587
+ auto_trigger_ladder: {
22588
+ honeypot_auto_freeze: true,
22589
+ threshold_rule_action: "operator_approved",
22590
+ ml_anomaly_action: "operator_approved"
22591
+ },
22592
+ source_english: "(null policy \u2014 no operator authoring yet; hermetic default denies all four slots)",
22593
+ compiled_at: "1970-01-01T00:00:00.000Z"
22594
+ };
22595
+ }
22596
+
22597
+ // src/policy-engine/channel-templates.ts
22598
+ function basePolicy(params) {
22599
+ if (params.merge_into) {
22600
+ const p2 = {
22601
+ ...params.merge_into,
22602
+ policy_version: params.policy_version,
22603
+ compiled_at: (/* @__PURE__ */ new Date()).toISOString(),
22604
+ slots: {
22605
+ memory: cloneRule(params.merge_into.slots.memory),
22606
+ credentials: cloneRule(params.merge_into.slots.credentials),
22607
+ plans: cloneRule(params.merge_into.slots.plans),
22608
+ outputs: cloneRule(params.merge_into.slots.outputs)
22609
+ },
22610
+ capabilities: {
22611
+ ...params.merge_into.capabilities,
22612
+ concordia_commitment_classes: [
22613
+ ...params.merge_into.capabilities.concordia_commitment_classes
22614
+ ],
22615
+ honeypot_skill_ids: [...params.merge_into.capabilities.honeypot_skill_ids]
22616
+ },
22617
+ auto_trigger_ladder: { ...params.merge_into.auto_trigger_ladder }
22618
+ };
22619
+ if (params.parent_version !== void 0) p2.parent_version = params.parent_version;
22620
+ else delete p2.parent_version;
22621
+ return p2;
22622
+ }
22623
+ const p = buildNullPolicy({
22624
+ agent_id: params.agent_id,
22625
+ fortress_id: params.fortress_id
22626
+ });
22627
+ p.policy_version = params.policy_version;
22628
+ p.compiled_at = (/* @__PURE__ */ new Date()).toISOString();
22629
+ if (params.parent_version !== void 0) p.parent_version = params.parent_version;
22630
+ return p;
22631
+ }
22632
+ function cloneRule(r) {
22633
+ return {
22634
+ slot: r.slot,
22635
+ mode: r.mode,
22636
+ grants: r.grants.map((g) => ({ ...g, scope: g.scope ? { ...g.scope } : void 0 }))
22637
+ };
22638
+ }
22639
+ function grantOn(policy, slot, grant) {
22640
+ const rule = policy.slots[slot];
22641
+ rule.mode = "grant";
22642
+ const dup = rule.grants.find(
22643
+ (g) => g.counterparty === grant.counterparty && g.action === grant.action
22644
+ );
22645
+ if (dup) {
22646
+ if (grant.scope) {
22647
+ dup.scope = { ...dup.scope ?? {}, ...grant.scope };
22648
+ }
22649
+ return;
22650
+ }
22651
+ rule.grants.push(grant);
22652
+ rule.grants.sort((a, b) => {
22653
+ if (a.counterparty === b.counterparty) return a.action.localeCompare(b.action);
22654
+ return a.counterparty.localeCompare(b.counterparty);
22655
+ });
22656
+ }
22657
+ var readOutputsOnly = (params) => {
22658
+ const p = basePolicy(params);
22659
+ p.source_english = `${params.counterparty} may read ${params.agent_id}'s outputs \u2014 read-only, no other access.`;
22660
+ grantOn(p, "outputs", {
22661
+ counterparty: params.counterparty,
22662
+ action: "read",
22663
+ ...params.scope ? { scope: { ...params.scope } } : {}
22664
+ });
22665
+ return p;
22666
+ };
22667
+ var bidirectionalSync = (params) => {
22668
+ const p = basePolicy(params);
22669
+ p.source_english = `Bidirectional sync between ${params.agent_id} and ${params.counterparty} on memory + outputs.`;
22670
+ for (const slot of ["memory", "outputs"]) {
22671
+ for (const action of ["read", "subscribe"]) {
22672
+ grantOn(p, slot, {
22673
+ counterparty: params.counterparty,
22674
+ action,
22675
+ ...params.scope ? { scope: { ...params.scope } } : {}
22676
+ });
22677
+ }
22678
+ }
22679
+ return p;
22680
+ };
22681
+ var credentialShareScoped = (params) => {
22682
+ const p = basePolicy(params);
22683
+ const credentialId = (params.scope && typeof params.scope.credential_id === "string" ? params.scope.credential_id : void 0) ?? "(unspecified)";
22684
+ p.source_english = `${params.agent_id} may share credential "${credentialId}" with ${params.counterparty}; no other credential access.`;
22685
+ grantOn(p, "credentials", {
22686
+ counterparty: params.counterparty,
22687
+ action: "share",
22688
+ scope: {
22689
+ credential_id: credentialId,
22690
+ ...params.scope ?? {}
22691
+ }
22692
+ });
22693
+ return p;
22694
+ };
22695
+ var planInspectReadOnly = (params) => {
22696
+ const p = basePolicy(params);
22697
+ p.source_english = `${params.counterparty} may read-only inspect ${params.agent_id}'s plans.`;
22698
+ grantOn(p, "plans", {
22699
+ counterparty: params.counterparty,
22700
+ action: "read",
22701
+ ...params.scope ? { scope: { ...params.scope } } : {}
22702
+ });
22703
+ return p;
22704
+ };
22705
+ var escrowHandoff = (params) => {
22706
+ const p = basePolicy(params);
22707
+ p.source_english = `${params.agent_id} may escrow-handoff outputs and plan-read to ${params.counterparty}. Commitment class: intra-mesh-escrow.`;
22708
+ grantOn(p, "plans", {
22709
+ counterparty: params.counterparty,
22710
+ action: "read",
22711
+ ...params.scope ? { scope: { ...params.scope } } : {}
22712
+ });
22713
+ for (const action of ["read", "subscribe"]) {
22714
+ grantOn(p, "outputs", {
22715
+ counterparty: params.counterparty,
22716
+ action,
22717
+ ...params.scope ? { scope: { ...params.scope } } : {}
22718
+ });
22719
+ }
22720
+ const cls = "intra-mesh-escrow";
22721
+ if (!p.capabilities.concordia_commitment_classes.includes(cls)) {
22722
+ p.capabilities.concordia_commitment_classes.push(cls);
22723
+ p.capabilities.concordia_commitment_classes.sort();
22724
+ }
22725
+ return p;
22726
+ };
22727
+ var REGISTRY = {
22728
+ "read-outputs-only": {
22729
+ id: "read-outputs-only",
22730
+ label: "Read outputs only",
22731
+ description: "Counterparty may read this agent's outputs. No memory, credentials, or plans access.",
22732
+ factory: readOutputsOnly
22733
+ },
22734
+ "bidirectional-sync": {
22735
+ id: "bidirectional-sync",
22736
+ label: "Bidirectional memory + output sync",
22737
+ description: "Both agents may read and subscribe to each other's memory and outputs. Credentials and plans remain hermetic.",
22738
+ factory: bidirectionalSync
22739
+ },
22740
+ "credential-share-scoped": {
22741
+ id: "credential-share-scoped",
22742
+ label: "Scoped credential share",
22743
+ description: "Share one specific credential with counterparty. Requires scope.credential_id. No broad credential access.",
22744
+ factory: credentialShareScoped
22745
+ },
22746
+ "plan-inspect-read-only": {
22747
+ id: "plan-inspect-read-only",
22748
+ label: "Plan inspect (read-only)",
22749
+ description: "Counterparty may read-only inspect this agent's plans. Intended for supervisor / sentinel patterns.",
22750
+ factory: planInspectReadOnly
22751
+ },
22752
+ "escrow-handoff": {
22753
+ id: "escrow-handoff",
22754
+ label: "Escrow handoff",
22755
+ description: "Escrow-style handoff. Counterparty reads plan, reads + subscribes to outputs, and the intra-mesh-escrow commitment class is declared. Memory and credentials remain hermetic.",
22756
+ factory: escrowHandoff
22757
+ }
22758
+ };
22759
+ function applyChannelTemplate(id, params) {
22760
+ const entry = REGISTRY[id];
22761
+ if (!entry) {
22762
+ throw new Error(`unknown channel template: ${id}`);
22763
+ }
22764
+ return entry.factory(params);
22765
+ }
22766
+
22767
+ // src/mesh/errors.ts
22768
+ var MeshError = class extends Error {
22769
+ constructor(message) {
22770
+ super(message);
22771
+ this.name = "MeshError";
22772
+ }
22773
+ };
22774
+ var MeshEnvelopeError = class extends MeshError {
22775
+ constructor(message) {
22776
+ super(message);
22777
+ this.name = "MeshEnvelopeError";
22778
+ }
22779
+ };
22780
+ var MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
22781
+ constructor(key) {
22782
+ super(
22783
+ `v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
22784
+ );
22785
+ this.name = "MeshReservedExtensionKeyError";
22786
+ }
22787
+ };
22788
+ var MeshReservedEventTypeError = class extends MeshEnvelopeError {
22789
+ constructor(eventType) {
22790
+ super(
22791
+ `v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
22792
+ );
22793
+ this.name = "MeshReservedEventTypeError";
22794
+ }
22795
+ };
22796
+
22797
+ // src/mesh/canonical-json.ts
22798
+ var MeshCanonicalJsonError = class extends MeshError {
22799
+ constructor(message) {
22800
+ super(message);
22801
+ this.name = "MeshCanonicalJsonError";
22802
+ }
22803
+ };
22804
+ function canonicalize2(value) {
22805
+ if (value === void 0) {
22806
+ throw new MeshCanonicalJsonError(
22807
+ "canonicalize(): top-level undefined is not serializable"
22808
+ );
22809
+ }
22810
+ return encode(value);
22811
+ }
22812
+ function encode(value) {
22813
+ if (value === null) return "null";
22814
+ if (typeof value === "boolean") return value ? "true" : "false";
22815
+ if (typeof value === "number") {
22816
+ if (!Number.isFinite(value)) {
22817
+ throw new MeshCanonicalJsonError(
22818
+ `canonicalize(): non-finite number (${String(value)}) is not serializable`
22819
+ );
22820
+ }
22821
+ return JSON.stringify(value);
22822
+ }
22823
+ if (typeof value === "string") return JSON.stringify(value);
22824
+ if (Array.isArray(value)) return encodeArray(value);
22825
+ if (typeof value === "object") return encodeObject(value);
22826
+ throw new MeshCanonicalJsonError(
22827
+ `canonicalize(): unsupported type ${typeof value}`
22828
+ );
22829
+ }
22830
+ function encodeArray(arr) {
22831
+ const parts = [];
22832
+ for (const item of arr) {
22833
+ parts.push(item === void 0 ? "null" : encode(item));
22834
+ }
22835
+ return "[" + parts.join(",") + "]";
22836
+ }
22837
+ function encodeObject(obj) {
22838
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
22839
+ const parts = [];
22840
+ for (const k of keys) {
22841
+ parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
22842
+ }
22843
+ return "{" + parts.join(",") + "}";
22844
+ }
22845
+ function canonicalizeToBytes(value) {
22846
+ return new TextEncoder().encode(canonicalize2(value));
22847
+ }
22848
+
22849
+ // src/policy-engine/canonical-policy.ts
22850
+ init_encoding();
22851
+
22852
+ // src/policy-engine/errors.ts
22853
+ var PolicyEngineError = class extends Error {
22854
+ constructor(message) {
22855
+ super(message);
22856
+ this.name = "PolicyEngineError";
22857
+ }
22858
+ };
22859
+ var CompiledPolicyShapeError = class extends PolicyEngineError {
22860
+ constructor(message) {
22861
+ super(message);
22862
+ this.name = "CompiledPolicyShapeError";
22863
+ }
22864
+ };
22865
+
22866
+ // src/policy-engine/canonical-policy.ts
22867
+ function checkSlotGrant(value, path) {
22868
+ if (typeof value !== "object" || value === null) {
22869
+ throw new CompiledPolicyShapeError(`${path}: grant must be an object`);
22870
+ }
22871
+ const g = value;
22872
+ if (typeof g.counterparty !== "string" || g.counterparty.length === 0) {
22873
+ throw new CompiledPolicyShapeError(
22874
+ `${path}.counterparty must be a non-empty string`
22875
+ );
22876
+ }
22877
+ if (typeof g.action !== "string" || g.action.length === 0) {
22878
+ throw new CompiledPolicyShapeError(
22879
+ `${path}.action must be a non-empty string`
22880
+ );
22881
+ }
22882
+ if (g.scope !== void 0) {
22883
+ if (typeof g.scope !== "object" || g.scope === null || Array.isArray(g.scope)) {
22884
+ throw new CompiledPolicyShapeError(
22885
+ `${path}.scope must be an object when present`
22886
+ );
22887
+ }
22888
+ }
22889
+ if (g.max_uses_per_day !== void 0) {
22890
+ if (typeof g.max_uses_per_day !== "number" || !Number.isInteger(g.max_uses_per_day) || g.max_uses_per_day < 0) {
22891
+ throw new CompiledPolicyShapeError(
22892
+ `${path}.max_uses_per_day must be a non-negative integer when present`
22893
+ );
22894
+ }
22895
+ }
22896
+ }
22897
+ function checkSlotRule(slot, value, path) {
22898
+ if (typeof value !== "object" || value === null) {
22899
+ throw new CompiledPolicyShapeError(`${path}: slot rule must be an object`);
22900
+ }
22901
+ const r = value;
22902
+ if (r.slot !== slot) {
22903
+ throw new CompiledPolicyShapeError(
22904
+ `${path}.slot expected ${slot}, got ${String(r.slot)}`
22905
+ );
22906
+ }
22907
+ if (r.mode !== "deny" && r.mode !== "grant") {
22908
+ throw new CompiledPolicyShapeError(
22909
+ `${path}.mode must be "deny" or "grant"`
22910
+ );
22911
+ }
22912
+ if (!Array.isArray(r.grants)) {
22913
+ throw new CompiledPolicyShapeError(
22914
+ `${path}.grants must be an array (possibly empty)`
22915
+ );
22916
+ }
22917
+ if (r.mode === "deny" && r.grants.length > 0) {
22918
+ throw new CompiledPolicyShapeError(
22919
+ `${path}: deny-mode slot rule must carry zero grants`
22920
+ );
22921
+ }
22922
+ r.grants.forEach((g, i) => checkSlotGrant(g, `${path}.grants[${i}]`));
22923
+ }
22924
+ function validateCompiledPolicyShape(candidate) {
22925
+ if (typeof candidate !== "object" || candidate === null) {
22926
+ throw new CompiledPolicyShapeError("compiled policy must be an object");
22927
+ }
22928
+ const p = candidate;
22929
+ if (p.schema_version !== COMPILED_POLICY_SCHEMA_VERSION) {
22930
+ throw new CompiledPolicyShapeError(
22931
+ `schema_version ${String(p.schema_version)} not supported at v0.1 (expected "${COMPILED_POLICY_SCHEMA_VERSION}")`
22932
+ );
22933
+ }
22934
+ if (typeof p.agent_id !== "string" || p.agent_id.length === 0) {
22935
+ throw new CompiledPolicyShapeError("agent_id must be a non-empty string");
22936
+ }
22937
+ if (typeof p.fortress_id !== "string" || p.fortress_id.length === 0) {
22938
+ throw new CompiledPolicyShapeError(
22939
+ "fortress_id must be a non-empty string"
22940
+ );
22941
+ }
22942
+ if (typeof p.policy_version !== "number" || !Number.isInteger(p.policy_version) || p.policy_version < 0) {
22943
+ throw new CompiledPolicyShapeError(
22944
+ "policy_version must be a non-negative integer"
22945
+ );
22946
+ }
22947
+ if (p.parent_version !== void 0 && (typeof p.parent_version !== "number" || !Number.isInteger(p.parent_version) || p.parent_version < 0)) {
22948
+ throw new CompiledPolicyShapeError(
22949
+ "parent_version must be a non-negative integer when present"
22950
+ );
22951
+ }
22952
+ if (typeof p.slots !== "object" || p.slots === null) {
22953
+ throw new CompiledPolicyShapeError("slots must be an object");
22954
+ }
22955
+ const slots = p.slots;
22956
+ const slotKeys = Object.keys(slots).sort();
22957
+ const expectedSlotKeys = [...POLICY_SLOTS].sort();
22958
+ if (slotKeys.length !== expectedSlotKeys.length || !slotKeys.every((k, i) => k === expectedSlotKeys[i])) {
22959
+ throw new CompiledPolicyShapeError(
22960
+ `slots must contain exactly ${expectedSlotKeys.join(", ")}; got ${slotKeys.join(", ") || "(none)"}`
22961
+ );
22962
+ }
22963
+ for (const slot of POLICY_SLOTS) {
22964
+ checkSlotRule(slot, slots[slot], `slots.${slot}`);
22965
+ }
22966
+ if (typeof p.capabilities !== "object" || p.capabilities === null) {
22967
+ throw new CompiledPolicyShapeError("capabilities must be an object");
22968
+ }
22969
+ const caps = p.capabilities;
22970
+ if (!Array.isArray(caps.concordia_commitment_classes) || !caps.concordia_commitment_classes.every((c) => typeof c === "string")) {
22971
+ throw new CompiledPolicyShapeError(
22972
+ "capabilities.concordia_commitment_classes must be a string[]"
22973
+ );
22974
+ }
22975
+ if (!Array.isArray(caps.honeypot_skill_ids) || !caps.honeypot_skill_ids.every((c) => typeof c === "string")) {
22976
+ throw new CompiledPolicyShapeError(
22977
+ "capabilities.honeypot_skill_ids must be a string[]"
22978
+ );
22979
+ }
22980
+ if (typeof caps.is_sentinel !== "boolean") {
22981
+ throw new CompiledPolicyShapeError(
22982
+ "capabilities.is_sentinel must be boolean"
22983
+ );
22984
+ }
22985
+ if (typeof p.auto_trigger_ladder !== "object" || p.auto_trigger_ladder === null) {
22986
+ throw new CompiledPolicyShapeError(
22987
+ "auto_trigger_ladder must be an object"
22988
+ );
22989
+ }
22990
+ const lad = p.auto_trigger_ladder;
22991
+ if (typeof lad.honeypot_auto_freeze !== "boolean") {
22992
+ throw new CompiledPolicyShapeError(
22993
+ "auto_trigger_ladder.honeypot_auto_freeze must be boolean"
22994
+ );
22995
+ }
22996
+ if (lad.threshold_rule_action !== "operator_approved" && lad.threshold_rule_action !== "auto") {
22997
+ throw new CompiledPolicyShapeError(
22998
+ 'auto_trigger_ladder.threshold_rule_action must be "operator_approved" or "auto"'
22999
+ );
23000
+ }
23001
+ if (lad.ml_anomaly_action !== "operator_approved" && lad.ml_anomaly_action !== "auto") {
23002
+ throw new CompiledPolicyShapeError(
23003
+ 'auto_trigger_ladder.ml_anomaly_action must be "operator_approved" or "auto"'
23004
+ );
23005
+ }
23006
+ if (typeof p.source_english !== "string") {
23007
+ throw new CompiledPolicyShapeError("source_english must be a string");
23008
+ }
23009
+ if (typeof p.compiled_at !== "string" || Number.isNaN(Date.parse(p.compiled_at))) {
23010
+ throw new CompiledPolicyShapeError(
23011
+ "compiled_at must be an ISO8601 timestamp string"
23012
+ );
23013
+ }
23014
+ for (const key of slotKeys) {
23015
+ if (!isPolicySlot(key)) {
23016
+ throw new CompiledPolicyShapeError(
23017
+ `slots.${key} is not a recognized policy slot`
23018
+ );
23019
+ }
23020
+ }
23021
+ if (p.egress !== void 0) {
23022
+ checkEgressPolicy(p.egress, "egress");
23023
+ }
23024
+ if (p.budgets !== void 0) {
23025
+ checkBudgetPolicy(p.budgets, "budgets");
23026
+ }
23027
+ if (p.retention !== void 0) {
23028
+ checkRetentionPolicy(p.retention, "retention");
23029
+ }
23030
+ }
23031
+ function checkEgressPolicy(value, path) {
23032
+ if (typeof value !== "object" || value === null) {
23033
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23034
+ }
23035
+ const eg = value;
23036
+ if (!Array.isArray(eg.allowlist)) {
23037
+ throw new CompiledPolicyShapeError(`${path}.allowlist must be an array`);
23038
+ }
23039
+ for (let i = 0; i < eg.allowlist.length; i++) {
23040
+ const rule = eg.allowlist[i];
23041
+ if (typeof rule !== "object" || rule === null) {
23042
+ throw new CompiledPolicyShapeError(
23043
+ `${path}.allowlist[${i}] must be an object`
23044
+ );
23045
+ }
23046
+ const r = rule;
23047
+ if (typeof r.destination !== "string" || r.destination.length === 0) {
23048
+ throw new CompiledPolicyShapeError(
23049
+ `${path}.allowlist[${i}].destination must be a non-empty string`
23050
+ );
23051
+ }
23052
+ if (!Array.isArray(r.methods) || !r.methods.every((m) => typeof m === "string")) {
23053
+ throw new CompiledPolicyShapeError(
23054
+ `${path}.allowlist[${i}].methods must be a string[]`
23055
+ );
23056
+ }
23057
+ }
23058
+ }
23059
+ function checkBudgetLimit(value, path) {
23060
+ if (typeof value !== "object" || value === null) {
23061
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23062
+ }
23063
+ const lim = value;
23064
+ if (typeof lim.amount !== "number" || lim.amount <= 0 || !Number.isFinite(lim.amount)) {
23065
+ throw new CompiledPolicyShapeError(
23066
+ `${path}.amount must be a positive finite number`
23067
+ );
23068
+ }
23069
+ if (!BUDGET_UNITS.includes(lim.unit)) {
23070
+ throw new CompiledPolicyShapeError(
23071
+ `${path}.unit must be one of: ${BUDGET_UNITS.join(", ")}`
23072
+ );
23073
+ }
23074
+ if (lim.soft_warn_threshold !== void 0) {
23075
+ if (typeof lim.soft_warn_threshold !== "number" || lim.soft_warn_threshold <= 0 || lim.soft_warn_threshold >= 1) {
23076
+ throw new CompiledPolicyShapeError(
23077
+ `${path}.soft_warn_threshold must be in (0, 1) when present`
23078
+ );
23079
+ }
23080
+ }
23081
+ }
23082
+ function checkBudgetPolicy(value, path) {
23083
+ if (typeof value !== "object" || value === null) {
23084
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23085
+ }
23086
+ const bp = value;
23087
+ if (bp.daily !== void 0) checkBudgetLimit(bp.daily, `${path}.daily`);
23088
+ if (bp.monthly !== void 0) checkBudgetLimit(bp.monthly, `${path}.monthly`);
23089
+ if (bp.daily === void 0 && bp.monthly === void 0) {
23090
+ throw new CompiledPolicyShapeError(
23091
+ `${path} must have at least one of daily or monthly`
23092
+ );
23093
+ }
23094
+ }
23095
+ function checkRetentionPolicy(value, path) {
23096
+ if (typeof value !== "object" || value === null) {
23097
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23098
+ }
23099
+ const rp = value;
23100
+ if (typeof rp.windows !== "object" || rp.windows === null) {
23101
+ throw new CompiledPolicyShapeError(`${path}.windows must be an object`);
23102
+ }
23103
+ const wins = rp.windows;
23104
+ for (const key of Object.keys(wins)) {
23105
+ if (!isPolicySlot(key)) {
23106
+ throw new CompiledPolicyShapeError(
23107
+ `${path}.windows.${key} is not a recognized policy slot`
23108
+ );
23109
+ }
23110
+ const w = wins[key];
23111
+ if (typeof w !== "object" || w === null) {
23112
+ throw new CompiledPolicyShapeError(
23113
+ `${path}.windows.${key} must be an object`
23114
+ );
23115
+ }
23116
+ const win = w;
23117
+ if (typeof win.max_age_seconds !== "number" || !Number.isInteger(win.max_age_seconds) || win.max_age_seconds <= 0) {
23118
+ throw new CompiledPolicyShapeError(
23119
+ `${path}.windows.${key}.max_age_seconds must be a positive integer`
23120
+ );
23121
+ }
23122
+ if (win.archive !== void 0 && typeof win.archive !== "boolean") {
23123
+ throw new CompiledPolicyShapeError(
23124
+ `${path}.windows.${key}.archive must be boolean when present`
23125
+ );
23126
+ }
23127
+ }
23128
+ }
23129
+ function encodePolicyBlob(policy) {
23130
+ validateCompiledPolicyShape(policy);
23131
+ return toBase64url(canonicalizeToBytes(policy));
23132
+ }
23133
+
23134
+ // src/mesh/envelope.ts
23135
+ init_encoding();
23136
+ init_random();
23137
+
23138
+ // src/mesh/constants.ts
23139
+ var PROTOCOL_VERSION = "0.1";
23140
+ var RESERVED_EVENT_TYPE_PREFIXES = [
23141
+ "EXTENSION_",
23142
+ "cross_fortress_",
23143
+ "multi_master_"
23144
+ ];
23145
+ function isReservedEventType(s) {
23146
+ return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
23147
+ }
23148
+ var RESERVED_EXTENSION_ENVELOPE_KEYS = [
23149
+ "cross_fortress_read_grant",
23150
+ "cross_fortress_read_query",
23151
+ "cross_fortress_read_response",
23152
+ "multi_master_policy_merge",
23153
+ "audit_replication_full_n_way",
23154
+ "auto_promote_canonical_audit",
23155
+ "agent_live_migration"
23156
+ ];
23157
+ function isReservedExtensionKey(k) {
23158
+ return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
23159
+ }
23160
+
23161
+ // src/mesh/trust-root.ts
23162
+ init_encoding();
23163
+ init_identity();
23164
+ init_random();
23165
+
23166
+ // src/mesh/envelope.ts
23167
+ function packSignedEvent(params) {
23168
+ if (isReservedEventType(params.event_type)) {
23169
+ throw new MeshReservedEventTypeError(params.event_type);
23170
+ }
23171
+ const ext = params.extension_envelope ?? {};
23172
+ for (const key of Object.keys(ext)) {
23173
+ if (isReservedExtensionKey(key)) {
23174
+ throw new MeshReservedExtensionKeyError(key);
23175
+ }
23176
+ }
23177
+ const payloadBytes = canonicalizeToBytes(params.payload);
23178
+ const payload_hash = toBase64url(sha256.sha256(payloadBytes));
23179
+ const body = {
23180
+ protocol_version: PROTOCOL_VERSION,
23181
+ event_type: params.event_type,
23182
+ event_id: generateEventId(),
23183
+ emitter_node: params.emitter_node,
23184
+ emitter_principal: params.emitter_principal,
23185
+ fortress_id: params.fortress_id,
23186
+ causal_parents: params.causal_parents ?? [],
23187
+ payload: params.payload,
23188
+ payload_hash,
23189
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
23190
+ monotonic_seq: params.monotonic_seq,
23191
+ extension_envelope: ext
23192
+ };
23193
+ const bytesToSign = canonicalizeToBytes(body);
23194
+ const nodeSig = ed25519.ed25519.sign(bytesToSign, params.node_private_key);
23195
+ const evt = {
23196
+ ...body,
23197
+ node_signature: toBase64url(nodeSig)
23198
+ };
23199
+ if (params.principal_private_key) {
23200
+ const principalSig = ed25519.ed25519.sign(bytesToSign, params.principal_private_key);
23201
+ evt.principal_signature = toBase64url(principalSig);
23202
+ }
23203
+ return evt;
23204
+ }
23205
+ function generateEventId() {
23206
+ const bytes = randomBytes(16);
23207
+ let hex = "";
23208
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
23209
+ return hex;
23210
+ }
23211
+
23212
+ // src/policy-engine/envelope.ts
23213
+ function packPolicyUpdate(params) {
23214
+ validateCompiledPolicyShape(params.policy);
23215
+ const blob = encodePolicyBlob(params.policy);
23216
+ const payload = {
23217
+ agent_id: params.policy.agent_id,
23218
+ policy_version: params.policy.policy_version,
23219
+ policy_blob: blob,
23220
+ ...params.policy.parent_version !== void 0 ? { parent_version: params.policy.parent_version } : {}
23221
+ };
23222
+ return packSignedEvent({
23223
+ event_type: POLICY_UPDATE_EVENT_TYPE,
23224
+ emitter_node: params.emitter_node,
23225
+ emitter_principal: params.emitter_principal,
23226
+ fortress_id: params.policy.fortress_id,
23227
+ causal_parents: params.causal_parents,
23228
+ payload,
23229
+ monotonic_seq: params.monotonic_seq,
23230
+ node_private_key: params.node_private_key,
23231
+ principal_private_key: params.principal_private_key
23232
+ });
23233
+ }
23234
+
23235
+ // src/templates/init.ts
23236
+ function deterministicCompiledAt(version) {
23237
+ const [major, minor, patch] = version.split(".").map(Number);
23238
+ const epoch = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
23239
+ epoch.setUTCDate(epoch.getUTCDate() + (major - 1) * 365 + minor * 30 + patch);
23240
+ return epoch.toISOString();
23241
+ }
23242
+ function buildCompiledPolicyFromTemplate(bundle, params) {
23243
+ const policy = applyChannelTemplate(
23244
+ bundle.metadata.channel,
23245
+ {
23246
+ agent_id: params.agent_id,
23247
+ counterparty: params.counterparty,
23248
+ fortress_id: params.fortress_id,
23249
+ policy_version: params.policy_version
23250
+ }
23251
+ );
23252
+ policy.compiled_at = deterministicCompiledAt(bundle.metadata.version);
23253
+ policy.source_english = bundle.policy_english;
23254
+ if (bundle.defaults.egress.length > 0) {
23255
+ const egress = {
23256
+ allowlist: bundle.defaults.egress.map((e) => ({
23257
+ destination: e.destination,
23258
+ methods: [...e.methods]
23259
+ }))
23260
+ };
23261
+ policy.egress = egress;
23262
+ }
23263
+ const budgets = {};
23264
+ if (bundle.defaults.budgets.daily) {
23265
+ budgets.daily = { ...bundle.defaults.budgets.daily };
23266
+ }
23267
+ if (bundle.defaults.budgets.monthly) {
23268
+ budgets.monthly = { ...bundle.defaults.budgets.monthly };
23269
+ }
23270
+ if (budgets.daily || budgets.monthly) {
23271
+ policy.budgets = budgets;
23272
+ }
23273
+ const windows = bundle.defaults.retention.windows;
23274
+ if (Object.keys(windows).length > 0) {
23275
+ const retention = {
23276
+ windows: {}
23277
+ };
23278
+ for (const [slot, win] of Object.entries(windows)) {
23279
+ if (win) {
23280
+ retention.windows[slot] = { ...win };
23281
+ }
23282
+ }
23283
+ policy.retention = retention;
23284
+ }
23285
+ const classes = bundle.commitments.shapes.map((s) => s.commitment_class);
23286
+ for (const cls of classes) {
23287
+ if (!policy.capabilities.concordia_commitment_classes.includes(cls)) {
23288
+ policy.capabilities.concordia_commitment_classes.push(cls);
23289
+ }
23290
+ }
23291
+ policy.capabilities.concordia_commitment_classes.sort();
23292
+ validateCompiledPolicyShape(policy);
23293
+ return policy;
23294
+ }
23295
+ function initTemplate(params) {
23296
+ const bundle = getTemplate2(params.template_name);
23297
+ if (!bundle) {
23298
+ throw new Error(`unknown template: ${params.template_name}`);
23299
+ }
23300
+ const compiled = buildCompiledPolicyFromTemplate(bundle, {
23301
+ agent_id: params.agent_id,
23302
+ fortress_id: params.fortress_id,
23303
+ counterparty: params.counterparty ?? "*",
23304
+ policy_version: params.policy_version ?? 1
23305
+ });
23306
+ const signed_event = packPolicyUpdate({
23307
+ policy: compiled,
23308
+ emitter_node: params.emitter_node,
23309
+ emitter_principal: params.emitter_principal,
23310
+ monotonic_seq: params.monotonic_seq,
23311
+ node_private_key: params.node_private_key,
23312
+ principal_private_key: params.principal_private_key
23313
+ });
23314
+ return { compiled, signed_event, bundle };
23315
+ }
23316
+ var DEFAULT_STORAGE_DIR = ".sanctuary";
23317
+ var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
23318
+ function keychainServiceFor(storagePath, home = os.homedir()) {
23319
+ const defaultPath = path.join(home, DEFAULT_STORAGE_DIR);
23320
+ if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
23321
+ const digest = sha256.sha256(Buffer.from(storagePath, "utf-8"));
23322
+ const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
23323
+ return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
23324
+ }
23325
+ var RUNTIME_FILE_NAME = "runtime.json";
23326
+ function runtimePath(storagePath) {
23327
+ return path.join(storagePath, RUNTIME_FILE_NAME);
23328
+ }
23329
+ async function readTenantRuntime(storagePath) {
23330
+ try {
23331
+ const raw = await promises.readFile(runtimePath(storagePath), "utf-8");
23332
+ const parsed = JSON.parse(raw);
23333
+ if (typeof parsed.dashboard_port !== "number" || typeof parsed.pid !== "number" || typeof parsed.started_at !== "string" || typeof parsed.version !== "string" || typeof parsed.dashboard_host !== "string" || typeof parsed.mode !== "string") {
23334
+ return null;
23335
+ }
23336
+ const state = {
23337
+ version: parsed.version,
23338
+ pid: parsed.pid,
23339
+ started_at: parsed.started_at,
23340
+ dashboard_host: parsed.dashboard_host,
23341
+ dashboard_port: parsed.dashboard_port,
23342
+ mode: parsed.mode
23343
+ };
23344
+ if (typeof parsed.webhook_callback_port === "number") {
23345
+ state.webhook_callback_port = parsed.webhook_callback_port;
23346
+ }
23347
+ if (typeof parsed.webhook_callback_host === "string") {
23348
+ state.webhook_callback_host = parsed.webhook_callback_host;
23349
+ }
23350
+ return state;
23351
+ } catch {
23352
+ return null;
23353
+ }
23354
+ }
23355
+
23356
+ // src/cli/agents/discovery.ts
23357
+ var EXTRAS_FILE_NAME = "agents-extra.json";
23358
+ async function isTenantDir(path$1) {
23359
+ const [hasState, hasProfile, hasFallback] = await Promise.all([
23360
+ dirExists(path.join(path$1, "state")),
23361
+ fileExists2(path.join(path$1, "cocoon-profile.json")),
23362
+ fileExists2(path.join(path$1, "passphrase.enc"))
23363
+ ]);
23364
+ const initialized = hasState;
23365
+ let passphraseStatus;
23366
+ if (hasFallback) passphraseStatus = "fallback-file";
23367
+ else if (hasProfile || hasState) passphraseStatus = "keychain";
23368
+ else passphraseStatus = "not-initialized";
23369
+ return { initialized, hasProfile, passphraseStatus };
23370
+ }
23371
+ async function dirExists(path) {
23372
+ try {
23373
+ const s = await promises.stat(path);
23374
+ return s.isDirectory();
23375
+ } catch {
23376
+ return false;
23377
+ }
23378
+ }
23379
+ async function fileExists2(path) {
23380
+ try {
23381
+ const s = await promises.stat(path);
23382
+ return s.isFile();
23383
+ } catch {
23384
+ return false;
23385
+ }
23386
+ }
23387
+ async function newestAuditMtime(storagePath) {
23388
+ const auditDir = path.join(storagePath, "state", "_audit");
23389
+ let entries = [];
23390
+ try {
23391
+ entries = await promises.readdir(auditDir);
23392
+ } catch {
23393
+ return null;
23394
+ }
23395
+ let newest = 0;
23396
+ for (const name of entries) {
23397
+ try {
23398
+ const s = await promises.stat(path.join(auditDir, name));
23399
+ if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
23400
+ } catch {
23401
+ }
23402
+ }
23403
+ if (newest === 0) return null;
23404
+ return new Date(newest).toISOString();
23405
+ }
23406
+ async function readExtraPaths(root, env) {
23407
+ const out = [];
23408
+ const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
23409
+ if (fromEnv && fromEnv.length > 0) {
23410
+ for (const part of fromEnv.split(":")) {
23411
+ const trimmed = part.trim();
23412
+ if (trimmed.length > 0) out.push(path.resolve(trimmed));
23413
+ }
23414
+ }
23415
+ try {
23416
+ const raw = await promises.readFile(path.join(root, EXTRAS_FILE_NAME), "utf-8");
23417
+ const parsed = JSON.parse(raw);
23418
+ if (Array.isArray(parsed)) {
23419
+ for (const p of parsed) {
23420
+ if (typeof p === "string" && p.trim().length > 0) out.push(path.resolve(p));
23421
+ }
23422
+ }
23423
+ } catch {
23424
+ }
23425
+ return Array.from(new Set(out));
23426
+ }
23427
+ async function describeTenant(name, storagePath, home) {
23428
+ const exists = await dirExists(storagePath);
23429
+ if (!exists) return null;
23430
+ const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
23431
+ if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
23432
+ return null;
23433
+ }
23434
+ const last_activity = await newestAuditMtime(storagePath);
23435
+ const runtime = await readTenantRuntime(storagePath);
23436
+ return {
23437
+ name,
23438
+ storage_path: storagePath,
23439
+ exists: true,
23440
+ initialized,
23441
+ has_cocoon_profile: hasProfile,
23442
+ keychain_service: keychainServiceFor(storagePath, home),
23443
+ passphrase_status: passphraseStatus,
23444
+ last_activity,
23445
+ runtime
23446
+ };
23447
+ }
23448
+ async function discoverTenants(options = {}) {
23449
+ const home = options.home ?? os.homedir();
23450
+ const env = options.env ?? process.env;
23451
+ const root = options.root ?? path.join(home, DEFAULT_STORAGE_DIR);
23452
+ const tenants = [];
23453
+ const rootTenant = await describeTenant("default", root, home);
23454
+ if (rootTenant) tenants.push(rootTenant);
23455
+ let children = [];
23456
+ try {
23457
+ children = await promises.readdir(root);
23458
+ } catch {
23459
+ }
23460
+ for (const child of children) {
23461
+ const childPath = path.join(root, child);
23462
+ if (child.startsWith(".")) continue;
23463
+ if (child === "state" || child === "backup" || child === "config") continue;
23464
+ const s = await promises.stat(childPath).catch(() => null);
23465
+ if (!s || !s.isDirectory()) continue;
23466
+ const desc = await describeTenant(child, childPath, home);
23467
+ if (desc) tenants.push(desc);
23468
+ }
23469
+ const extras = await readExtraPaths(root, env);
23470
+ for (const extra of extras) {
23471
+ if (tenants.some((t) => t.storage_path === extra)) continue;
23472
+ const desc = await describeTenant(path.basename(extra), extra, home);
23473
+ if (desc) tenants.push(desc);
23474
+ }
23475
+ tenants.sort((a, b) => {
23476
+ if (a.name === "default") return -1;
23477
+ if (b.name === "default") return 1;
23478
+ return a.name.localeCompare(b.name);
23479
+ });
23480
+ return tenants;
23481
+ }
23482
+ async function findTenant(name, options = {}) {
23483
+ const tenants = await discoverTenants(options);
23484
+ return tenants.find((t) => t.name === name) ?? null;
23485
+ }
23486
+
22223
23487
  // src/dashboard/api.ts
22224
23488
  function constantTimeEquals(a, b) {
22225
23489
  if (a.length !== b.length) return false;
@@ -22250,6 +23514,22 @@ function writeJSON(res, status, payload) {
22250
23514
  });
22251
23515
  res.end(JSON.stringify(payload));
22252
23516
  }
23517
+ async function readJSONBody(req) {
23518
+ const chunks = [];
23519
+ let size = 0;
23520
+ const MAX = 256 * 1024;
23521
+ for await (const chunk of req) {
23522
+ size += chunk.length;
23523
+ if (size > MAX) throw new Error("request body too large");
23524
+ chunks.push(chunk);
23525
+ }
23526
+ const body = Buffer.concat(chunks).toString("utf-8");
23527
+ if (!body) return {};
23528
+ return JSON.parse(body);
23529
+ }
23530
+ function generateEphemeralKey() {
23531
+ return new Uint8Array(crypto.randomBytes(32));
23532
+ }
22253
23533
  function writeText(res, status, body, contentType = "text/plain") {
22254
23534
  res.writeHead(status, {
22255
23535
  "Content-Type": contentType,
@@ -22302,6 +23582,102 @@ async function handleRequest(deps, req, res) {
22302
23582
  await handleStream(deps, res);
22303
23583
  return true;
22304
23584
  }
23585
+ if (method === "GET" && path === "/api/templates") {
23586
+ try {
23587
+ const templates = listTemplates();
23588
+ writeJSON(res, 200, { templates });
23589
+ } catch (err) {
23590
+ writeJSON(res, 500, {
23591
+ error: "template_load_failed",
23592
+ message: err.message
23593
+ });
23594
+ }
23595
+ return true;
23596
+ }
23597
+ const templateMatch = /^\/api\/templates\/([^/]+)$/.exec(path);
23598
+ if (method === "GET" && templateMatch) {
23599
+ const name = decodeURIComponent(templateMatch[1]);
23600
+ try {
23601
+ const entry = getTemplateEntry(name);
23602
+ if (!entry) {
23603
+ writeJSON(res, 404, { error: "template_not_found", name });
23604
+ return true;
23605
+ }
23606
+ writeJSON(res, 200, entry);
23607
+ } catch (err) {
23608
+ writeJSON(res, 500, {
23609
+ error: "template_load_failed",
23610
+ message: err.message
23611
+ });
23612
+ }
23613
+ return true;
23614
+ }
23615
+ const initMatch = /^\/api\/templates\/([^/]+)\/init$/.exec(path);
23616
+ if (method === "POST" && initMatch) {
23617
+ const name = decodeURIComponent(initMatch[1]);
23618
+ try {
23619
+ const bundle = getTemplate2(name);
23620
+ if (!bundle) {
23621
+ writeJSON(res, 404, { error: "template_not_found", name });
23622
+ return true;
23623
+ }
23624
+ const body = await readJSONBody(req);
23625
+ if (!body.agent_name || typeof body.agent_name !== "string") {
23626
+ writeJSON(res, 400, {
23627
+ error: "validation_error",
23628
+ message: "agent_name is required and must be a string"
23629
+ });
23630
+ return true;
23631
+ }
23632
+ if (!/^[a-zA-Z0-9_-]+$/.test(body.agent_name)) {
23633
+ writeJSON(res, 400, {
23634
+ error: "validation_error",
23635
+ message: "agent_name must contain only alphanumeric characters, hyphens, and underscores"
23636
+ });
23637
+ return true;
23638
+ }
23639
+ const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
23640
+ const tenant = await findTenant(agentId);
23641
+ if (!tenant) return false;
23642
+ return tenant.initialized || tenant.has_cocoon_profile;
23643
+ });
23644
+ if (!await isAgentWrapped(body.agent_name)) {
23645
+ writeJSON(res, 400, {
23646
+ error: "orphan_agent_id",
23647
+ message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
23648
+ });
23649
+ return true;
23650
+ }
23651
+ const nodeId = deps.nodeId ?? "dashboard-node";
23652
+ const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
23653
+ const principalId = deps.principalId ?? "dashboard-principal";
23654
+ const fortressId = deps.fortressId ?? "default";
23655
+ const result = initTemplate({
23656
+ template_name: name,
23657
+ agent_id: body.agent_name,
23658
+ fortress_id: fortressId,
23659
+ counterparty: "*",
23660
+ policy_version: 1,
23661
+ emitter_node: nodeId,
23662
+ emitter_principal: principalId,
23663
+ monotonic_seq: 1,
23664
+ node_private_key: nodePrivateKey
23665
+ });
23666
+ writeJSON(res, 200, {
23667
+ agent_id: body.agent_name,
23668
+ signed_event_id: result.signed_event.event_id,
23669
+ policy_version: result.compiled.policy_version,
23670
+ template_name: name,
23671
+ attestation_panel_url: `/console#agent_roster`
23672
+ });
23673
+ } catch (err) {
23674
+ writeJSON(res, 500, {
23675
+ error: "template_init_failed",
23676
+ message: err.message
23677
+ });
23678
+ }
23679
+ return true;
23680
+ }
22305
23681
  return false;
22306
23682
  }
22307
23683
  async function handleStream(deps, res) {
@@ -22380,11 +23756,11 @@ async function startDashboardServer(options) {
22380
23756
  }
22381
23757
  }
22382
23758
  });
22383
- await new Promise((resolve, reject) => {
23759
+ await new Promise((resolve2, reject) => {
22384
23760
  server.once("error", reject);
22385
23761
  server.listen(port, host, () => {
22386
23762
  server.off("error", reject);
22387
- resolve();
23763
+ resolve2();
22388
23764
  });
22389
23765
  });
22390
23766
  const actualPort = (() => {
@@ -22397,8 +23773,8 @@ async function startDashboardServer(options) {
22397
23773
  url,
22398
23774
  port: actualPort,
22399
23775
  host,
22400
- stop: () => new Promise((resolve, reject) => {
22401
- server.close((err) => err ? reject(err) : resolve());
23776
+ stop: () => new Promise((resolve2, reject) => {
23777
+ server.close((err) => err ? reject(err) : resolve2());
22402
23778
  }),
22403
23779
  publish,
22404
23780
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
@@ -22999,7 +24375,7 @@ async function createSanctuaryServer(options) {
22999
24375
  clientManager.configure(enabledServers).catch((err) => {
23000
24376
  console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
23001
24377
  });
23002
- await new Promise((resolve) => setTimeout(resolve, 2e3));
24378
+ await new Promise((resolve2) => setTimeout(resolve2, 2e3));
23003
24379
  const proxiedTools = proxyRouter.getProxiedTools();
23004
24380
  if (proxiedTools.length > 0) {
23005
24381
  allTools.push(...proxiedTools);