@sanctuary-framework/mcp-server 0.10.5 → 1.0.0-rc.1

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
@@ -4,7 +4,7 @@ import { sha256 } from '@noble/hashes/sha256';
4
4
  import { hmac } from '@noble/hashes/hmac';
5
5
  import { RistrettoPoint, ed25519 } from '@noble/curves/ed25519';
6
6
  import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, access } from 'fs/promises';
7
- import { join } from 'path';
7
+ import { join, dirname } from 'path';
8
8
  import { platform, homedir } from 'os';
9
9
  import { createRequire } from 'module';
10
10
  import { argon2id } from 'hash-wasm';
@@ -14,10 +14,11 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprot
14
14
  import { createServer as createServer$2 } from 'http';
15
15
  import { createServer as createServer$1 } from 'https';
16
16
  import { exec, execSync } from 'child_process';
17
- import { statSync } from 'fs';
17
+ import { statSync, existsSync, readFileSync } from 'fs';
18
18
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
19
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
20
20
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
21
+ import { fileURLToPath } from 'url';
21
22
 
22
23
  var __defProp = Object.defineProperty;
23
24
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -813,7 +814,8 @@ var RESERVED_NAMESPACE_PREFIXES = [
813
814
  "_handshake",
814
815
  "_shr",
815
816
  "_sovereignty_profile",
816
- "_context_gate_policies"
817
+ "_context_gate_policies",
818
+ "_fortress_mode"
817
819
  ];
818
820
  var StateStore = class _StateStore {
819
821
  storage;
@@ -1387,7 +1389,8 @@ var RESERVED_NAMESPACE_PREFIXES2 = [
1387
1389
  "_handshake",
1388
1390
  "_shr",
1389
1391
  "_sovereignty_profile",
1390
- "_context_gate_policies"
1392
+ "_context_gate_policies",
1393
+ "_fortress_mode"
1391
1394
  ];
1392
1395
  function getReservedNamespaceViolation(namespace) {
1393
1396
  for (const prefix of RESERVED_NAMESPACE_PREFIXES2) {
@@ -4140,8 +4143,12 @@ var DEFAULT_POLICY = {
4140
4143
  // Clears all runtime governance state — always requires approval
4141
4144
  "sanctuary_bootstrap",
4142
4145
  // Creates new Ed25519 identity + publishes — always requires approval
4143
- "sanctuary_export_identity_bundle"
4146
+ "sanctuary_export_identity_bundle",
4144
4147
  // Exports portable identity — always requires approval
4148
+ // WP-MVP-2 Operator Console: federation-node-join requires explicit
4149
+ // operator confirmation per Key 8. No auto-approve path. The console's
4150
+ // JoinApprover drives this gate via `MeshConsoleClient.makeJoinApprover`.
4151
+ "federation_node_join"
4145
4152
  ],
4146
4153
  tier2_anomaly: DEFAULT_TIER2,
4147
4154
  tier3_always_allow: [
@@ -6400,6 +6407,28 @@ function generateDashboardHTML(options) {
6400
6407
  </div>
6401
6408
  </div>
6402
6409
 
6410
+ <!--
6411
+ Mesh Health panel (WP-MVP-3 Follow-up #3).
6412
+ Subscribes to the existing /events SSE channel \u2014 no new transport.
6413
+ Render shape: per-node row with presence + flags + rollup.
6414
+ On open alert: list inline with operator-decision CTAs (rollback /
6415
+ split-brain / canonical-audit promotion).
6416
+ On post-recovery prompt: render rotation-prompt overlay with
6417
+ broker-credential list + "rotate now" buttons (rotation flow itself
6418
+ is the existing v0.10.x broker rotation surface).
6419
+ -->
6420
+ <div class="mesh-health-panel" id="mesh-health-panel">
6421
+ <div class="panel-header">
6422
+ <div class="panel-title">Mesh Health</div>
6423
+ <span class="card-value" id="mesh-health-updated-at" style="font-size: 11px; color: var(--text-secondary);">\u2014</span>
6424
+ </div>
6425
+ <div id="mesh-health-rows" class="mesh-health-rows">
6426
+ <div class="empty-state">Waiting for mesh health data\u2026</div>
6427
+ </div>
6428
+ <div id="mesh-health-alerts" class="mesh-health-alerts" style="margin-top: 12px;"></div>
6429
+ <div id="mesh-post-recovery-prompt" class="mesh-post-recovery-prompt" style="margin-top: 12px; display: none;"></div>
6430
+ </div>
6431
+
6403
6432
  <!-- Sovereignty Profile Panel -->
6404
6433
  <div class="profile-panel" id="sovereignty-profile-panel">
6405
6434
  <div class="panel-header">
@@ -6602,6 +6631,11 @@ function generateDashboardHTML(options) {
6602
6631
  // SEC-038: Do NOT embed the long-lived auth token in page source.
6603
6632
  // Use only the session token stored in sessionStorage by the login flow.
6604
6633
  const AUTH_TOKEN = sessionStorage.getItem('authToken') || '';
6634
+ // v0.10.6: server-baked flag mirroring _autoAuthLocalhost. When true,
6635
+ // the init-time auth gate does NOT redirect to '/' on empty AUTH_TOKEN,
6636
+ // because the server already admitted this loopback caller without a
6637
+ // bearer token. See dashboard-html.ts generateDashboardHTML() doc.
6638
+ const LOOPBACK_AUTH = ${JSON.stringify(options.loopbackAutoAuth === true)};
6605
6639
  const TIMEOUT_SECONDS = ${options.timeoutSeconds};
6606
6640
  const API_BASE = '';
6607
6641
 
@@ -7104,12 +7138,96 @@ function generateDashboardHTML(options) {
7104
7138
  loadProxyServers();
7105
7139
  });
7106
7140
 
7141
+ // \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
7142
+ // The federation FailureModeDetector pushes per-tick snapshots and
7143
+ // per-detection alerts via the existing /events channel. Renders are
7144
+ // best-effort \u2014 if the panel DOM is absent (older HTML cache), we
7145
+ // silently skip rather than crashing.
7146
+ eventSource.addEventListener('mesh-health', (e) => {
7147
+ try {
7148
+ const snap = JSON.parse(e.data);
7149
+ renderMeshHealth(snap);
7150
+ } catch (err) { console.error('mesh-health render failed', err); }
7151
+ });
7152
+ eventSource.addEventListener('mesh-failure-mode-alert', (e) => {
7153
+ try {
7154
+ const alert = JSON.parse(e.data);
7155
+ appendMeshAlert(alert);
7156
+ } catch (err) { console.error('mesh alert render failed', err); }
7157
+ });
7158
+ eventSource.addEventListener('mesh-post-recovery-prompt', (e) => {
7159
+ try {
7160
+ const prompt = JSON.parse(e.data);
7161
+ renderMeshPostRecoveryPrompt(prompt);
7162
+ } catch (err) { console.error('mesh post-recovery render failed', err); }
7163
+ });
7164
+
7107
7165
  eventSource.onerror = () => {
7108
7166
  console.error('SSE error');
7109
7167
  setTimeout(setupSSE, 5000);
7110
7168
  };
7111
7169
  }
7112
7170
 
7171
+ // \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
7172
+ function renderMeshHealth(snap) {
7173
+ const updatedAt = document.getElementById('mesh-health-updated-at');
7174
+ const rows = document.getElementById('mesh-health-rows');
7175
+ if (!rows || !snap) return;
7176
+ if (updatedAt) updatedAt.textContent = formatTime(snap.generated_at);
7177
+ const nodes = Array.isArray(snap.nodes) ? snap.nodes : [];
7178
+ if (nodes.length === 0) {
7179
+ rows.innerHTML = '<div class="empty-state">No mesh nodes seen yet.</div>';
7180
+ return;
7181
+ }
7182
+ rows.innerHTML = nodes.map((n) => {
7183
+ const flags = (n.flags || []).map((f) => '<span class="mesh-flag">' + esc(f) + '</span>').join(' ');
7184
+ return '<div class="mesh-row" data-rollup="' + esc(n.rollup) + '">' +
7185
+ '<span class="mesh-node-id">' + esc(n.node_id) + '</span>' +
7186
+ '<span class="mesh-presence">' + esc(n.presence) + '</span>' +
7187
+ '<span class="mesh-rollup">' + esc(n.rollup) + '</span>' +
7188
+ '<span class="mesh-flags">' + flags + '</span>' +
7189
+ '</div>';
7190
+ }).join('');
7191
+ const alertsBox = document.getElementById('mesh-health-alerts');
7192
+ if (alertsBox && Array.isArray(snap.open_alerts)) {
7193
+ alertsBox.innerHTML = snap.open_alerts.map((a) =>
7194
+ '<div class="mesh-alert" data-mode="' + esc(a.mode) + '">' +
7195
+ '<div class="mesh-alert-mode">' + esc(a.mode) + ' \u2014 ' + esc(a.target_node) + '</div>' +
7196
+ '<div class="mesh-alert-message">' + esc(a.message) + '</div>' +
7197
+ '</div>'
7198
+ ).join('');
7199
+ }
7200
+ }
7201
+
7202
+ function appendMeshAlert(alert) {
7203
+ const alertsBox = document.getElementById('mesh-health-alerts');
7204
+ if (!alertsBox || !alert) return;
7205
+ const html = '<div class="mesh-alert" data-mode="' + esc(alert.mode) + '">' +
7206
+ '<div class="mesh-alert-mode">' + esc(alert.mode) + ' \u2014 ' + esc(alert.target_node) + '</div>' +
7207
+ '<div class="mesh-alert-message">' + esc(alert.message) + '</div>' +
7208
+ '</div>';
7209
+ alertsBox.insertAdjacentHTML('afterbegin', html);
7210
+ }
7211
+
7212
+ function renderMeshPostRecoveryPrompt(prompt) {
7213
+ const box = document.getElementById('mesh-post-recovery-prompt');
7214
+ if (!box || !prompt) return;
7215
+ box.style.display = 'block';
7216
+ const creds = Array.isArray(prompt.credentials) ? prompt.credentials : [];
7217
+ box.innerHTML = '<div class="mesh-rotation-banner">' +
7218
+ '<strong>Post-rotation hygiene:</strong> we just rotated your fortress root key. ' +
7219
+ 'Rotate externally-scoped broker credentials that third parties may still associate with ' +
7220
+ 'the pre-rotation key material.' +
7221
+ '</div>' +
7222
+ '<ul class="mesh-rotation-list">' +
7223
+ creds.map((c) => '<li>' +
7224
+ '<span class="mesh-cred-name">' + esc(c.secret_name) + '</span>' +
7225
+ '<button class="mesh-cred-rotate" data-secret="' + esc(c.secret_name) + '">' +
7226
+ 'rotate now</button>' +
7227
+ '</li>').join('') +
7228
+ '</ul>';
7229
+ }
7230
+
7113
7231
  // Activity Feed
7114
7232
  function addActivityItem(item) {
7115
7233
  activityLog.unshift(item);
@@ -7709,7 +7827,13 @@ function generateDashboardHTML(options) {
7709
7827
 
7710
7828
  // Initialize
7711
7829
  async function initialize() {
7712
- if (!AUTH_TOKEN) {
7830
+ // v0.10.6: gate on BOTH sessionStorage and the server-baked loopback
7831
+ // auto-auth mirror. Pre-fix, a fresh loopback tab had empty
7832
+ // sessionStorage.authToken AND was admitted by the server via
7833
+ // _autoAuthLocalhost \u2014 this single-operand gate redirected to '/'
7834
+ // which reloaded the same page, which redirected again, infinitely.
7835
+ // See generateDashboardHTML() header comment for full threat model.
7836
+ if (!AUTH_TOKEN && !LOOPBACK_AUTH) {
7713
7837
  redirectToLogin();
7714
7838
  return;
7715
7839
  }
@@ -8735,7 +8859,11 @@ var DashboardApprovalChannel = class {
8735
8859
  this.sessionTTLMs = isLocalhost ? SESSION_TTL_LOCAL_MS : SESSION_TTL_REMOTE_MS;
8736
8860
  this.dashboardHTML = generateDashboardHTML({
8737
8861
  timeoutSeconds: config.timeout_seconds,
8738
- serverVersion: SANCTUARY_VERSION
8862
+ serverVersion: SANCTUARY_VERSION,
8863
+ // Construction-time default; real value is set by setAutoAuthLocalhost()
8864
+ // below (which regenerates this HTML). Default false preserves the
8865
+ // pre-v0.10.6 remote-deployment behavior when auto-auth is not enabled.
8866
+ loopbackAutoAuth: this._autoAuthLocalhost
8739
8867
  });
8740
8868
  this.loginHTML = generateLoginHTML({ serverVersion: SANCTUARY_VERSION });
8741
8869
  this.sessionCleanupTimer = setInterval(() => this.cleanupSessions(), 6e4);
@@ -8771,6 +8899,11 @@ var DashboardApprovalChannel = class {
8771
8899
  */
8772
8900
  setAutoAuthLocalhost(enabled) {
8773
8901
  this._autoAuthLocalhost = enabled;
8902
+ this.dashboardHTML = generateDashboardHTML({
8903
+ timeoutSeconds: this.config.timeout_seconds,
8904
+ serverVersion: SANCTUARY_VERSION,
8905
+ loopbackAutoAuth: this._autoAuthLocalhost
8906
+ });
8774
8907
  }
8775
8908
  /**
8776
8909
  * v0.10.2: is this request from a loopback interface? We treat the
@@ -9655,6 +9788,27 @@ data: ${JSON.stringify(data)}
9655
9788
  broadcastProtectionStatus(data) {
9656
9789
  this.broadcastSSE("protection-status", data);
9657
9790
  }
9791
+ // ── Mesh-health surface (WP-MVP-3 Follow-up #3) ─────────────────────
9792
+ //
9793
+ // The federation FailureModeDetector pushes per-tick health snapshots and
9794
+ // per-detection alerts here; the existing /events SSE channel transports
9795
+ // them to the browser. No new transport.
9796
+ //
9797
+ // Spec §8 + §9. Spawn-prompt acceptance criterion 7: "Mesh Health dashboard
9798
+ // panel renders via existing SSE /events channel — no new transport. Every
9799
+ // state transition produces an observable SSE event."
9800
+ /** Push a Mesh Health snapshot (full re-render trigger on the client). */
9801
+ broadcastMeshHealth(snapshot) {
9802
+ this.broadcastSSE("mesh-health", snapshot);
9803
+ }
9804
+ /** Push a single failure-mode alert (incremental client update). */
9805
+ broadcastMeshFailureModeAlert(alert) {
9806
+ this.broadcastSSE("mesh-failure-mode-alert", alert);
9807
+ }
9808
+ /** Push a post-recovery prompt update (master rotation hygiene flow). */
9809
+ broadcastMeshPostRecoveryPrompt(prompt) {
9810
+ this.broadcastSSE("mesh-post-recovery-prompt", prompt);
9811
+ }
9658
9812
  /**
9659
9813
  * Open a URL in the system's default browser.
9660
9814
  * Cross-platform: macOS (open), Linux (xdg-open), Windows (start).
@@ -12985,7 +13139,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
12985
13139
  const now = (/* @__PURE__ */ new Date()).toISOString();
12986
13140
  const canonicalBytes = canonicalize(outcome);
12987
13141
  const canonicalString = new TextDecoder().decode(canonicalBytes);
12988
- const sha2565 = createCommitment(canonicalString);
13142
+ const sha2567 = createCommitment(canonicalString);
12989
13143
  let pedersenData;
12990
13144
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
12991
13145
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -12997,7 +13151,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
12997
13151
  const commitmentPayload = {
12998
13152
  bridge_commitment_id: commitmentId,
12999
13153
  session_id: outcome.session_id,
13000
- sha256_commitment: sha2565.commitment,
13154
+ sha256_commitment: sha2567.commitment,
13001
13155
  terms_hash: outcome.terms_hash,
13002
13156
  committer_did: identity.did,
13003
13157
  committed_at: now,
@@ -13008,8 +13162,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13008
13162
  return {
13009
13163
  bridge_commitment_id: commitmentId,
13010
13164
  session_id: outcome.session_id,
13011
- sha256_commitment: sha2565.commitment,
13012
- blinding_factor: sha2565.blinding_factor,
13165
+ sha256_commitment: sha2567.commitment,
13166
+ blinding_factor: sha2567.blinding_factor,
13013
13167
  committer_did: identity.did,
13014
13168
  signature: toBase64url(signature),
13015
13169
  pedersen_commitment: pedersenData,
@@ -21533,8 +21687,15 @@ function l3Card(l3) {
21533
21687
  <div><dt>Proofs today</dt><dd>${escHtml(l3.proofs_today)}</dd></div>`
21534
21688
  );
21535
21689
  }
21690
+ var VERASCORE_CLAIM_URL = "https://www.verascore.ai";
21536
21691
  function l4Card(l4) {
21537
- 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>`;
21692
+ let score;
21693
+ if (l4.score != null) {
21694
+ score = `<div class="score-block"><span class="score-value">${escHtml(l4.score)}</span><span class="score-label">Verascore</span></div>`;
21695
+ } else {
21696
+ const claimText = l4.claim_cta ?? "Claim your profile at verascore.ai";
21697
+ score = `<div class="claim-block"><a class="claim-link" href="${VERASCORE_CLAIM_URL}" target="_blank" rel="noopener noreferrer">${escHtml(claimText)}</a></div>`;
21698
+ }
21538
21699
  return layerCard(
21539
21700
  l4,
21540
21701
  `<div class="layer-cta">${score}</div>${l4EvidenceBlock(l4)}`
@@ -22197,6 +22358,961 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
22197
22358
  </html>`;
22198
22359
  }
22199
22360
 
22361
+ // src/policy-engine/constants.ts
22362
+ var COMPILED_POLICY_SCHEMA_VERSION = "0.1";
22363
+ var POLICY_UPDATE_EVENT_TYPE = "policy_update";
22364
+ var POLICY_SLOTS = [
22365
+ "memory",
22366
+ "credentials",
22367
+ "plans",
22368
+ "outputs"
22369
+ ];
22370
+ function isPolicySlot(value) {
22371
+ return typeof value === "string" && POLICY_SLOTS.includes(value);
22372
+ }
22373
+ var CHANNEL_TEMPLATE_IDS = [
22374
+ "read-outputs-only",
22375
+ "bidirectional-sync",
22376
+ "credential-share-scoped",
22377
+ "plan-inspect-read-only",
22378
+ "escrow-handoff"
22379
+ ];
22380
+ var BUDGET_UNITS = ["tokens", "usd"];
22381
+
22382
+ // src/templates/registry.ts
22383
+ var TEMPLATE_NAMES = [
22384
+ "research-assistant",
22385
+ "coding-assistant",
22386
+ "ops-runner",
22387
+ "planner",
22388
+ "handoff-coordinator",
22389
+ "x-miner",
22390
+ "github-miner"
22391
+ ];
22392
+ function isTemplateName(value) {
22393
+ return typeof value === "string" && TEMPLATE_NAMES.includes(value);
22394
+ }
22395
+ var TemplateValidationError = class extends Error {
22396
+ constructor(templateName, message) {
22397
+ super(`template "${templateName}": ${message}`);
22398
+ this.name = "TemplateValidationError";
22399
+ }
22400
+ };
22401
+ function validateMetadata(name, data) {
22402
+ if (typeof data !== "object" || data === null) {
22403
+ throw new TemplateValidationError(name, "template.json must be an object");
22404
+ }
22405
+ const d = data;
22406
+ if (typeof d.name !== "string" || d.name !== name) {
22407
+ throw new TemplateValidationError(name, `template.json name must be "${name}"`);
22408
+ }
22409
+ if (typeof d.version !== "string" || !/^\d+\.\d+\.\d+$/.test(d.version)) {
22410
+ throw new TemplateValidationError(name, "template.json version must be semver");
22411
+ }
22412
+ if (typeof d.channel !== "string" || !CHANNEL_TEMPLATE_IDS.includes(d.channel)) {
22413
+ throw new TemplateValidationError(
22414
+ name,
22415
+ `template.json channel must be one of: ${CHANNEL_TEMPLATE_IDS.join(", ")}`
22416
+ );
22417
+ }
22418
+ if (typeof d.tier !== "string" || !["A", "B", "C"].includes(d.tier)) {
22419
+ throw new TemplateValidationError(name, "template.json tier must be A, B, or C");
22420
+ }
22421
+ if (typeof d.target_archetype !== "string" || d.target_archetype.length === 0) {
22422
+ throw new TemplateValidationError(name, "template.json target_archetype must be a non-empty string");
22423
+ }
22424
+ if (typeof d.description !== "string" || d.description.length === 0) {
22425
+ throw new TemplateValidationError(name, "template.json description must be a non-empty string");
22426
+ }
22427
+ }
22428
+ function validateDefaults(name, data) {
22429
+ if (typeof data !== "object" || data === null) {
22430
+ throw new TemplateValidationError(name, "defaults.json must be an object");
22431
+ }
22432
+ const d = data;
22433
+ if (!Array.isArray(d.egress)) {
22434
+ throw new TemplateValidationError(name, "defaults.json egress must be an array");
22435
+ }
22436
+ for (const entry of d.egress) {
22437
+ if (typeof entry !== "object" || entry === null) {
22438
+ throw new TemplateValidationError(name, "defaults.json egress entry must be an object");
22439
+ }
22440
+ const e = entry;
22441
+ if (typeof e.destination !== "string" || e.destination.length === 0) {
22442
+ throw new TemplateValidationError(name, "egress entry destination must be a non-empty string");
22443
+ }
22444
+ if (!Array.isArray(e.methods)) {
22445
+ throw new TemplateValidationError(name, "egress entry methods must be an array");
22446
+ }
22447
+ }
22448
+ if (typeof d.budgets !== "object" || d.budgets === null) {
22449
+ throw new TemplateValidationError(name, "defaults.json budgets must be an object");
22450
+ }
22451
+ if (typeof d.retention !== "object" || d.retention === null) {
22452
+ throw new TemplateValidationError(name, "defaults.json retention must be an object");
22453
+ }
22454
+ const ret = d.retention;
22455
+ if (typeof ret.windows !== "object" || ret.windows === null) {
22456
+ throw new TemplateValidationError(name, "defaults.json retention.windows must be an object");
22457
+ }
22458
+ }
22459
+ function validateCommitments(name, data) {
22460
+ if (typeof data !== "object" || data === null) {
22461
+ throw new TemplateValidationError(name, "commitments.json must be an object");
22462
+ }
22463
+ const d = data;
22464
+ if (!Array.isArray(d.shapes)) {
22465
+ throw new TemplateValidationError(name, "commitments.json shapes must be an array");
22466
+ }
22467
+ for (const shape of d.shapes) {
22468
+ if (typeof shape !== "object" || shape === null) {
22469
+ throw new TemplateValidationError(name, "commitment shape must be an object");
22470
+ }
22471
+ const s = shape;
22472
+ if (typeof s.commitment_class !== "string" || s.commitment_class.length === 0) {
22473
+ throw new TemplateValidationError(name, "commitment shape commitment_class must be a non-empty string");
22474
+ }
22475
+ if (typeof s.example_deliverable !== "string") {
22476
+ throw new TemplateValidationError(name, "commitment shape example_deliverable must be a string");
22477
+ }
22478
+ if (typeof s.example_deadline_or_terminal !== "string") {
22479
+ throw new TemplateValidationError(name, "commitment shape example_deadline_or_terminal must be a string");
22480
+ }
22481
+ }
22482
+ }
22483
+ function lintOnboarding(_name, content) {
22484
+ const violations = [];
22485
+ if (content.includes("\u2014")) {
22486
+ violations.push("onboarding.md contains em-dashes (U+2014). Replace with commas, semicolons, colons, or parentheses.");
22487
+ }
22488
+ if (content.includes("\u2013")) {
22489
+ violations.push("onboarding.md contains en-dashes (U+2013). Replace with hyphens or other punctuation.");
22490
+ }
22491
+ return { clean: violations.length === 0, violations };
22492
+ }
22493
+ function resolveTemplatesDir() {
22494
+ const thisFile = fileURLToPath(import.meta.url);
22495
+ const thisDir = dirname(thisFile);
22496
+ if (thisDir.includes("/dist/")) {
22497
+ return thisDir.replace("/dist/templates", "/src/templates");
22498
+ }
22499
+ return thisDir;
22500
+ }
22501
+ function loadTemplateBundle(name, templatesDir) {
22502
+ const dir = join(templatesDir, name);
22503
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
22504
+ throw new TemplateValidationError(name, `template directory not found: ${dir}`);
22505
+ }
22506
+ const metadataRaw = JSON.parse(readFileSync(join(dir, "template.json"), "utf-8"));
22507
+ validateMetadata(name, metadataRaw);
22508
+ const policyEnglish = readFileSync(join(dir, "policy.md"), "utf-8").trim();
22509
+ if (policyEnglish.length === 0) {
22510
+ throw new TemplateValidationError(name, "policy.md must not be empty");
22511
+ }
22512
+ const defaultsRaw = JSON.parse(readFileSync(join(dir, "defaults.json"), "utf-8"));
22513
+ validateDefaults(name, defaultsRaw);
22514
+ const commitmentsRaw = JSON.parse(readFileSync(join(dir, "commitments.json"), "utf-8"));
22515
+ validateCommitments(name, commitmentsRaw);
22516
+ const onboarding = readFileSync(join(dir, "onboarding.md"), "utf-8").trim();
22517
+ if (onboarding.length === 0) {
22518
+ throw new TemplateValidationError(name, "onboarding.md must not be empty");
22519
+ }
22520
+ const lint = lintOnboarding(name, onboarding);
22521
+ if (!lint.clean) {
22522
+ throw new TemplateValidationError(name, lint.violations.join("; "));
22523
+ }
22524
+ return {
22525
+ metadata: metadataRaw,
22526
+ policy_english: policyEnglish,
22527
+ defaults: defaultsRaw,
22528
+ commitments: commitmentsRaw,
22529
+ onboarding
22530
+ };
22531
+ }
22532
+ var _cache = null;
22533
+ function ensureLoaded() {
22534
+ if (_cache) return _cache;
22535
+ _cache = /* @__PURE__ */ new Map();
22536
+ const dir = resolveTemplatesDir();
22537
+ for (const name of TEMPLATE_NAMES) {
22538
+ _cache.set(name, loadTemplateBundle(name, dir));
22539
+ }
22540
+ return _cache;
22541
+ }
22542
+ function listTemplates() {
22543
+ const cache = ensureLoaded();
22544
+ return TEMPLATE_NAMES.map((name) => {
22545
+ const bundle = cache.get(name);
22546
+ return {
22547
+ metadata: bundle.metadata,
22548
+ onboarding: bundle.onboarding
22549
+ };
22550
+ });
22551
+ }
22552
+ function getTemplate2(name) {
22553
+ if (!isTemplateName(name)) return null;
22554
+ const cache = ensureLoaded();
22555
+ return cache.get(name) ?? null;
22556
+ }
22557
+ function getTemplateEntry(name) {
22558
+ const bundle = getTemplate2(name);
22559
+ if (!bundle) return null;
22560
+ return { metadata: bundle.metadata, onboarding: bundle.onboarding };
22561
+ }
22562
+
22563
+ // src/policy-engine/null-policy.ts
22564
+ function buildNullPolicy(params) {
22565
+ const denyRule = (slot) => ({
22566
+ slot,
22567
+ mode: "deny",
22568
+ grants: []
22569
+ });
22570
+ return {
22571
+ schema_version: "0.1",
22572
+ agent_id: params.agent_id,
22573
+ fortress_id: params.fortress_id,
22574
+ policy_version: 0,
22575
+ slots: {
22576
+ memory: denyRule("memory"),
22577
+ credentials: denyRule("credentials"),
22578
+ plans: denyRule("plans"),
22579
+ outputs: denyRule("outputs")
22580
+ },
22581
+ capabilities: {
22582
+ concordia_commitment_classes: [],
22583
+ honeypot_skill_ids: [],
22584
+ is_sentinel: false
22585
+ },
22586
+ auto_trigger_ladder: {
22587
+ honeypot_auto_freeze: true,
22588
+ threshold_rule_action: "operator_approved",
22589
+ ml_anomaly_action: "operator_approved"
22590
+ },
22591
+ source_english: "(null policy \u2014 no operator authoring yet; hermetic default denies all four slots)",
22592
+ compiled_at: "1970-01-01T00:00:00.000Z"
22593
+ };
22594
+ }
22595
+
22596
+ // src/policy-engine/channel-templates.ts
22597
+ function basePolicy(params) {
22598
+ if (params.merge_into) {
22599
+ const p2 = {
22600
+ ...params.merge_into,
22601
+ policy_version: params.policy_version,
22602
+ compiled_at: (/* @__PURE__ */ new Date()).toISOString(),
22603
+ slots: {
22604
+ memory: cloneRule(params.merge_into.slots.memory),
22605
+ credentials: cloneRule(params.merge_into.slots.credentials),
22606
+ plans: cloneRule(params.merge_into.slots.plans),
22607
+ outputs: cloneRule(params.merge_into.slots.outputs)
22608
+ },
22609
+ capabilities: {
22610
+ ...params.merge_into.capabilities,
22611
+ concordia_commitment_classes: [
22612
+ ...params.merge_into.capabilities.concordia_commitment_classes
22613
+ ],
22614
+ honeypot_skill_ids: [...params.merge_into.capabilities.honeypot_skill_ids]
22615
+ },
22616
+ auto_trigger_ladder: { ...params.merge_into.auto_trigger_ladder }
22617
+ };
22618
+ if (params.parent_version !== void 0) p2.parent_version = params.parent_version;
22619
+ else delete p2.parent_version;
22620
+ return p2;
22621
+ }
22622
+ const p = buildNullPolicy({
22623
+ agent_id: params.agent_id,
22624
+ fortress_id: params.fortress_id
22625
+ });
22626
+ p.policy_version = params.policy_version;
22627
+ p.compiled_at = (/* @__PURE__ */ new Date()).toISOString();
22628
+ if (params.parent_version !== void 0) p.parent_version = params.parent_version;
22629
+ return p;
22630
+ }
22631
+ function cloneRule(r) {
22632
+ return {
22633
+ slot: r.slot,
22634
+ mode: r.mode,
22635
+ grants: r.grants.map((g) => ({ ...g, scope: g.scope ? { ...g.scope } : void 0 }))
22636
+ };
22637
+ }
22638
+ function grantOn(policy, slot, grant) {
22639
+ const rule = policy.slots[slot];
22640
+ rule.mode = "grant";
22641
+ const dup = rule.grants.find(
22642
+ (g) => g.counterparty === grant.counterparty && g.action === grant.action
22643
+ );
22644
+ if (dup) {
22645
+ if (grant.scope) {
22646
+ dup.scope = { ...dup.scope ?? {}, ...grant.scope };
22647
+ }
22648
+ return;
22649
+ }
22650
+ rule.grants.push(grant);
22651
+ rule.grants.sort((a, b) => {
22652
+ if (a.counterparty === b.counterparty) return a.action.localeCompare(b.action);
22653
+ return a.counterparty.localeCompare(b.counterparty);
22654
+ });
22655
+ }
22656
+ var readOutputsOnly = (params) => {
22657
+ const p = basePolicy(params);
22658
+ p.source_english = `${params.counterparty} may read ${params.agent_id}'s outputs \u2014 read-only, no other access.`;
22659
+ grantOn(p, "outputs", {
22660
+ counterparty: params.counterparty,
22661
+ action: "read",
22662
+ ...params.scope ? { scope: { ...params.scope } } : {}
22663
+ });
22664
+ return p;
22665
+ };
22666
+ var bidirectionalSync = (params) => {
22667
+ const p = basePolicy(params);
22668
+ p.source_english = `Bidirectional sync between ${params.agent_id} and ${params.counterparty} on memory + outputs.`;
22669
+ for (const slot of ["memory", "outputs"]) {
22670
+ for (const action of ["read", "subscribe"]) {
22671
+ grantOn(p, slot, {
22672
+ counterparty: params.counterparty,
22673
+ action,
22674
+ ...params.scope ? { scope: { ...params.scope } } : {}
22675
+ });
22676
+ }
22677
+ }
22678
+ return p;
22679
+ };
22680
+ var credentialShareScoped = (params) => {
22681
+ const p = basePolicy(params);
22682
+ const credentialId = (params.scope && typeof params.scope.credential_id === "string" ? params.scope.credential_id : void 0) ?? "(unspecified)";
22683
+ p.source_english = `${params.agent_id} may share credential "${credentialId}" with ${params.counterparty}; no other credential access.`;
22684
+ grantOn(p, "credentials", {
22685
+ counterparty: params.counterparty,
22686
+ action: "share",
22687
+ scope: {
22688
+ credential_id: credentialId,
22689
+ ...params.scope ?? {}
22690
+ }
22691
+ });
22692
+ return p;
22693
+ };
22694
+ var planInspectReadOnly = (params) => {
22695
+ const p = basePolicy(params);
22696
+ p.source_english = `${params.counterparty} may read-only inspect ${params.agent_id}'s plans.`;
22697
+ grantOn(p, "plans", {
22698
+ counterparty: params.counterparty,
22699
+ action: "read",
22700
+ ...params.scope ? { scope: { ...params.scope } } : {}
22701
+ });
22702
+ return p;
22703
+ };
22704
+ var escrowHandoff = (params) => {
22705
+ const p = basePolicy(params);
22706
+ p.source_english = `${params.agent_id} may escrow-handoff outputs and plan-read to ${params.counterparty}. Commitment class: intra-mesh-escrow.`;
22707
+ grantOn(p, "plans", {
22708
+ counterparty: params.counterparty,
22709
+ action: "read",
22710
+ ...params.scope ? { scope: { ...params.scope } } : {}
22711
+ });
22712
+ for (const action of ["read", "subscribe"]) {
22713
+ grantOn(p, "outputs", {
22714
+ counterparty: params.counterparty,
22715
+ action,
22716
+ ...params.scope ? { scope: { ...params.scope } } : {}
22717
+ });
22718
+ }
22719
+ const cls = "intra-mesh-escrow";
22720
+ if (!p.capabilities.concordia_commitment_classes.includes(cls)) {
22721
+ p.capabilities.concordia_commitment_classes.push(cls);
22722
+ p.capabilities.concordia_commitment_classes.sort();
22723
+ }
22724
+ return p;
22725
+ };
22726
+ var REGISTRY = {
22727
+ "read-outputs-only": {
22728
+ id: "read-outputs-only",
22729
+ label: "Read outputs only",
22730
+ description: "Counterparty may read this agent's outputs. No memory, credentials, or plans access.",
22731
+ factory: readOutputsOnly
22732
+ },
22733
+ "bidirectional-sync": {
22734
+ id: "bidirectional-sync",
22735
+ label: "Bidirectional memory + output sync",
22736
+ description: "Both agents may read and subscribe to each other's memory and outputs. Credentials and plans remain hermetic.",
22737
+ factory: bidirectionalSync
22738
+ },
22739
+ "credential-share-scoped": {
22740
+ id: "credential-share-scoped",
22741
+ label: "Scoped credential share",
22742
+ description: "Share one specific credential with counterparty. Requires scope.credential_id. No broad credential access.",
22743
+ factory: credentialShareScoped
22744
+ },
22745
+ "plan-inspect-read-only": {
22746
+ id: "plan-inspect-read-only",
22747
+ label: "Plan inspect (read-only)",
22748
+ description: "Counterparty may read-only inspect this agent's plans. Intended for supervisor / sentinel patterns.",
22749
+ factory: planInspectReadOnly
22750
+ },
22751
+ "escrow-handoff": {
22752
+ id: "escrow-handoff",
22753
+ label: "Escrow handoff",
22754
+ 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.",
22755
+ factory: escrowHandoff
22756
+ }
22757
+ };
22758
+ function applyChannelTemplate(id, params) {
22759
+ const entry = REGISTRY[id];
22760
+ if (!entry) {
22761
+ throw new Error(`unknown channel template: ${id}`);
22762
+ }
22763
+ return entry.factory(params);
22764
+ }
22765
+
22766
+ // src/mesh/errors.ts
22767
+ var MeshError = class extends Error {
22768
+ constructor(message) {
22769
+ super(message);
22770
+ this.name = "MeshError";
22771
+ }
22772
+ };
22773
+ var MeshEnvelopeError = class extends MeshError {
22774
+ constructor(message) {
22775
+ super(message);
22776
+ this.name = "MeshEnvelopeError";
22777
+ }
22778
+ };
22779
+ var MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
22780
+ constructor(key) {
22781
+ super(
22782
+ `v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
22783
+ );
22784
+ this.name = "MeshReservedExtensionKeyError";
22785
+ }
22786
+ };
22787
+ var MeshReservedEventTypeError = class extends MeshEnvelopeError {
22788
+ constructor(eventType) {
22789
+ super(
22790
+ `v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
22791
+ );
22792
+ this.name = "MeshReservedEventTypeError";
22793
+ }
22794
+ };
22795
+
22796
+ // src/mesh/canonical-json.ts
22797
+ var MeshCanonicalJsonError = class extends MeshError {
22798
+ constructor(message) {
22799
+ super(message);
22800
+ this.name = "MeshCanonicalJsonError";
22801
+ }
22802
+ };
22803
+ function canonicalize2(value) {
22804
+ if (value === void 0) {
22805
+ throw new MeshCanonicalJsonError(
22806
+ "canonicalize(): top-level undefined is not serializable"
22807
+ );
22808
+ }
22809
+ return encode(value);
22810
+ }
22811
+ function encode(value) {
22812
+ if (value === null) return "null";
22813
+ if (typeof value === "boolean") return value ? "true" : "false";
22814
+ if (typeof value === "number") {
22815
+ if (!Number.isFinite(value)) {
22816
+ throw new MeshCanonicalJsonError(
22817
+ `canonicalize(): non-finite number (${String(value)}) is not serializable`
22818
+ );
22819
+ }
22820
+ return JSON.stringify(value);
22821
+ }
22822
+ if (typeof value === "string") return JSON.stringify(value);
22823
+ if (Array.isArray(value)) return encodeArray(value);
22824
+ if (typeof value === "object") return encodeObject(value);
22825
+ throw new MeshCanonicalJsonError(
22826
+ `canonicalize(): unsupported type ${typeof value}`
22827
+ );
22828
+ }
22829
+ function encodeArray(arr) {
22830
+ const parts = [];
22831
+ for (const item of arr) {
22832
+ parts.push(item === void 0 ? "null" : encode(item));
22833
+ }
22834
+ return "[" + parts.join(",") + "]";
22835
+ }
22836
+ function encodeObject(obj) {
22837
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
22838
+ const parts = [];
22839
+ for (const k of keys) {
22840
+ parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
22841
+ }
22842
+ return "{" + parts.join(",") + "}";
22843
+ }
22844
+ function canonicalizeToBytes(value) {
22845
+ return new TextEncoder().encode(canonicalize2(value));
22846
+ }
22847
+
22848
+ // src/policy-engine/canonical-policy.ts
22849
+ init_encoding();
22850
+
22851
+ // src/policy-engine/errors.ts
22852
+ var PolicyEngineError = class extends Error {
22853
+ constructor(message) {
22854
+ super(message);
22855
+ this.name = "PolicyEngineError";
22856
+ }
22857
+ };
22858
+ var CompiledPolicyShapeError = class extends PolicyEngineError {
22859
+ constructor(message) {
22860
+ super(message);
22861
+ this.name = "CompiledPolicyShapeError";
22862
+ }
22863
+ };
22864
+
22865
+ // src/policy-engine/canonical-policy.ts
22866
+ function checkSlotGrant(value, path) {
22867
+ if (typeof value !== "object" || value === null) {
22868
+ throw new CompiledPolicyShapeError(`${path}: grant must be an object`);
22869
+ }
22870
+ const g = value;
22871
+ if (typeof g.counterparty !== "string" || g.counterparty.length === 0) {
22872
+ throw new CompiledPolicyShapeError(
22873
+ `${path}.counterparty must be a non-empty string`
22874
+ );
22875
+ }
22876
+ if (typeof g.action !== "string" || g.action.length === 0) {
22877
+ throw new CompiledPolicyShapeError(
22878
+ `${path}.action must be a non-empty string`
22879
+ );
22880
+ }
22881
+ if (g.scope !== void 0) {
22882
+ if (typeof g.scope !== "object" || g.scope === null || Array.isArray(g.scope)) {
22883
+ throw new CompiledPolicyShapeError(
22884
+ `${path}.scope must be an object when present`
22885
+ );
22886
+ }
22887
+ }
22888
+ if (g.max_uses_per_day !== void 0) {
22889
+ if (typeof g.max_uses_per_day !== "number" || !Number.isInteger(g.max_uses_per_day) || g.max_uses_per_day < 0) {
22890
+ throw new CompiledPolicyShapeError(
22891
+ `${path}.max_uses_per_day must be a non-negative integer when present`
22892
+ );
22893
+ }
22894
+ }
22895
+ }
22896
+ function checkSlotRule(slot, value, path) {
22897
+ if (typeof value !== "object" || value === null) {
22898
+ throw new CompiledPolicyShapeError(`${path}: slot rule must be an object`);
22899
+ }
22900
+ const r = value;
22901
+ if (r.slot !== slot) {
22902
+ throw new CompiledPolicyShapeError(
22903
+ `${path}.slot expected ${slot}, got ${String(r.slot)}`
22904
+ );
22905
+ }
22906
+ if (r.mode !== "deny" && r.mode !== "grant") {
22907
+ throw new CompiledPolicyShapeError(
22908
+ `${path}.mode must be "deny" or "grant"`
22909
+ );
22910
+ }
22911
+ if (!Array.isArray(r.grants)) {
22912
+ throw new CompiledPolicyShapeError(
22913
+ `${path}.grants must be an array (possibly empty)`
22914
+ );
22915
+ }
22916
+ if (r.mode === "deny" && r.grants.length > 0) {
22917
+ throw new CompiledPolicyShapeError(
22918
+ `${path}: deny-mode slot rule must carry zero grants`
22919
+ );
22920
+ }
22921
+ r.grants.forEach((g, i) => checkSlotGrant(g, `${path}.grants[${i}]`));
22922
+ }
22923
+ function validateCompiledPolicyShape(candidate) {
22924
+ if (typeof candidate !== "object" || candidate === null) {
22925
+ throw new CompiledPolicyShapeError("compiled policy must be an object");
22926
+ }
22927
+ const p = candidate;
22928
+ if (p.schema_version !== COMPILED_POLICY_SCHEMA_VERSION) {
22929
+ throw new CompiledPolicyShapeError(
22930
+ `schema_version ${String(p.schema_version)} not supported at v0.1 (expected "${COMPILED_POLICY_SCHEMA_VERSION}")`
22931
+ );
22932
+ }
22933
+ if (typeof p.agent_id !== "string" || p.agent_id.length === 0) {
22934
+ throw new CompiledPolicyShapeError("agent_id must be a non-empty string");
22935
+ }
22936
+ if (typeof p.fortress_id !== "string" || p.fortress_id.length === 0) {
22937
+ throw new CompiledPolicyShapeError(
22938
+ "fortress_id must be a non-empty string"
22939
+ );
22940
+ }
22941
+ if (typeof p.policy_version !== "number" || !Number.isInteger(p.policy_version) || p.policy_version < 0) {
22942
+ throw new CompiledPolicyShapeError(
22943
+ "policy_version must be a non-negative integer"
22944
+ );
22945
+ }
22946
+ if (p.parent_version !== void 0 && (typeof p.parent_version !== "number" || !Number.isInteger(p.parent_version) || p.parent_version < 0)) {
22947
+ throw new CompiledPolicyShapeError(
22948
+ "parent_version must be a non-negative integer when present"
22949
+ );
22950
+ }
22951
+ if (typeof p.slots !== "object" || p.slots === null) {
22952
+ throw new CompiledPolicyShapeError("slots must be an object");
22953
+ }
22954
+ const slots = p.slots;
22955
+ const slotKeys = Object.keys(slots).sort();
22956
+ const expectedSlotKeys = [...POLICY_SLOTS].sort();
22957
+ if (slotKeys.length !== expectedSlotKeys.length || !slotKeys.every((k, i) => k === expectedSlotKeys[i])) {
22958
+ throw new CompiledPolicyShapeError(
22959
+ `slots must contain exactly ${expectedSlotKeys.join(", ")}; got ${slotKeys.join(", ") || "(none)"}`
22960
+ );
22961
+ }
22962
+ for (const slot of POLICY_SLOTS) {
22963
+ checkSlotRule(slot, slots[slot], `slots.${slot}`);
22964
+ }
22965
+ if (typeof p.capabilities !== "object" || p.capabilities === null) {
22966
+ throw new CompiledPolicyShapeError("capabilities must be an object");
22967
+ }
22968
+ const caps = p.capabilities;
22969
+ if (!Array.isArray(caps.concordia_commitment_classes) || !caps.concordia_commitment_classes.every((c) => typeof c === "string")) {
22970
+ throw new CompiledPolicyShapeError(
22971
+ "capabilities.concordia_commitment_classes must be a string[]"
22972
+ );
22973
+ }
22974
+ if (!Array.isArray(caps.honeypot_skill_ids) || !caps.honeypot_skill_ids.every((c) => typeof c === "string")) {
22975
+ throw new CompiledPolicyShapeError(
22976
+ "capabilities.honeypot_skill_ids must be a string[]"
22977
+ );
22978
+ }
22979
+ if (typeof caps.is_sentinel !== "boolean") {
22980
+ throw new CompiledPolicyShapeError(
22981
+ "capabilities.is_sentinel must be boolean"
22982
+ );
22983
+ }
22984
+ if (typeof p.auto_trigger_ladder !== "object" || p.auto_trigger_ladder === null) {
22985
+ throw new CompiledPolicyShapeError(
22986
+ "auto_trigger_ladder must be an object"
22987
+ );
22988
+ }
22989
+ const lad = p.auto_trigger_ladder;
22990
+ if (typeof lad.honeypot_auto_freeze !== "boolean") {
22991
+ throw new CompiledPolicyShapeError(
22992
+ "auto_trigger_ladder.honeypot_auto_freeze must be boolean"
22993
+ );
22994
+ }
22995
+ if (lad.threshold_rule_action !== "operator_approved" && lad.threshold_rule_action !== "auto") {
22996
+ throw new CompiledPolicyShapeError(
22997
+ 'auto_trigger_ladder.threshold_rule_action must be "operator_approved" or "auto"'
22998
+ );
22999
+ }
23000
+ if (lad.ml_anomaly_action !== "operator_approved" && lad.ml_anomaly_action !== "auto") {
23001
+ throw new CompiledPolicyShapeError(
23002
+ 'auto_trigger_ladder.ml_anomaly_action must be "operator_approved" or "auto"'
23003
+ );
23004
+ }
23005
+ if (typeof p.source_english !== "string") {
23006
+ throw new CompiledPolicyShapeError("source_english must be a string");
23007
+ }
23008
+ if (typeof p.compiled_at !== "string" || Number.isNaN(Date.parse(p.compiled_at))) {
23009
+ throw new CompiledPolicyShapeError(
23010
+ "compiled_at must be an ISO8601 timestamp string"
23011
+ );
23012
+ }
23013
+ for (const key of slotKeys) {
23014
+ if (!isPolicySlot(key)) {
23015
+ throw new CompiledPolicyShapeError(
23016
+ `slots.${key} is not a recognized policy slot`
23017
+ );
23018
+ }
23019
+ }
23020
+ if (p.egress !== void 0) {
23021
+ checkEgressPolicy(p.egress, "egress");
23022
+ }
23023
+ if (p.budgets !== void 0) {
23024
+ checkBudgetPolicy(p.budgets, "budgets");
23025
+ }
23026
+ if (p.retention !== void 0) {
23027
+ checkRetentionPolicy(p.retention, "retention");
23028
+ }
23029
+ }
23030
+ function checkEgressPolicy(value, path) {
23031
+ if (typeof value !== "object" || value === null) {
23032
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23033
+ }
23034
+ const eg = value;
23035
+ if (!Array.isArray(eg.allowlist)) {
23036
+ throw new CompiledPolicyShapeError(`${path}.allowlist must be an array`);
23037
+ }
23038
+ for (let i = 0; i < eg.allowlist.length; i++) {
23039
+ const rule = eg.allowlist[i];
23040
+ if (typeof rule !== "object" || rule === null) {
23041
+ throw new CompiledPolicyShapeError(
23042
+ `${path}.allowlist[${i}] must be an object`
23043
+ );
23044
+ }
23045
+ const r = rule;
23046
+ if (typeof r.destination !== "string" || r.destination.length === 0) {
23047
+ throw new CompiledPolicyShapeError(
23048
+ `${path}.allowlist[${i}].destination must be a non-empty string`
23049
+ );
23050
+ }
23051
+ if (!Array.isArray(r.methods) || !r.methods.every((m) => typeof m === "string")) {
23052
+ throw new CompiledPolicyShapeError(
23053
+ `${path}.allowlist[${i}].methods must be a string[]`
23054
+ );
23055
+ }
23056
+ }
23057
+ }
23058
+ function checkBudgetLimit(value, path) {
23059
+ if (typeof value !== "object" || value === null) {
23060
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23061
+ }
23062
+ const lim = value;
23063
+ if (typeof lim.amount !== "number" || lim.amount <= 0 || !Number.isFinite(lim.amount)) {
23064
+ throw new CompiledPolicyShapeError(
23065
+ `${path}.amount must be a positive finite number`
23066
+ );
23067
+ }
23068
+ if (!BUDGET_UNITS.includes(lim.unit)) {
23069
+ throw new CompiledPolicyShapeError(
23070
+ `${path}.unit must be one of: ${BUDGET_UNITS.join(", ")}`
23071
+ );
23072
+ }
23073
+ if (lim.soft_warn_threshold !== void 0) {
23074
+ if (typeof lim.soft_warn_threshold !== "number" || lim.soft_warn_threshold <= 0 || lim.soft_warn_threshold >= 1) {
23075
+ throw new CompiledPolicyShapeError(
23076
+ `${path}.soft_warn_threshold must be in (0, 1) when present`
23077
+ );
23078
+ }
23079
+ }
23080
+ }
23081
+ function checkBudgetPolicy(value, path) {
23082
+ if (typeof value !== "object" || value === null) {
23083
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23084
+ }
23085
+ const bp = value;
23086
+ if (bp.daily !== void 0) checkBudgetLimit(bp.daily, `${path}.daily`);
23087
+ if (bp.monthly !== void 0) checkBudgetLimit(bp.monthly, `${path}.monthly`);
23088
+ if (bp.daily === void 0 && bp.monthly === void 0) {
23089
+ throw new CompiledPolicyShapeError(
23090
+ `${path} must have at least one of daily or monthly`
23091
+ );
23092
+ }
23093
+ }
23094
+ function checkRetentionPolicy(value, path) {
23095
+ if (typeof value !== "object" || value === null) {
23096
+ throw new CompiledPolicyShapeError(`${path} must be an object`);
23097
+ }
23098
+ const rp = value;
23099
+ if (typeof rp.windows !== "object" || rp.windows === null) {
23100
+ throw new CompiledPolicyShapeError(`${path}.windows must be an object`);
23101
+ }
23102
+ const wins = rp.windows;
23103
+ for (const key of Object.keys(wins)) {
23104
+ if (!isPolicySlot(key)) {
23105
+ throw new CompiledPolicyShapeError(
23106
+ `${path}.windows.${key} is not a recognized policy slot`
23107
+ );
23108
+ }
23109
+ const w = wins[key];
23110
+ if (typeof w !== "object" || w === null) {
23111
+ throw new CompiledPolicyShapeError(
23112
+ `${path}.windows.${key} must be an object`
23113
+ );
23114
+ }
23115
+ const win = w;
23116
+ if (typeof win.max_age_seconds !== "number" || !Number.isInteger(win.max_age_seconds) || win.max_age_seconds <= 0) {
23117
+ throw new CompiledPolicyShapeError(
23118
+ `${path}.windows.${key}.max_age_seconds must be a positive integer`
23119
+ );
23120
+ }
23121
+ if (win.archive !== void 0 && typeof win.archive !== "boolean") {
23122
+ throw new CompiledPolicyShapeError(
23123
+ `${path}.windows.${key}.archive must be boolean when present`
23124
+ );
23125
+ }
23126
+ }
23127
+ }
23128
+ function encodePolicyBlob(policy) {
23129
+ validateCompiledPolicyShape(policy);
23130
+ return toBase64url(canonicalizeToBytes(policy));
23131
+ }
23132
+
23133
+ // src/mesh/envelope.ts
23134
+ init_encoding();
23135
+ init_random();
23136
+
23137
+ // src/mesh/constants.ts
23138
+ var PROTOCOL_VERSION = "0.1";
23139
+ var RESERVED_EVENT_TYPE_PREFIXES = [
23140
+ "EXTENSION_",
23141
+ "cross_fortress_",
23142
+ "multi_master_"
23143
+ ];
23144
+ function isReservedEventType(s) {
23145
+ return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
23146
+ }
23147
+ var RESERVED_EXTENSION_ENVELOPE_KEYS = [
23148
+ "cross_fortress_read_grant",
23149
+ "cross_fortress_read_query",
23150
+ "cross_fortress_read_response",
23151
+ "multi_master_policy_merge",
23152
+ "audit_replication_full_n_way",
23153
+ "auto_promote_canonical_audit",
23154
+ "agent_live_migration"
23155
+ ];
23156
+ function isReservedExtensionKey(k) {
23157
+ return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
23158
+ }
23159
+
23160
+ // src/mesh/trust-root.ts
23161
+ init_encoding();
23162
+ init_identity();
23163
+ init_random();
23164
+
23165
+ // src/mesh/envelope.ts
23166
+ function packSignedEvent(params) {
23167
+ if (isReservedEventType(params.event_type)) {
23168
+ throw new MeshReservedEventTypeError(params.event_type);
23169
+ }
23170
+ const ext = params.extension_envelope ?? {};
23171
+ for (const key of Object.keys(ext)) {
23172
+ if (isReservedExtensionKey(key)) {
23173
+ throw new MeshReservedExtensionKeyError(key);
23174
+ }
23175
+ }
23176
+ const payloadBytes = canonicalizeToBytes(params.payload);
23177
+ const payload_hash = toBase64url(sha256(payloadBytes));
23178
+ const body = {
23179
+ protocol_version: PROTOCOL_VERSION,
23180
+ event_type: params.event_type,
23181
+ event_id: generateEventId(),
23182
+ emitter_node: params.emitter_node,
23183
+ emitter_principal: params.emitter_principal,
23184
+ fortress_id: params.fortress_id,
23185
+ causal_parents: params.causal_parents ?? [],
23186
+ payload: params.payload,
23187
+ payload_hash,
23188
+ emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
23189
+ monotonic_seq: params.monotonic_seq,
23190
+ extension_envelope: ext
23191
+ };
23192
+ const bytesToSign = canonicalizeToBytes(body);
23193
+ const nodeSig = ed25519.sign(bytesToSign, params.node_private_key);
23194
+ const evt = {
23195
+ ...body,
23196
+ node_signature: toBase64url(nodeSig)
23197
+ };
23198
+ if (params.principal_private_key) {
23199
+ const principalSig = ed25519.sign(bytesToSign, params.principal_private_key);
23200
+ evt.principal_signature = toBase64url(principalSig);
23201
+ }
23202
+ return evt;
23203
+ }
23204
+ function generateEventId() {
23205
+ const bytes = randomBytes(16);
23206
+ let hex = "";
23207
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
23208
+ return hex;
23209
+ }
23210
+
23211
+ // src/policy-engine/envelope.ts
23212
+ function packPolicyUpdate(params) {
23213
+ validateCompiledPolicyShape(params.policy);
23214
+ const blob = encodePolicyBlob(params.policy);
23215
+ const payload = {
23216
+ agent_id: params.policy.agent_id,
23217
+ policy_version: params.policy.policy_version,
23218
+ policy_blob: blob,
23219
+ ...params.policy.parent_version !== void 0 ? { parent_version: params.policy.parent_version } : {}
23220
+ };
23221
+ return packSignedEvent({
23222
+ event_type: POLICY_UPDATE_EVENT_TYPE,
23223
+ emitter_node: params.emitter_node,
23224
+ emitter_principal: params.emitter_principal,
23225
+ fortress_id: params.policy.fortress_id,
23226
+ causal_parents: params.causal_parents,
23227
+ payload,
23228
+ monotonic_seq: params.monotonic_seq,
23229
+ node_private_key: params.node_private_key,
23230
+ principal_private_key: params.principal_private_key
23231
+ });
23232
+ }
23233
+
23234
+ // src/templates/init.ts
23235
+ function deterministicCompiledAt(version) {
23236
+ const [major, minor, patch] = version.split(".").map(Number);
23237
+ const epoch = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
23238
+ epoch.setUTCDate(epoch.getUTCDate() + (major - 1) * 365 + minor * 30 + patch);
23239
+ return epoch.toISOString();
23240
+ }
23241
+ function buildCompiledPolicyFromTemplate(bundle, params) {
23242
+ const policy = applyChannelTemplate(
23243
+ bundle.metadata.channel,
23244
+ {
23245
+ agent_id: params.agent_id,
23246
+ counterparty: params.counterparty,
23247
+ fortress_id: params.fortress_id,
23248
+ policy_version: params.policy_version
23249
+ }
23250
+ );
23251
+ policy.compiled_at = deterministicCompiledAt(bundle.metadata.version);
23252
+ policy.source_english = bundle.policy_english;
23253
+ if (bundle.defaults.egress.length > 0) {
23254
+ const egress = {
23255
+ allowlist: bundle.defaults.egress.map((e) => ({
23256
+ destination: e.destination,
23257
+ methods: [...e.methods]
23258
+ }))
23259
+ };
23260
+ policy.egress = egress;
23261
+ }
23262
+ const budgets = {};
23263
+ if (bundle.defaults.budgets.daily) {
23264
+ budgets.daily = { ...bundle.defaults.budgets.daily };
23265
+ }
23266
+ if (bundle.defaults.budgets.monthly) {
23267
+ budgets.monthly = { ...bundle.defaults.budgets.monthly };
23268
+ }
23269
+ if (budgets.daily || budgets.monthly) {
23270
+ policy.budgets = budgets;
23271
+ }
23272
+ const windows = bundle.defaults.retention.windows;
23273
+ if (Object.keys(windows).length > 0) {
23274
+ const retention = {
23275
+ windows: {}
23276
+ };
23277
+ for (const [slot, win] of Object.entries(windows)) {
23278
+ if (win) {
23279
+ retention.windows[slot] = { ...win };
23280
+ }
23281
+ }
23282
+ policy.retention = retention;
23283
+ }
23284
+ const classes = bundle.commitments.shapes.map((s) => s.commitment_class);
23285
+ for (const cls of classes) {
23286
+ if (!policy.capabilities.concordia_commitment_classes.includes(cls)) {
23287
+ policy.capabilities.concordia_commitment_classes.push(cls);
23288
+ }
23289
+ }
23290
+ policy.capabilities.concordia_commitment_classes.sort();
23291
+ validateCompiledPolicyShape(policy);
23292
+ return policy;
23293
+ }
23294
+ function initTemplate(params) {
23295
+ const bundle = getTemplate2(params.template_name);
23296
+ if (!bundle) {
23297
+ throw new Error(`unknown template: ${params.template_name}`);
23298
+ }
23299
+ const compiled = buildCompiledPolicyFromTemplate(bundle, {
23300
+ agent_id: params.agent_id,
23301
+ fortress_id: params.fortress_id,
23302
+ counterparty: params.counterparty ?? "*",
23303
+ policy_version: params.policy_version ?? 1
23304
+ });
23305
+ const signed_event = packPolicyUpdate({
23306
+ policy: compiled,
23307
+ emitter_node: params.emitter_node,
23308
+ emitter_principal: params.emitter_principal,
23309
+ monotonic_seq: params.monotonic_seq,
23310
+ node_private_key: params.node_private_key,
23311
+ principal_private_key: params.principal_private_key
23312
+ });
23313
+ return { compiled, signed_event, bundle };
23314
+ }
23315
+
22200
23316
  // src/dashboard/api.ts
22201
23317
  function constantTimeEquals(a, b) {
22202
23318
  if (a.length !== b.length) return false;
@@ -22227,6 +23343,22 @@ function writeJSON(res, status, payload) {
22227
23343
  });
22228
23344
  res.end(JSON.stringify(payload));
22229
23345
  }
23346
+ async function readJSONBody(req) {
23347
+ const chunks = [];
23348
+ let size = 0;
23349
+ const MAX = 256 * 1024;
23350
+ for await (const chunk of req) {
23351
+ size += chunk.length;
23352
+ if (size > MAX) throw new Error("request body too large");
23353
+ chunks.push(chunk);
23354
+ }
23355
+ const body = Buffer.concat(chunks).toString("utf-8");
23356
+ if (!body) return {};
23357
+ return JSON.parse(body);
23358
+ }
23359
+ function generateEphemeralKey() {
23360
+ return new Uint8Array(randomBytes$1(32));
23361
+ }
22230
23362
  function writeText(res, status, body, contentType = "text/plain") {
22231
23363
  res.writeHead(status, {
22232
23364
  "Content-Type": contentType,
@@ -22279,6 +23411,90 @@ async function handleRequest(deps, req, res) {
22279
23411
  await handleStream(deps, res);
22280
23412
  return true;
22281
23413
  }
23414
+ if (method === "GET" && path === "/api/templates") {
23415
+ try {
23416
+ const templates = listTemplates();
23417
+ writeJSON(res, 200, { templates });
23418
+ } catch (err) {
23419
+ writeJSON(res, 500, {
23420
+ error: "template_load_failed",
23421
+ message: err.message
23422
+ });
23423
+ }
23424
+ return true;
23425
+ }
23426
+ const templateMatch = /^\/api\/templates\/([^/]+)$/.exec(path);
23427
+ if (method === "GET" && templateMatch) {
23428
+ const name = decodeURIComponent(templateMatch[1]);
23429
+ try {
23430
+ const entry = getTemplateEntry(name);
23431
+ if (!entry) {
23432
+ writeJSON(res, 404, { error: "template_not_found", name });
23433
+ return true;
23434
+ }
23435
+ writeJSON(res, 200, entry);
23436
+ } catch (err) {
23437
+ writeJSON(res, 500, {
23438
+ error: "template_load_failed",
23439
+ message: err.message
23440
+ });
23441
+ }
23442
+ return true;
23443
+ }
23444
+ const initMatch = /^\/api\/templates\/([^/]+)\/init$/.exec(path);
23445
+ if (method === "POST" && initMatch) {
23446
+ const name = decodeURIComponent(initMatch[1]);
23447
+ try {
23448
+ const bundle = getTemplate2(name);
23449
+ if (!bundle) {
23450
+ writeJSON(res, 404, { error: "template_not_found", name });
23451
+ return true;
23452
+ }
23453
+ const body = await readJSONBody(req);
23454
+ if (!body.agent_name || typeof body.agent_name !== "string") {
23455
+ writeJSON(res, 400, {
23456
+ error: "validation_error",
23457
+ message: "agent_name is required and must be a string"
23458
+ });
23459
+ return true;
23460
+ }
23461
+ if (!/^[a-zA-Z0-9_-]+$/.test(body.agent_name)) {
23462
+ writeJSON(res, 400, {
23463
+ error: "validation_error",
23464
+ message: "agent_name must contain only alphanumeric characters, hyphens, and underscores"
23465
+ });
23466
+ return true;
23467
+ }
23468
+ const nodeId = deps.nodeId ?? "dashboard-node";
23469
+ const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
23470
+ const principalId = deps.principalId ?? "dashboard-principal";
23471
+ const fortressId = deps.fortressId ?? "default";
23472
+ const result = initTemplate({
23473
+ template_name: name,
23474
+ agent_id: body.agent_name,
23475
+ fortress_id: fortressId,
23476
+ counterparty: "*",
23477
+ policy_version: 1,
23478
+ emitter_node: nodeId,
23479
+ emitter_principal: principalId,
23480
+ monotonic_seq: 1,
23481
+ node_private_key: nodePrivateKey
23482
+ });
23483
+ writeJSON(res, 200, {
23484
+ agent_id: body.agent_name,
23485
+ signed_event_id: result.signed_event.event_id,
23486
+ policy_version: result.compiled.policy_version,
23487
+ template_name: name,
23488
+ attestation_panel_url: `/console#agent_roster`
23489
+ });
23490
+ } catch (err) {
23491
+ writeJSON(res, 500, {
23492
+ error: "template_init_failed",
23493
+ message: err.message
23494
+ });
23495
+ }
23496
+ return true;
23497
+ }
22282
23498
  return false;
22283
23499
  }
22284
23500
  async function handleStream(deps, res) {