@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/cli.cjs +3213 -1518
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3215 -1520
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1405 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1407 -31
- package/dist/index.js.map +1 -1
- package/package.json +21 -2
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, basename, dirname, resolve } 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">
|
|
@@ -7109,12 +7138,96 @@ function generateDashboardHTML(options) {
|
|
|
7109
7138
|
loadProxyServers();
|
|
7110
7139
|
});
|
|
7111
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
|
+
|
|
7112
7165
|
eventSource.onerror = () => {
|
|
7113
7166
|
console.error('SSE error');
|
|
7114
7167
|
setTimeout(setupSSE, 5000);
|
|
7115
7168
|
};
|
|
7116
7169
|
}
|
|
7117
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
|
+
|
|
7118
7231
|
// Activity Feed
|
|
7119
7232
|
function addActivityItem(item) {
|
|
7120
7233
|
activityLog.unshift(item);
|
|
@@ -8818,7 +8931,7 @@ var DashboardApprovalChannel = class {
|
|
|
8818
8931
|
server = createServer$2(handler);
|
|
8819
8932
|
}
|
|
8820
8933
|
this.httpServer = server;
|
|
8821
|
-
return new Promise((
|
|
8934
|
+
return new Promise((resolve2, reject) => {
|
|
8822
8935
|
const protocol = this.useTLS ? "https" : "http";
|
|
8823
8936
|
const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
8824
8937
|
server.listen(this.config.port, this.config.host, () => {
|
|
@@ -8843,7 +8956,7 @@ var DashboardApprovalChannel = class {
|
|
|
8843
8956
|
if (shouldAutoOpen) {
|
|
8844
8957
|
this.openInBrowser(sessionUrl);
|
|
8845
8958
|
}
|
|
8846
|
-
|
|
8959
|
+
resolve2();
|
|
8847
8960
|
});
|
|
8848
8961
|
server.on("error", (err) => {
|
|
8849
8962
|
if (err.code === "EADDRINUSE") {
|
|
@@ -8889,8 +9002,8 @@ var DashboardApprovalChannel = class {
|
|
|
8889
9002
|
}
|
|
8890
9003
|
this.rateLimits.clear();
|
|
8891
9004
|
if (this.httpServer) {
|
|
8892
|
-
return new Promise((
|
|
8893
|
-
this.httpServer.close(() =>
|
|
9005
|
+
return new Promise((resolve2) => {
|
|
9006
|
+
this.httpServer.close(() => resolve2());
|
|
8894
9007
|
});
|
|
8895
9008
|
}
|
|
8896
9009
|
}
|
|
@@ -8904,7 +9017,7 @@ var DashboardApprovalChannel = class {
|
|
|
8904
9017
|
`[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
|
|
8905
9018
|
`
|
|
8906
9019
|
);
|
|
8907
|
-
return new Promise((
|
|
9020
|
+
return new Promise((resolve2) => {
|
|
8908
9021
|
const timer = setTimeout(() => {
|
|
8909
9022
|
this.pending.delete(id);
|
|
8910
9023
|
const response = {
|
|
@@ -8918,12 +9031,12 @@ var DashboardApprovalChannel = class {
|
|
|
8918
9031
|
decision: response.decision,
|
|
8919
9032
|
decided_by: "timeout"
|
|
8920
9033
|
});
|
|
8921
|
-
|
|
9034
|
+
resolve2(response);
|
|
8922
9035
|
}, this.config.timeout_seconds * 1e3);
|
|
8923
9036
|
const pending = {
|
|
8924
9037
|
id,
|
|
8925
9038
|
request,
|
|
8926
|
-
resolve,
|
|
9039
|
+
resolve: resolve2,
|
|
8927
9040
|
timer,
|
|
8928
9041
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8929
9042
|
};
|
|
@@ -9675,6 +9788,27 @@ data: ${JSON.stringify(data)}
|
|
|
9675
9788
|
broadcastProtectionStatus(data) {
|
|
9676
9789
|
this.broadcastSSE("protection-status", data);
|
|
9677
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
|
+
}
|
|
9678
9812
|
/**
|
|
9679
9813
|
* Open a URL in the system's default browser.
|
|
9680
9814
|
* Cross-platform: macOS (open), Linux (xdg-open), Windows (start).
|
|
@@ -9748,7 +9882,7 @@ var WebhookApprovalChannel = class {
|
|
|
9748
9882
|
* Start the callback listener server.
|
|
9749
9883
|
*/
|
|
9750
9884
|
async start() {
|
|
9751
|
-
return new Promise((
|
|
9885
|
+
return new Promise((resolve2, reject) => {
|
|
9752
9886
|
this.callbackServer = createServer$2(
|
|
9753
9887
|
(req, res) => this.handleCallback(req, res)
|
|
9754
9888
|
);
|
|
@@ -9763,7 +9897,7 @@ var WebhookApprovalChannel = class {
|
|
|
9763
9897
|
|
|
9764
9898
|
`
|
|
9765
9899
|
);
|
|
9766
|
-
|
|
9900
|
+
resolve2();
|
|
9767
9901
|
}
|
|
9768
9902
|
);
|
|
9769
9903
|
this.callbackServer.on("error", reject);
|
|
@@ -9783,8 +9917,8 @@ var WebhookApprovalChannel = class {
|
|
|
9783
9917
|
}
|
|
9784
9918
|
this.pending.clear();
|
|
9785
9919
|
if (this.callbackServer) {
|
|
9786
|
-
return new Promise((
|
|
9787
|
-
this.callbackServer.close(() =>
|
|
9920
|
+
return new Promise((resolve2) => {
|
|
9921
|
+
this.callbackServer.close(() => resolve2());
|
|
9788
9922
|
});
|
|
9789
9923
|
}
|
|
9790
9924
|
}
|
|
@@ -9797,7 +9931,7 @@ var WebhookApprovalChannel = class {
|
|
|
9797
9931
|
`[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
|
|
9798
9932
|
`
|
|
9799
9933
|
);
|
|
9800
|
-
return new Promise((
|
|
9934
|
+
return new Promise((resolve2) => {
|
|
9801
9935
|
const timer = setTimeout(() => {
|
|
9802
9936
|
this.pending.delete(id);
|
|
9803
9937
|
const response = {
|
|
@@ -9806,12 +9940,12 @@ var WebhookApprovalChannel = class {
|
|
|
9806
9940
|
decided_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9807
9941
|
decided_by: "timeout"
|
|
9808
9942
|
};
|
|
9809
|
-
|
|
9943
|
+
resolve2(response);
|
|
9810
9944
|
}, this.config.timeout_seconds * 1e3);
|
|
9811
9945
|
const pending = {
|
|
9812
9946
|
id,
|
|
9813
9947
|
request,
|
|
9814
|
-
resolve,
|
|
9948
|
+
resolve: resolve2,
|
|
9815
9949
|
timer,
|
|
9816
9950
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9817
9951
|
};
|
|
@@ -13005,7 +13139,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13005
13139
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13006
13140
|
const canonicalBytes = canonicalize(outcome);
|
|
13007
13141
|
const canonicalString = new TextDecoder().decode(canonicalBytes);
|
|
13008
|
-
const
|
|
13142
|
+
const sha2568 = createCommitment(canonicalString);
|
|
13009
13143
|
let pedersenData;
|
|
13010
13144
|
if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
|
|
13011
13145
|
const pedersen = createPedersenCommitment(outcome.rounds);
|
|
@@ -13017,7 +13151,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13017
13151
|
const commitmentPayload = {
|
|
13018
13152
|
bridge_commitment_id: commitmentId,
|
|
13019
13153
|
session_id: outcome.session_id,
|
|
13020
|
-
sha256_commitment:
|
|
13154
|
+
sha256_commitment: sha2568.commitment,
|
|
13021
13155
|
terms_hash: outcome.terms_hash,
|
|
13022
13156
|
committer_did: identity.did,
|
|
13023
13157
|
committed_at: now,
|
|
@@ -13028,8 +13162,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13028
13162
|
return {
|
|
13029
13163
|
bridge_commitment_id: commitmentId,
|
|
13030
13164
|
session_id: outcome.session_id,
|
|
13031
|
-
sha256_commitment:
|
|
13032
|
-
blinding_factor:
|
|
13165
|
+
sha256_commitment: sha2568.commitment,
|
|
13166
|
+
blinding_factor: sha2568.blinding_factor,
|
|
13033
13167
|
committer_did: identity.did,
|
|
13034
13168
|
signature: toBase64url(signature),
|
|
13035
13169
|
pedersen_commitment: pedersenData,
|
|
@@ -17196,13 +17330,13 @@ var ProxyRouter = class {
|
|
|
17196
17330
|
* Call an upstream tool with a timeout.
|
|
17197
17331
|
*/
|
|
17198
17332
|
async callWithTimeout(serverName, toolName, args, timeoutMs) {
|
|
17199
|
-
return new Promise((
|
|
17333
|
+
return new Promise((resolve2, reject) => {
|
|
17200
17334
|
const timer = setTimeout(() => {
|
|
17201
17335
|
reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
|
|
17202
17336
|
}, timeoutMs);
|
|
17203
17337
|
this.clientManager.callTool(serverName, toolName, args).then((result) => {
|
|
17204
17338
|
clearTimeout(timer);
|
|
17205
|
-
|
|
17339
|
+
resolve2(result);
|
|
17206
17340
|
}).catch((err) => {
|
|
17207
17341
|
clearTimeout(timer);
|
|
17208
17342
|
reject(err);
|
|
@@ -21553,8 +21687,15 @@ function l3Card(l3) {
|
|
|
21553
21687
|
<div><dt>Proofs today</dt><dd>${escHtml(l3.proofs_today)}</dd></div>`
|
|
21554
21688
|
);
|
|
21555
21689
|
}
|
|
21690
|
+
var VERASCORE_CLAIM_URL = "https://www.verascore.ai";
|
|
21556
21691
|
function l4Card(l4) {
|
|
21557
|
-
|
|
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
|
+
}
|
|
21558
21699
|
return layerCard(
|
|
21559
21700
|
l4,
|
|
21560
21701
|
`<div class="layer-cta">${score}</div>${l4EvidenceBlock(l4)}`
|
|
@@ -22217,6 +22358,1129 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
|
|
|
22217
22358
|
</html>`;
|
|
22218
22359
|
}
|
|
22219
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
|
+
];
|
|
22390
|
+
function isTemplateName(value) {
|
|
22391
|
+
return typeof value === "string" && TEMPLATE_NAMES.includes(value);
|
|
22392
|
+
}
|
|
22393
|
+
var TemplateValidationError = class extends Error {
|
|
22394
|
+
constructor(templateName, message) {
|
|
22395
|
+
super(`template "${templateName}": ${message}`);
|
|
22396
|
+
this.name = "TemplateValidationError";
|
|
22397
|
+
}
|
|
22398
|
+
};
|
|
22399
|
+
function validateMetadata(name, data) {
|
|
22400
|
+
if (typeof data !== "object" || data === null) {
|
|
22401
|
+
throw new TemplateValidationError(name, "template.json must be an object");
|
|
22402
|
+
}
|
|
22403
|
+
const d = data;
|
|
22404
|
+
if (typeof d.name !== "string" || d.name !== name) {
|
|
22405
|
+
throw new TemplateValidationError(name, `template.json name must be "${name}"`);
|
|
22406
|
+
}
|
|
22407
|
+
if (typeof d.version !== "string" || !/^\d+\.\d+\.\d+$/.test(d.version)) {
|
|
22408
|
+
throw new TemplateValidationError(name, "template.json version must be semver");
|
|
22409
|
+
}
|
|
22410
|
+
if (typeof d.channel !== "string" || !CHANNEL_TEMPLATE_IDS.includes(d.channel)) {
|
|
22411
|
+
throw new TemplateValidationError(
|
|
22412
|
+
name,
|
|
22413
|
+
`template.json channel must be one of: ${CHANNEL_TEMPLATE_IDS.join(", ")}`
|
|
22414
|
+
);
|
|
22415
|
+
}
|
|
22416
|
+
if (typeof d.tier !== "string" || !["A", "B", "C"].includes(d.tier)) {
|
|
22417
|
+
throw new TemplateValidationError(name, "template.json tier must be A, B, or C");
|
|
22418
|
+
}
|
|
22419
|
+
if (typeof d.target_archetype !== "string" || d.target_archetype.length === 0) {
|
|
22420
|
+
throw new TemplateValidationError(name, "template.json target_archetype must be a non-empty string");
|
|
22421
|
+
}
|
|
22422
|
+
if (typeof d.description !== "string" || d.description.length === 0) {
|
|
22423
|
+
throw new TemplateValidationError(name, "template.json description must be a non-empty string");
|
|
22424
|
+
}
|
|
22425
|
+
}
|
|
22426
|
+
function validateDefaults(name, data) {
|
|
22427
|
+
if (typeof data !== "object" || data === null) {
|
|
22428
|
+
throw new TemplateValidationError(name, "defaults.json must be an object");
|
|
22429
|
+
}
|
|
22430
|
+
const d = data;
|
|
22431
|
+
if (!Array.isArray(d.egress)) {
|
|
22432
|
+
throw new TemplateValidationError(name, "defaults.json egress must be an array");
|
|
22433
|
+
}
|
|
22434
|
+
for (const entry of d.egress) {
|
|
22435
|
+
if (typeof entry !== "object" || entry === null) {
|
|
22436
|
+
throw new TemplateValidationError(name, "defaults.json egress entry must be an object");
|
|
22437
|
+
}
|
|
22438
|
+
const e = entry;
|
|
22439
|
+
if (typeof e.destination !== "string" || e.destination.length === 0) {
|
|
22440
|
+
throw new TemplateValidationError(name, "egress entry destination must be a non-empty string");
|
|
22441
|
+
}
|
|
22442
|
+
if (!Array.isArray(e.methods)) {
|
|
22443
|
+
throw new TemplateValidationError(name, "egress entry methods must be an array");
|
|
22444
|
+
}
|
|
22445
|
+
}
|
|
22446
|
+
if (typeof d.budgets !== "object" || d.budgets === null) {
|
|
22447
|
+
throw new TemplateValidationError(name, "defaults.json budgets must be an object");
|
|
22448
|
+
}
|
|
22449
|
+
if (typeof d.retention !== "object" || d.retention === null) {
|
|
22450
|
+
throw new TemplateValidationError(name, "defaults.json retention must be an object");
|
|
22451
|
+
}
|
|
22452
|
+
const ret = d.retention;
|
|
22453
|
+
if (typeof ret.windows !== "object" || ret.windows === null) {
|
|
22454
|
+
throw new TemplateValidationError(name, "defaults.json retention.windows must be an object");
|
|
22455
|
+
}
|
|
22456
|
+
}
|
|
22457
|
+
function validateCommitments(name, data) {
|
|
22458
|
+
if (typeof data !== "object" || data === null) {
|
|
22459
|
+
throw new TemplateValidationError(name, "commitments.json must be an object");
|
|
22460
|
+
}
|
|
22461
|
+
const d = data;
|
|
22462
|
+
if (!Array.isArray(d.shapes)) {
|
|
22463
|
+
throw new TemplateValidationError(name, "commitments.json shapes must be an array");
|
|
22464
|
+
}
|
|
22465
|
+
for (const shape of d.shapes) {
|
|
22466
|
+
if (typeof shape !== "object" || shape === null) {
|
|
22467
|
+
throw new TemplateValidationError(name, "commitment shape must be an object");
|
|
22468
|
+
}
|
|
22469
|
+
const s = shape;
|
|
22470
|
+
if (typeof s.commitment_class !== "string" || s.commitment_class.length === 0) {
|
|
22471
|
+
throw new TemplateValidationError(name, "commitment shape commitment_class must be a non-empty string");
|
|
22472
|
+
}
|
|
22473
|
+
if (typeof s.example_deliverable !== "string") {
|
|
22474
|
+
throw new TemplateValidationError(name, "commitment shape example_deliverable must be a string");
|
|
22475
|
+
}
|
|
22476
|
+
if (typeof s.example_deadline_or_terminal !== "string") {
|
|
22477
|
+
throw new TemplateValidationError(name, "commitment shape example_deadline_or_terminal must be a string");
|
|
22478
|
+
}
|
|
22479
|
+
}
|
|
22480
|
+
}
|
|
22481
|
+
function lintOnboarding(_name, content) {
|
|
22482
|
+
const violations = [];
|
|
22483
|
+
if (content.includes("\u2014")) {
|
|
22484
|
+
violations.push("onboarding.md contains em-dashes (U+2014). Replace with commas, semicolons, colons, or parentheses.");
|
|
22485
|
+
}
|
|
22486
|
+
if (content.includes("\u2013")) {
|
|
22487
|
+
violations.push("onboarding.md contains en-dashes (U+2013). Replace with hyphens or other punctuation.");
|
|
22488
|
+
}
|
|
22489
|
+
return { clean: violations.length === 0, violations };
|
|
22490
|
+
}
|
|
22491
|
+
function resolveTemplatesDir() {
|
|
22492
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
22493
|
+
const thisDir = dirname(thisFile);
|
|
22494
|
+
if (thisDir.includes("/dist/")) {
|
|
22495
|
+
return thisDir.replace("/dist/templates", "/src/templates");
|
|
22496
|
+
}
|
|
22497
|
+
return thisDir;
|
|
22498
|
+
}
|
|
22499
|
+
function loadTemplateBundle(name, templatesDir) {
|
|
22500
|
+
const dir = join(templatesDir, name);
|
|
22501
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
|
22502
|
+
throw new TemplateValidationError(name, `template directory not found: ${dir}`);
|
|
22503
|
+
}
|
|
22504
|
+
const metadataRaw = JSON.parse(readFileSync(join(dir, "template.json"), "utf-8"));
|
|
22505
|
+
validateMetadata(name, metadataRaw);
|
|
22506
|
+
const policyEnglish = readFileSync(join(dir, "policy.md"), "utf-8").trim();
|
|
22507
|
+
if (policyEnglish.length === 0) {
|
|
22508
|
+
throw new TemplateValidationError(name, "policy.md must not be empty");
|
|
22509
|
+
}
|
|
22510
|
+
const defaultsRaw = JSON.parse(readFileSync(join(dir, "defaults.json"), "utf-8"));
|
|
22511
|
+
validateDefaults(name, defaultsRaw);
|
|
22512
|
+
const commitmentsRaw = JSON.parse(readFileSync(join(dir, "commitments.json"), "utf-8"));
|
|
22513
|
+
validateCommitments(name, commitmentsRaw);
|
|
22514
|
+
const onboarding = readFileSync(join(dir, "onboarding.md"), "utf-8").trim();
|
|
22515
|
+
if (onboarding.length === 0) {
|
|
22516
|
+
throw new TemplateValidationError(name, "onboarding.md must not be empty");
|
|
22517
|
+
}
|
|
22518
|
+
const lint = lintOnboarding(name, onboarding);
|
|
22519
|
+
if (!lint.clean) {
|
|
22520
|
+
throw new TemplateValidationError(name, lint.violations.join("; "));
|
|
22521
|
+
}
|
|
22522
|
+
return {
|
|
22523
|
+
metadata: metadataRaw,
|
|
22524
|
+
policy_english: policyEnglish,
|
|
22525
|
+
defaults: defaultsRaw,
|
|
22526
|
+
commitments: commitmentsRaw,
|
|
22527
|
+
onboarding
|
|
22528
|
+
};
|
|
22529
|
+
}
|
|
22530
|
+
var _cache = null;
|
|
22531
|
+
function ensureLoaded() {
|
|
22532
|
+
if (_cache) return _cache;
|
|
22533
|
+
_cache = /* @__PURE__ */ new Map();
|
|
22534
|
+
const dir = resolveTemplatesDir();
|
|
22535
|
+
for (const name of TEMPLATE_NAMES) {
|
|
22536
|
+
_cache.set(name, loadTemplateBundle(name, dir));
|
|
22537
|
+
}
|
|
22538
|
+
return _cache;
|
|
22539
|
+
}
|
|
22540
|
+
function listTemplates() {
|
|
22541
|
+
const cache = ensureLoaded();
|
|
22542
|
+
return TEMPLATE_NAMES.map((name) => {
|
|
22543
|
+
const bundle = cache.get(name);
|
|
22544
|
+
return {
|
|
22545
|
+
metadata: bundle.metadata,
|
|
22546
|
+
onboarding: bundle.onboarding
|
|
22547
|
+
};
|
|
22548
|
+
});
|
|
22549
|
+
}
|
|
22550
|
+
function getTemplate2(name) {
|
|
22551
|
+
if (!isTemplateName(name)) return null;
|
|
22552
|
+
const cache = ensureLoaded();
|
|
22553
|
+
return cache.get(name) ?? null;
|
|
22554
|
+
}
|
|
22555
|
+
function getTemplateEntry(name) {
|
|
22556
|
+
const bundle = getTemplate2(name);
|
|
22557
|
+
if (!bundle) return null;
|
|
22558
|
+
return { metadata: bundle.metadata, onboarding: bundle.onboarding };
|
|
22559
|
+
}
|
|
22560
|
+
|
|
22561
|
+
// src/policy-engine/null-policy.ts
|
|
22562
|
+
function buildNullPolicy(params) {
|
|
22563
|
+
const denyRule = (slot) => ({
|
|
22564
|
+
slot,
|
|
22565
|
+
mode: "deny",
|
|
22566
|
+
grants: []
|
|
22567
|
+
});
|
|
22568
|
+
return {
|
|
22569
|
+
schema_version: "0.1",
|
|
22570
|
+
agent_id: params.agent_id,
|
|
22571
|
+
fortress_id: params.fortress_id,
|
|
22572
|
+
policy_version: 0,
|
|
22573
|
+
slots: {
|
|
22574
|
+
memory: denyRule("memory"),
|
|
22575
|
+
credentials: denyRule("credentials"),
|
|
22576
|
+
plans: denyRule("plans"),
|
|
22577
|
+
outputs: denyRule("outputs")
|
|
22578
|
+
},
|
|
22579
|
+
capabilities: {
|
|
22580
|
+
concordia_commitment_classes: [],
|
|
22581
|
+
honeypot_skill_ids: [],
|
|
22582
|
+
is_sentinel: false
|
|
22583
|
+
},
|
|
22584
|
+
auto_trigger_ladder: {
|
|
22585
|
+
honeypot_auto_freeze: true,
|
|
22586
|
+
threshold_rule_action: "operator_approved",
|
|
22587
|
+
ml_anomaly_action: "operator_approved"
|
|
22588
|
+
},
|
|
22589
|
+
source_english: "(null policy \u2014 no operator authoring yet; hermetic default denies all four slots)",
|
|
22590
|
+
compiled_at: "1970-01-01T00:00:00.000Z"
|
|
22591
|
+
};
|
|
22592
|
+
}
|
|
22593
|
+
|
|
22594
|
+
// src/policy-engine/channel-templates.ts
|
|
22595
|
+
function basePolicy(params) {
|
|
22596
|
+
if (params.merge_into) {
|
|
22597
|
+
const p2 = {
|
|
22598
|
+
...params.merge_into,
|
|
22599
|
+
policy_version: params.policy_version,
|
|
22600
|
+
compiled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22601
|
+
slots: {
|
|
22602
|
+
memory: cloneRule(params.merge_into.slots.memory),
|
|
22603
|
+
credentials: cloneRule(params.merge_into.slots.credentials),
|
|
22604
|
+
plans: cloneRule(params.merge_into.slots.plans),
|
|
22605
|
+
outputs: cloneRule(params.merge_into.slots.outputs)
|
|
22606
|
+
},
|
|
22607
|
+
capabilities: {
|
|
22608
|
+
...params.merge_into.capabilities,
|
|
22609
|
+
concordia_commitment_classes: [
|
|
22610
|
+
...params.merge_into.capabilities.concordia_commitment_classes
|
|
22611
|
+
],
|
|
22612
|
+
honeypot_skill_ids: [...params.merge_into.capabilities.honeypot_skill_ids]
|
|
22613
|
+
},
|
|
22614
|
+
auto_trigger_ladder: { ...params.merge_into.auto_trigger_ladder }
|
|
22615
|
+
};
|
|
22616
|
+
if (params.parent_version !== void 0) p2.parent_version = params.parent_version;
|
|
22617
|
+
else delete p2.parent_version;
|
|
22618
|
+
return p2;
|
|
22619
|
+
}
|
|
22620
|
+
const p = buildNullPolicy({
|
|
22621
|
+
agent_id: params.agent_id,
|
|
22622
|
+
fortress_id: params.fortress_id
|
|
22623
|
+
});
|
|
22624
|
+
p.policy_version = params.policy_version;
|
|
22625
|
+
p.compiled_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
22626
|
+
if (params.parent_version !== void 0) p.parent_version = params.parent_version;
|
|
22627
|
+
return p;
|
|
22628
|
+
}
|
|
22629
|
+
function cloneRule(r) {
|
|
22630
|
+
return {
|
|
22631
|
+
slot: r.slot,
|
|
22632
|
+
mode: r.mode,
|
|
22633
|
+
grants: r.grants.map((g) => ({ ...g, scope: g.scope ? { ...g.scope } : void 0 }))
|
|
22634
|
+
};
|
|
22635
|
+
}
|
|
22636
|
+
function grantOn(policy, slot, grant) {
|
|
22637
|
+
const rule = policy.slots[slot];
|
|
22638
|
+
rule.mode = "grant";
|
|
22639
|
+
const dup = rule.grants.find(
|
|
22640
|
+
(g) => g.counterparty === grant.counterparty && g.action === grant.action
|
|
22641
|
+
);
|
|
22642
|
+
if (dup) {
|
|
22643
|
+
if (grant.scope) {
|
|
22644
|
+
dup.scope = { ...dup.scope ?? {}, ...grant.scope };
|
|
22645
|
+
}
|
|
22646
|
+
return;
|
|
22647
|
+
}
|
|
22648
|
+
rule.grants.push(grant);
|
|
22649
|
+
rule.grants.sort((a, b) => {
|
|
22650
|
+
if (a.counterparty === b.counterparty) return a.action.localeCompare(b.action);
|
|
22651
|
+
return a.counterparty.localeCompare(b.counterparty);
|
|
22652
|
+
});
|
|
22653
|
+
}
|
|
22654
|
+
var readOutputsOnly = (params) => {
|
|
22655
|
+
const p = basePolicy(params);
|
|
22656
|
+
p.source_english = `${params.counterparty} may read ${params.agent_id}'s outputs \u2014 read-only, no other access.`;
|
|
22657
|
+
grantOn(p, "outputs", {
|
|
22658
|
+
counterparty: params.counterparty,
|
|
22659
|
+
action: "read",
|
|
22660
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
22661
|
+
});
|
|
22662
|
+
return p;
|
|
22663
|
+
};
|
|
22664
|
+
var bidirectionalSync = (params) => {
|
|
22665
|
+
const p = basePolicy(params);
|
|
22666
|
+
p.source_english = `Bidirectional sync between ${params.agent_id} and ${params.counterparty} on memory + outputs.`;
|
|
22667
|
+
for (const slot of ["memory", "outputs"]) {
|
|
22668
|
+
for (const action of ["read", "subscribe"]) {
|
|
22669
|
+
grantOn(p, slot, {
|
|
22670
|
+
counterparty: params.counterparty,
|
|
22671
|
+
action,
|
|
22672
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
22673
|
+
});
|
|
22674
|
+
}
|
|
22675
|
+
}
|
|
22676
|
+
return p;
|
|
22677
|
+
};
|
|
22678
|
+
var credentialShareScoped = (params) => {
|
|
22679
|
+
const p = basePolicy(params);
|
|
22680
|
+
const credentialId = (params.scope && typeof params.scope.credential_id === "string" ? params.scope.credential_id : void 0) ?? "(unspecified)";
|
|
22681
|
+
p.source_english = `${params.agent_id} may share credential "${credentialId}" with ${params.counterparty}; no other credential access.`;
|
|
22682
|
+
grantOn(p, "credentials", {
|
|
22683
|
+
counterparty: params.counterparty,
|
|
22684
|
+
action: "share",
|
|
22685
|
+
scope: {
|
|
22686
|
+
credential_id: credentialId,
|
|
22687
|
+
...params.scope ?? {}
|
|
22688
|
+
}
|
|
22689
|
+
});
|
|
22690
|
+
return p;
|
|
22691
|
+
};
|
|
22692
|
+
var planInspectReadOnly = (params) => {
|
|
22693
|
+
const p = basePolicy(params);
|
|
22694
|
+
p.source_english = `${params.counterparty} may read-only inspect ${params.agent_id}'s plans.`;
|
|
22695
|
+
grantOn(p, "plans", {
|
|
22696
|
+
counterparty: params.counterparty,
|
|
22697
|
+
action: "read",
|
|
22698
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
22699
|
+
});
|
|
22700
|
+
return p;
|
|
22701
|
+
};
|
|
22702
|
+
var escrowHandoff = (params) => {
|
|
22703
|
+
const p = basePolicy(params);
|
|
22704
|
+
p.source_english = `${params.agent_id} may escrow-handoff outputs and plan-read to ${params.counterparty}. Commitment class: intra-mesh-escrow.`;
|
|
22705
|
+
grantOn(p, "plans", {
|
|
22706
|
+
counterparty: params.counterparty,
|
|
22707
|
+
action: "read",
|
|
22708
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
22709
|
+
});
|
|
22710
|
+
for (const action of ["read", "subscribe"]) {
|
|
22711
|
+
grantOn(p, "outputs", {
|
|
22712
|
+
counterparty: params.counterparty,
|
|
22713
|
+
action,
|
|
22714
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
22715
|
+
});
|
|
22716
|
+
}
|
|
22717
|
+
const cls = "intra-mesh-escrow";
|
|
22718
|
+
if (!p.capabilities.concordia_commitment_classes.includes(cls)) {
|
|
22719
|
+
p.capabilities.concordia_commitment_classes.push(cls);
|
|
22720
|
+
p.capabilities.concordia_commitment_classes.sort();
|
|
22721
|
+
}
|
|
22722
|
+
return p;
|
|
22723
|
+
};
|
|
22724
|
+
var REGISTRY = {
|
|
22725
|
+
"read-outputs-only": {
|
|
22726
|
+
id: "read-outputs-only",
|
|
22727
|
+
label: "Read outputs only",
|
|
22728
|
+
description: "Counterparty may read this agent's outputs. No memory, credentials, or plans access.",
|
|
22729
|
+
factory: readOutputsOnly
|
|
22730
|
+
},
|
|
22731
|
+
"bidirectional-sync": {
|
|
22732
|
+
id: "bidirectional-sync",
|
|
22733
|
+
label: "Bidirectional memory + output sync",
|
|
22734
|
+
description: "Both agents may read and subscribe to each other's memory and outputs. Credentials and plans remain hermetic.",
|
|
22735
|
+
factory: bidirectionalSync
|
|
22736
|
+
},
|
|
22737
|
+
"credential-share-scoped": {
|
|
22738
|
+
id: "credential-share-scoped",
|
|
22739
|
+
label: "Scoped credential share",
|
|
22740
|
+
description: "Share one specific credential with counterparty. Requires scope.credential_id. No broad credential access.",
|
|
22741
|
+
factory: credentialShareScoped
|
|
22742
|
+
},
|
|
22743
|
+
"plan-inspect-read-only": {
|
|
22744
|
+
id: "plan-inspect-read-only",
|
|
22745
|
+
label: "Plan inspect (read-only)",
|
|
22746
|
+
description: "Counterparty may read-only inspect this agent's plans. Intended for supervisor / sentinel patterns.",
|
|
22747
|
+
factory: planInspectReadOnly
|
|
22748
|
+
},
|
|
22749
|
+
"escrow-handoff": {
|
|
22750
|
+
id: "escrow-handoff",
|
|
22751
|
+
label: "Escrow handoff",
|
|
22752
|
+
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.",
|
|
22753
|
+
factory: escrowHandoff
|
|
22754
|
+
}
|
|
22755
|
+
};
|
|
22756
|
+
function applyChannelTemplate(id, params) {
|
|
22757
|
+
const entry = REGISTRY[id];
|
|
22758
|
+
if (!entry) {
|
|
22759
|
+
throw new Error(`unknown channel template: ${id}`);
|
|
22760
|
+
}
|
|
22761
|
+
return entry.factory(params);
|
|
22762
|
+
}
|
|
22763
|
+
|
|
22764
|
+
// src/mesh/errors.ts
|
|
22765
|
+
var MeshError = class extends Error {
|
|
22766
|
+
constructor(message) {
|
|
22767
|
+
super(message);
|
|
22768
|
+
this.name = "MeshError";
|
|
22769
|
+
}
|
|
22770
|
+
};
|
|
22771
|
+
var MeshEnvelopeError = class extends MeshError {
|
|
22772
|
+
constructor(message) {
|
|
22773
|
+
super(message);
|
|
22774
|
+
this.name = "MeshEnvelopeError";
|
|
22775
|
+
}
|
|
22776
|
+
};
|
|
22777
|
+
var MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
|
|
22778
|
+
constructor(key) {
|
|
22779
|
+
super(
|
|
22780
|
+
`v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
|
|
22781
|
+
);
|
|
22782
|
+
this.name = "MeshReservedExtensionKeyError";
|
|
22783
|
+
}
|
|
22784
|
+
};
|
|
22785
|
+
var MeshReservedEventTypeError = class extends MeshEnvelopeError {
|
|
22786
|
+
constructor(eventType) {
|
|
22787
|
+
super(
|
|
22788
|
+
`v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
|
|
22789
|
+
);
|
|
22790
|
+
this.name = "MeshReservedEventTypeError";
|
|
22791
|
+
}
|
|
22792
|
+
};
|
|
22793
|
+
|
|
22794
|
+
// src/mesh/canonical-json.ts
|
|
22795
|
+
var MeshCanonicalJsonError = class extends MeshError {
|
|
22796
|
+
constructor(message) {
|
|
22797
|
+
super(message);
|
|
22798
|
+
this.name = "MeshCanonicalJsonError";
|
|
22799
|
+
}
|
|
22800
|
+
};
|
|
22801
|
+
function canonicalize2(value) {
|
|
22802
|
+
if (value === void 0) {
|
|
22803
|
+
throw new MeshCanonicalJsonError(
|
|
22804
|
+
"canonicalize(): top-level undefined is not serializable"
|
|
22805
|
+
);
|
|
22806
|
+
}
|
|
22807
|
+
return encode(value);
|
|
22808
|
+
}
|
|
22809
|
+
function encode(value) {
|
|
22810
|
+
if (value === null) return "null";
|
|
22811
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
22812
|
+
if (typeof value === "number") {
|
|
22813
|
+
if (!Number.isFinite(value)) {
|
|
22814
|
+
throw new MeshCanonicalJsonError(
|
|
22815
|
+
`canonicalize(): non-finite number (${String(value)}) is not serializable`
|
|
22816
|
+
);
|
|
22817
|
+
}
|
|
22818
|
+
return JSON.stringify(value);
|
|
22819
|
+
}
|
|
22820
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
22821
|
+
if (Array.isArray(value)) return encodeArray(value);
|
|
22822
|
+
if (typeof value === "object") return encodeObject(value);
|
|
22823
|
+
throw new MeshCanonicalJsonError(
|
|
22824
|
+
`canonicalize(): unsupported type ${typeof value}`
|
|
22825
|
+
);
|
|
22826
|
+
}
|
|
22827
|
+
function encodeArray(arr) {
|
|
22828
|
+
const parts = [];
|
|
22829
|
+
for (const item of arr) {
|
|
22830
|
+
parts.push(item === void 0 ? "null" : encode(item));
|
|
22831
|
+
}
|
|
22832
|
+
return "[" + parts.join(",") + "]";
|
|
22833
|
+
}
|
|
22834
|
+
function encodeObject(obj) {
|
|
22835
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
22836
|
+
const parts = [];
|
|
22837
|
+
for (const k of keys) {
|
|
22838
|
+
parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
|
|
22839
|
+
}
|
|
22840
|
+
return "{" + parts.join(",") + "}";
|
|
22841
|
+
}
|
|
22842
|
+
function canonicalizeToBytes(value) {
|
|
22843
|
+
return new TextEncoder().encode(canonicalize2(value));
|
|
22844
|
+
}
|
|
22845
|
+
|
|
22846
|
+
// src/policy-engine/canonical-policy.ts
|
|
22847
|
+
init_encoding();
|
|
22848
|
+
|
|
22849
|
+
// src/policy-engine/errors.ts
|
|
22850
|
+
var PolicyEngineError = class extends Error {
|
|
22851
|
+
constructor(message) {
|
|
22852
|
+
super(message);
|
|
22853
|
+
this.name = "PolicyEngineError";
|
|
22854
|
+
}
|
|
22855
|
+
};
|
|
22856
|
+
var CompiledPolicyShapeError = class extends PolicyEngineError {
|
|
22857
|
+
constructor(message) {
|
|
22858
|
+
super(message);
|
|
22859
|
+
this.name = "CompiledPolicyShapeError";
|
|
22860
|
+
}
|
|
22861
|
+
};
|
|
22862
|
+
|
|
22863
|
+
// src/policy-engine/canonical-policy.ts
|
|
22864
|
+
function checkSlotGrant(value, path) {
|
|
22865
|
+
if (typeof value !== "object" || value === null) {
|
|
22866
|
+
throw new CompiledPolicyShapeError(`${path}: grant must be an object`);
|
|
22867
|
+
}
|
|
22868
|
+
const g = value;
|
|
22869
|
+
if (typeof g.counterparty !== "string" || g.counterparty.length === 0) {
|
|
22870
|
+
throw new CompiledPolicyShapeError(
|
|
22871
|
+
`${path}.counterparty must be a non-empty string`
|
|
22872
|
+
);
|
|
22873
|
+
}
|
|
22874
|
+
if (typeof g.action !== "string" || g.action.length === 0) {
|
|
22875
|
+
throw new CompiledPolicyShapeError(
|
|
22876
|
+
`${path}.action must be a non-empty string`
|
|
22877
|
+
);
|
|
22878
|
+
}
|
|
22879
|
+
if (g.scope !== void 0) {
|
|
22880
|
+
if (typeof g.scope !== "object" || g.scope === null || Array.isArray(g.scope)) {
|
|
22881
|
+
throw new CompiledPolicyShapeError(
|
|
22882
|
+
`${path}.scope must be an object when present`
|
|
22883
|
+
);
|
|
22884
|
+
}
|
|
22885
|
+
}
|
|
22886
|
+
if (g.max_uses_per_day !== void 0) {
|
|
22887
|
+
if (typeof g.max_uses_per_day !== "number" || !Number.isInteger(g.max_uses_per_day) || g.max_uses_per_day < 0) {
|
|
22888
|
+
throw new CompiledPolicyShapeError(
|
|
22889
|
+
`${path}.max_uses_per_day must be a non-negative integer when present`
|
|
22890
|
+
);
|
|
22891
|
+
}
|
|
22892
|
+
}
|
|
22893
|
+
}
|
|
22894
|
+
function checkSlotRule(slot, value, path) {
|
|
22895
|
+
if (typeof value !== "object" || value === null) {
|
|
22896
|
+
throw new CompiledPolicyShapeError(`${path}: slot rule must be an object`);
|
|
22897
|
+
}
|
|
22898
|
+
const r = value;
|
|
22899
|
+
if (r.slot !== slot) {
|
|
22900
|
+
throw new CompiledPolicyShapeError(
|
|
22901
|
+
`${path}.slot expected ${slot}, got ${String(r.slot)}`
|
|
22902
|
+
);
|
|
22903
|
+
}
|
|
22904
|
+
if (r.mode !== "deny" && r.mode !== "grant") {
|
|
22905
|
+
throw new CompiledPolicyShapeError(
|
|
22906
|
+
`${path}.mode must be "deny" or "grant"`
|
|
22907
|
+
);
|
|
22908
|
+
}
|
|
22909
|
+
if (!Array.isArray(r.grants)) {
|
|
22910
|
+
throw new CompiledPolicyShapeError(
|
|
22911
|
+
`${path}.grants must be an array (possibly empty)`
|
|
22912
|
+
);
|
|
22913
|
+
}
|
|
22914
|
+
if (r.mode === "deny" && r.grants.length > 0) {
|
|
22915
|
+
throw new CompiledPolicyShapeError(
|
|
22916
|
+
`${path}: deny-mode slot rule must carry zero grants`
|
|
22917
|
+
);
|
|
22918
|
+
}
|
|
22919
|
+
r.grants.forEach((g, i) => checkSlotGrant(g, `${path}.grants[${i}]`));
|
|
22920
|
+
}
|
|
22921
|
+
function validateCompiledPolicyShape(candidate) {
|
|
22922
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
22923
|
+
throw new CompiledPolicyShapeError("compiled policy must be an object");
|
|
22924
|
+
}
|
|
22925
|
+
const p = candidate;
|
|
22926
|
+
if (p.schema_version !== COMPILED_POLICY_SCHEMA_VERSION) {
|
|
22927
|
+
throw new CompiledPolicyShapeError(
|
|
22928
|
+
`schema_version ${String(p.schema_version)} not supported at v0.1 (expected "${COMPILED_POLICY_SCHEMA_VERSION}")`
|
|
22929
|
+
);
|
|
22930
|
+
}
|
|
22931
|
+
if (typeof p.agent_id !== "string" || p.agent_id.length === 0) {
|
|
22932
|
+
throw new CompiledPolicyShapeError("agent_id must be a non-empty string");
|
|
22933
|
+
}
|
|
22934
|
+
if (typeof p.fortress_id !== "string" || p.fortress_id.length === 0) {
|
|
22935
|
+
throw new CompiledPolicyShapeError(
|
|
22936
|
+
"fortress_id must be a non-empty string"
|
|
22937
|
+
);
|
|
22938
|
+
}
|
|
22939
|
+
if (typeof p.policy_version !== "number" || !Number.isInteger(p.policy_version) || p.policy_version < 0) {
|
|
22940
|
+
throw new CompiledPolicyShapeError(
|
|
22941
|
+
"policy_version must be a non-negative integer"
|
|
22942
|
+
);
|
|
22943
|
+
}
|
|
22944
|
+
if (p.parent_version !== void 0 && (typeof p.parent_version !== "number" || !Number.isInteger(p.parent_version) || p.parent_version < 0)) {
|
|
22945
|
+
throw new CompiledPolicyShapeError(
|
|
22946
|
+
"parent_version must be a non-negative integer when present"
|
|
22947
|
+
);
|
|
22948
|
+
}
|
|
22949
|
+
if (typeof p.slots !== "object" || p.slots === null) {
|
|
22950
|
+
throw new CompiledPolicyShapeError("slots must be an object");
|
|
22951
|
+
}
|
|
22952
|
+
const slots = p.slots;
|
|
22953
|
+
const slotKeys = Object.keys(slots).sort();
|
|
22954
|
+
const expectedSlotKeys = [...POLICY_SLOTS].sort();
|
|
22955
|
+
if (slotKeys.length !== expectedSlotKeys.length || !slotKeys.every((k, i) => k === expectedSlotKeys[i])) {
|
|
22956
|
+
throw new CompiledPolicyShapeError(
|
|
22957
|
+
`slots must contain exactly ${expectedSlotKeys.join(", ")}; got ${slotKeys.join(", ") || "(none)"}`
|
|
22958
|
+
);
|
|
22959
|
+
}
|
|
22960
|
+
for (const slot of POLICY_SLOTS) {
|
|
22961
|
+
checkSlotRule(slot, slots[slot], `slots.${slot}`);
|
|
22962
|
+
}
|
|
22963
|
+
if (typeof p.capabilities !== "object" || p.capabilities === null) {
|
|
22964
|
+
throw new CompiledPolicyShapeError("capabilities must be an object");
|
|
22965
|
+
}
|
|
22966
|
+
const caps = p.capabilities;
|
|
22967
|
+
if (!Array.isArray(caps.concordia_commitment_classes) || !caps.concordia_commitment_classes.every((c) => typeof c === "string")) {
|
|
22968
|
+
throw new CompiledPolicyShapeError(
|
|
22969
|
+
"capabilities.concordia_commitment_classes must be a string[]"
|
|
22970
|
+
);
|
|
22971
|
+
}
|
|
22972
|
+
if (!Array.isArray(caps.honeypot_skill_ids) || !caps.honeypot_skill_ids.every((c) => typeof c === "string")) {
|
|
22973
|
+
throw new CompiledPolicyShapeError(
|
|
22974
|
+
"capabilities.honeypot_skill_ids must be a string[]"
|
|
22975
|
+
);
|
|
22976
|
+
}
|
|
22977
|
+
if (typeof caps.is_sentinel !== "boolean") {
|
|
22978
|
+
throw new CompiledPolicyShapeError(
|
|
22979
|
+
"capabilities.is_sentinel must be boolean"
|
|
22980
|
+
);
|
|
22981
|
+
}
|
|
22982
|
+
if (typeof p.auto_trigger_ladder !== "object" || p.auto_trigger_ladder === null) {
|
|
22983
|
+
throw new CompiledPolicyShapeError(
|
|
22984
|
+
"auto_trigger_ladder must be an object"
|
|
22985
|
+
);
|
|
22986
|
+
}
|
|
22987
|
+
const lad = p.auto_trigger_ladder;
|
|
22988
|
+
if (typeof lad.honeypot_auto_freeze !== "boolean") {
|
|
22989
|
+
throw new CompiledPolicyShapeError(
|
|
22990
|
+
"auto_trigger_ladder.honeypot_auto_freeze must be boolean"
|
|
22991
|
+
);
|
|
22992
|
+
}
|
|
22993
|
+
if (lad.threshold_rule_action !== "operator_approved" && lad.threshold_rule_action !== "auto") {
|
|
22994
|
+
throw new CompiledPolicyShapeError(
|
|
22995
|
+
'auto_trigger_ladder.threshold_rule_action must be "operator_approved" or "auto"'
|
|
22996
|
+
);
|
|
22997
|
+
}
|
|
22998
|
+
if (lad.ml_anomaly_action !== "operator_approved" && lad.ml_anomaly_action !== "auto") {
|
|
22999
|
+
throw new CompiledPolicyShapeError(
|
|
23000
|
+
'auto_trigger_ladder.ml_anomaly_action must be "operator_approved" or "auto"'
|
|
23001
|
+
);
|
|
23002
|
+
}
|
|
23003
|
+
if (typeof p.source_english !== "string") {
|
|
23004
|
+
throw new CompiledPolicyShapeError("source_english must be a string");
|
|
23005
|
+
}
|
|
23006
|
+
if (typeof p.compiled_at !== "string" || Number.isNaN(Date.parse(p.compiled_at))) {
|
|
23007
|
+
throw new CompiledPolicyShapeError(
|
|
23008
|
+
"compiled_at must be an ISO8601 timestamp string"
|
|
23009
|
+
);
|
|
23010
|
+
}
|
|
23011
|
+
for (const key of slotKeys) {
|
|
23012
|
+
if (!isPolicySlot(key)) {
|
|
23013
|
+
throw new CompiledPolicyShapeError(
|
|
23014
|
+
`slots.${key} is not a recognized policy slot`
|
|
23015
|
+
);
|
|
23016
|
+
}
|
|
23017
|
+
}
|
|
23018
|
+
if (p.egress !== void 0) {
|
|
23019
|
+
checkEgressPolicy(p.egress, "egress");
|
|
23020
|
+
}
|
|
23021
|
+
if (p.budgets !== void 0) {
|
|
23022
|
+
checkBudgetPolicy(p.budgets, "budgets");
|
|
23023
|
+
}
|
|
23024
|
+
if (p.retention !== void 0) {
|
|
23025
|
+
checkRetentionPolicy(p.retention, "retention");
|
|
23026
|
+
}
|
|
23027
|
+
}
|
|
23028
|
+
function checkEgressPolicy(value, path) {
|
|
23029
|
+
if (typeof value !== "object" || value === null) {
|
|
23030
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23031
|
+
}
|
|
23032
|
+
const eg = value;
|
|
23033
|
+
if (!Array.isArray(eg.allowlist)) {
|
|
23034
|
+
throw new CompiledPolicyShapeError(`${path}.allowlist must be an array`);
|
|
23035
|
+
}
|
|
23036
|
+
for (let i = 0; i < eg.allowlist.length; i++) {
|
|
23037
|
+
const rule = eg.allowlist[i];
|
|
23038
|
+
if (typeof rule !== "object" || rule === null) {
|
|
23039
|
+
throw new CompiledPolicyShapeError(
|
|
23040
|
+
`${path}.allowlist[${i}] must be an object`
|
|
23041
|
+
);
|
|
23042
|
+
}
|
|
23043
|
+
const r = rule;
|
|
23044
|
+
if (typeof r.destination !== "string" || r.destination.length === 0) {
|
|
23045
|
+
throw new CompiledPolicyShapeError(
|
|
23046
|
+
`${path}.allowlist[${i}].destination must be a non-empty string`
|
|
23047
|
+
);
|
|
23048
|
+
}
|
|
23049
|
+
if (!Array.isArray(r.methods) || !r.methods.every((m) => typeof m === "string")) {
|
|
23050
|
+
throw new CompiledPolicyShapeError(
|
|
23051
|
+
`${path}.allowlist[${i}].methods must be a string[]`
|
|
23052
|
+
);
|
|
23053
|
+
}
|
|
23054
|
+
}
|
|
23055
|
+
}
|
|
23056
|
+
function checkBudgetLimit(value, path) {
|
|
23057
|
+
if (typeof value !== "object" || value === null) {
|
|
23058
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23059
|
+
}
|
|
23060
|
+
const lim = value;
|
|
23061
|
+
if (typeof lim.amount !== "number" || lim.amount <= 0 || !Number.isFinite(lim.amount)) {
|
|
23062
|
+
throw new CompiledPolicyShapeError(
|
|
23063
|
+
`${path}.amount must be a positive finite number`
|
|
23064
|
+
);
|
|
23065
|
+
}
|
|
23066
|
+
if (!BUDGET_UNITS.includes(lim.unit)) {
|
|
23067
|
+
throw new CompiledPolicyShapeError(
|
|
23068
|
+
`${path}.unit must be one of: ${BUDGET_UNITS.join(", ")}`
|
|
23069
|
+
);
|
|
23070
|
+
}
|
|
23071
|
+
if (lim.soft_warn_threshold !== void 0) {
|
|
23072
|
+
if (typeof lim.soft_warn_threshold !== "number" || lim.soft_warn_threshold <= 0 || lim.soft_warn_threshold >= 1) {
|
|
23073
|
+
throw new CompiledPolicyShapeError(
|
|
23074
|
+
`${path}.soft_warn_threshold must be in (0, 1) when present`
|
|
23075
|
+
);
|
|
23076
|
+
}
|
|
23077
|
+
}
|
|
23078
|
+
}
|
|
23079
|
+
function checkBudgetPolicy(value, path) {
|
|
23080
|
+
if (typeof value !== "object" || value === null) {
|
|
23081
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23082
|
+
}
|
|
23083
|
+
const bp = value;
|
|
23084
|
+
if (bp.daily !== void 0) checkBudgetLimit(bp.daily, `${path}.daily`);
|
|
23085
|
+
if (bp.monthly !== void 0) checkBudgetLimit(bp.monthly, `${path}.monthly`);
|
|
23086
|
+
if (bp.daily === void 0 && bp.monthly === void 0) {
|
|
23087
|
+
throw new CompiledPolicyShapeError(
|
|
23088
|
+
`${path} must have at least one of daily or monthly`
|
|
23089
|
+
);
|
|
23090
|
+
}
|
|
23091
|
+
}
|
|
23092
|
+
function checkRetentionPolicy(value, path) {
|
|
23093
|
+
if (typeof value !== "object" || value === null) {
|
|
23094
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23095
|
+
}
|
|
23096
|
+
const rp = value;
|
|
23097
|
+
if (typeof rp.windows !== "object" || rp.windows === null) {
|
|
23098
|
+
throw new CompiledPolicyShapeError(`${path}.windows must be an object`);
|
|
23099
|
+
}
|
|
23100
|
+
const wins = rp.windows;
|
|
23101
|
+
for (const key of Object.keys(wins)) {
|
|
23102
|
+
if (!isPolicySlot(key)) {
|
|
23103
|
+
throw new CompiledPolicyShapeError(
|
|
23104
|
+
`${path}.windows.${key} is not a recognized policy slot`
|
|
23105
|
+
);
|
|
23106
|
+
}
|
|
23107
|
+
const w = wins[key];
|
|
23108
|
+
if (typeof w !== "object" || w === null) {
|
|
23109
|
+
throw new CompiledPolicyShapeError(
|
|
23110
|
+
`${path}.windows.${key} must be an object`
|
|
23111
|
+
);
|
|
23112
|
+
}
|
|
23113
|
+
const win = w;
|
|
23114
|
+
if (typeof win.max_age_seconds !== "number" || !Number.isInteger(win.max_age_seconds) || win.max_age_seconds <= 0) {
|
|
23115
|
+
throw new CompiledPolicyShapeError(
|
|
23116
|
+
`${path}.windows.${key}.max_age_seconds must be a positive integer`
|
|
23117
|
+
);
|
|
23118
|
+
}
|
|
23119
|
+
if (win.archive !== void 0 && typeof win.archive !== "boolean") {
|
|
23120
|
+
throw new CompiledPolicyShapeError(
|
|
23121
|
+
`${path}.windows.${key}.archive must be boolean when present`
|
|
23122
|
+
);
|
|
23123
|
+
}
|
|
23124
|
+
}
|
|
23125
|
+
}
|
|
23126
|
+
function encodePolicyBlob(policy) {
|
|
23127
|
+
validateCompiledPolicyShape(policy);
|
|
23128
|
+
return toBase64url(canonicalizeToBytes(policy));
|
|
23129
|
+
}
|
|
23130
|
+
|
|
23131
|
+
// src/mesh/envelope.ts
|
|
23132
|
+
init_encoding();
|
|
23133
|
+
init_random();
|
|
23134
|
+
|
|
23135
|
+
// src/mesh/constants.ts
|
|
23136
|
+
var PROTOCOL_VERSION = "0.1";
|
|
23137
|
+
var RESERVED_EVENT_TYPE_PREFIXES = [
|
|
23138
|
+
"EXTENSION_",
|
|
23139
|
+
"cross_fortress_",
|
|
23140
|
+
"multi_master_"
|
|
23141
|
+
];
|
|
23142
|
+
function isReservedEventType(s) {
|
|
23143
|
+
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
23144
|
+
}
|
|
23145
|
+
var RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
23146
|
+
"cross_fortress_read_grant",
|
|
23147
|
+
"cross_fortress_read_query",
|
|
23148
|
+
"cross_fortress_read_response",
|
|
23149
|
+
"multi_master_policy_merge",
|
|
23150
|
+
"audit_replication_full_n_way",
|
|
23151
|
+
"auto_promote_canonical_audit",
|
|
23152
|
+
"agent_live_migration"
|
|
23153
|
+
];
|
|
23154
|
+
function isReservedExtensionKey(k) {
|
|
23155
|
+
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
23156
|
+
}
|
|
23157
|
+
|
|
23158
|
+
// src/mesh/trust-root.ts
|
|
23159
|
+
init_encoding();
|
|
23160
|
+
init_identity();
|
|
23161
|
+
init_random();
|
|
23162
|
+
|
|
23163
|
+
// src/mesh/envelope.ts
|
|
23164
|
+
function packSignedEvent(params) {
|
|
23165
|
+
if (isReservedEventType(params.event_type)) {
|
|
23166
|
+
throw new MeshReservedEventTypeError(params.event_type);
|
|
23167
|
+
}
|
|
23168
|
+
const ext = params.extension_envelope ?? {};
|
|
23169
|
+
for (const key of Object.keys(ext)) {
|
|
23170
|
+
if (isReservedExtensionKey(key)) {
|
|
23171
|
+
throw new MeshReservedExtensionKeyError(key);
|
|
23172
|
+
}
|
|
23173
|
+
}
|
|
23174
|
+
const payloadBytes = canonicalizeToBytes(params.payload);
|
|
23175
|
+
const payload_hash = toBase64url(sha256(payloadBytes));
|
|
23176
|
+
const body = {
|
|
23177
|
+
protocol_version: PROTOCOL_VERSION,
|
|
23178
|
+
event_type: params.event_type,
|
|
23179
|
+
event_id: generateEventId(),
|
|
23180
|
+
emitter_node: params.emitter_node,
|
|
23181
|
+
emitter_principal: params.emitter_principal,
|
|
23182
|
+
fortress_id: params.fortress_id,
|
|
23183
|
+
causal_parents: params.causal_parents ?? [],
|
|
23184
|
+
payload: params.payload,
|
|
23185
|
+
payload_hash,
|
|
23186
|
+
emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23187
|
+
monotonic_seq: params.monotonic_seq,
|
|
23188
|
+
extension_envelope: ext
|
|
23189
|
+
};
|
|
23190
|
+
const bytesToSign = canonicalizeToBytes(body);
|
|
23191
|
+
const nodeSig = ed25519.sign(bytesToSign, params.node_private_key);
|
|
23192
|
+
const evt = {
|
|
23193
|
+
...body,
|
|
23194
|
+
node_signature: toBase64url(nodeSig)
|
|
23195
|
+
};
|
|
23196
|
+
if (params.principal_private_key) {
|
|
23197
|
+
const principalSig = ed25519.sign(bytesToSign, params.principal_private_key);
|
|
23198
|
+
evt.principal_signature = toBase64url(principalSig);
|
|
23199
|
+
}
|
|
23200
|
+
return evt;
|
|
23201
|
+
}
|
|
23202
|
+
function generateEventId() {
|
|
23203
|
+
const bytes = randomBytes(16);
|
|
23204
|
+
let hex = "";
|
|
23205
|
+
for (const b of bytes) hex += b.toString(16).padStart(2, "0");
|
|
23206
|
+
return hex;
|
|
23207
|
+
}
|
|
23208
|
+
|
|
23209
|
+
// src/policy-engine/envelope.ts
|
|
23210
|
+
function packPolicyUpdate(params) {
|
|
23211
|
+
validateCompiledPolicyShape(params.policy);
|
|
23212
|
+
const blob = encodePolicyBlob(params.policy);
|
|
23213
|
+
const payload = {
|
|
23214
|
+
agent_id: params.policy.agent_id,
|
|
23215
|
+
policy_version: params.policy.policy_version,
|
|
23216
|
+
policy_blob: blob,
|
|
23217
|
+
...params.policy.parent_version !== void 0 ? { parent_version: params.policy.parent_version } : {}
|
|
23218
|
+
};
|
|
23219
|
+
return packSignedEvent({
|
|
23220
|
+
event_type: POLICY_UPDATE_EVENT_TYPE,
|
|
23221
|
+
emitter_node: params.emitter_node,
|
|
23222
|
+
emitter_principal: params.emitter_principal,
|
|
23223
|
+
fortress_id: params.policy.fortress_id,
|
|
23224
|
+
causal_parents: params.causal_parents,
|
|
23225
|
+
payload,
|
|
23226
|
+
monotonic_seq: params.monotonic_seq,
|
|
23227
|
+
node_private_key: params.node_private_key,
|
|
23228
|
+
principal_private_key: params.principal_private_key
|
|
23229
|
+
});
|
|
23230
|
+
}
|
|
23231
|
+
|
|
23232
|
+
// src/templates/init.ts
|
|
23233
|
+
function deterministicCompiledAt(version) {
|
|
23234
|
+
const [major, minor, patch] = version.split(".").map(Number);
|
|
23235
|
+
const epoch = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
|
|
23236
|
+
epoch.setUTCDate(epoch.getUTCDate() + (major - 1) * 365 + minor * 30 + patch);
|
|
23237
|
+
return epoch.toISOString();
|
|
23238
|
+
}
|
|
23239
|
+
function buildCompiledPolicyFromTemplate(bundle, params) {
|
|
23240
|
+
const policy = applyChannelTemplate(
|
|
23241
|
+
bundle.metadata.channel,
|
|
23242
|
+
{
|
|
23243
|
+
agent_id: params.agent_id,
|
|
23244
|
+
counterparty: params.counterparty,
|
|
23245
|
+
fortress_id: params.fortress_id,
|
|
23246
|
+
policy_version: params.policy_version
|
|
23247
|
+
}
|
|
23248
|
+
);
|
|
23249
|
+
policy.compiled_at = deterministicCompiledAt(bundle.metadata.version);
|
|
23250
|
+
policy.source_english = bundle.policy_english;
|
|
23251
|
+
if (bundle.defaults.egress.length > 0) {
|
|
23252
|
+
const egress = {
|
|
23253
|
+
allowlist: bundle.defaults.egress.map((e) => ({
|
|
23254
|
+
destination: e.destination,
|
|
23255
|
+
methods: [...e.methods]
|
|
23256
|
+
}))
|
|
23257
|
+
};
|
|
23258
|
+
policy.egress = egress;
|
|
23259
|
+
}
|
|
23260
|
+
const budgets = {};
|
|
23261
|
+
if (bundle.defaults.budgets.daily) {
|
|
23262
|
+
budgets.daily = { ...bundle.defaults.budgets.daily };
|
|
23263
|
+
}
|
|
23264
|
+
if (bundle.defaults.budgets.monthly) {
|
|
23265
|
+
budgets.monthly = { ...bundle.defaults.budgets.monthly };
|
|
23266
|
+
}
|
|
23267
|
+
if (budgets.daily || budgets.monthly) {
|
|
23268
|
+
policy.budgets = budgets;
|
|
23269
|
+
}
|
|
23270
|
+
const windows = bundle.defaults.retention.windows;
|
|
23271
|
+
if (Object.keys(windows).length > 0) {
|
|
23272
|
+
const retention = {
|
|
23273
|
+
windows: {}
|
|
23274
|
+
};
|
|
23275
|
+
for (const [slot, win] of Object.entries(windows)) {
|
|
23276
|
+
if (win) {
|
|
23277
|
+
retention.windows[slot] = { ...win };
|
|
23278
|
+
}
|
|
23279
|
+
}
|
|
23280
|
+
policy.retention = retention;
|
|
23281
|
+
}
|
|
23282
|
+
const classes = bundle.commitments.shapes.map((s) => s.commitment_class);
|
|
23283
|
+
for (const cls of classes) {
|
|
23284
|
+
if (!policy.capabilities.concordia_commitment_classes.includes(cls)) {
|
|
23285
|
+
policy.capabilities.concordia_commitment_classes.push(cls);
|
|
23286
|
+
}
|
|
23287
|
+
}
|
|
23288
|
+
policy.capabilities.concordia_commitment_classes.sort();
|
|
23289
|
+
validateCompiledPolicyShape(policy);
|
|
23290
|
+
return policy;
|
|
23291
|
+
}
|
|
23292
|
+
function initTemplate(params) {
|
|
23293
|
+
const bundle = getTemplate2(params.template_name);
|
|
23294
|
+
if (!bundle) {
|
|
23295
|
+
throw new Error(`unknown template: ${params.template_name}`);
|
|
23296
|
+
}
|
|
23297
|
+
const compiled = buildCompiledPolicyFromTemplate(bundle, {
|
|
23298
|
+
agent_id: params.agent_id,
|
|
23299
|
+
fortress_id: params.fortress_id,
|
|
23300
|
+
counterparty: params.counterparty ?? "*",
|
|
23301
|
+
policy_version: params.policy_version ?? 1
|
|
23302
|
+
});
|
|
23303
|
+
const signed_event = packPolicyUpdate({
|
|
23304
|
+
policy: compiled,
|
|
23305
|
+
emitter_node: params.emitter_node,
|
|
23306
|
+
emitter_principal: params.emitter_principal,
|
|
23307
|
+
monotonic_seq: params.monotonic_seq,
|
|
23308
|
+
node_private_key: params.node_private_key,
|
|
23309
|
+
principal_private_key: params.principal_private_key
|
|
23310
|
+
});
|
|
23311
|
+
return { compiled, signed_event, bundle };
|
|
23312
|
+
}
|
|
23313
|
+
var DEFAULT_STORAGE_DIR = ".sanctuary";
|
|
23314
|
+
var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
|
|
23315
|
+
function keychainServiceFor(storagePath, home = homedir()) {
|
|
23316
|
+
const defaultPath = join(home, DEFAULT_STORAGE_DIR);
|
|
23317
|
+
if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
|
|
23318
|
+
const digest = sha256(Buffer.from(storagePath, "utf-8"));
|
|
23319
|
+
const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
|
|
23320
|
+
return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
|
|
23321
|
+
}
|
|
23322
|
+
var RUNTIME_FILE_NAME = "runtime.json";
|
|
23323
|
+
function runtimePath(storagePath) {
|
|
23324
|
+
return join(storagePath, RUNTIME_FILE_NAME);
|
|
23325
|
+
}
|
|
23326
|
+
async function readTenantRuntime(storagePath) {
|
|
23327
|
+
try {
|
|
23328
|
+
const raw = await readFile(runtimePath(storagePath), "utf-8");
|
|
23329
|
+
const parsed = JSON.parse(raw);
|
|
23330
|
+
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") {
|
|
23331
|
+
return null;
|
|
23332
|
+
}
|
|
23333
|
+
const state = {
|
|
23334
|
+
version: parsed.version,
|
|
23335
|
+
pid: parsed.pid,
|
|
23336
|
+
started_at: parsed.started_at,
|
|
23337
|
+
dashboard_host: parsed.dashboard_host,
|
|
23338
|
+
dashboard_port: parsed.dashboard_port,
|
|
23339
|
+
mode: parsed.mode
|
|
23340
|
+
};
|
|
23341
|
+
if (typeof parsed.webhook_callback_port === "number") {
|
|
23342
|
+
state.webhook_callback_port = parsed.webhook_callback_port;
|
|
23343
|
+
}
|
|
23344
|
+
if (typeof parsed.webhook_callback_host === "string") {
|
|
23345
|
+
state.webhook_callback_host = parsed.webhook_callback_host;
|
|
23346
|
+
}
|
|
23347
|
+
return state;
|
|
23348
|
+
} catch {
|
|
23349
|
+
return null;
|
|
23350
|
+
}
|
|
23351
|
+
}
|
|
23352
|
+
|
|
23353
|
+
// src/cli/agents/discovery.ts
|
|
23354
|
+
var EXTRAS_FILE_NAME = "agents-extra.json";
|
|
23355
|
+
async function isTenantDir(path) {
|
|
23356
|
+
const [hasState, hasProfile, hasFallback] = await Promise.all([
|
|
23357
|
+
dirExists(join(path, "state")),
|
|
23358
|
+
fileExists2(join(path, "cocoon-profile.json")),
|
|
23359
|
+
fileExists2(join(path, "passphrase.enc"))
|
|
23360
|
+
]);
|
|
23361
|
+
const initialized = hasState;
|
|
23362
|
+
let passphraseStatus;
|
|
23363
|
+
if (hasFallback) passphraseStatus = "fallback-file";
|
|
23364
|
+
else if (hasProfile || hasState) passphraseStatus = "keychain";
|
|
23365
|
+
else passphraseStatus = "not-initialized";
|
|
23366
|
+
return { initialized, hasProfile, passphraseStatus };
|
|
23367
|
+
}
|
|
23368
|
+
async function dirExists(path) {
|
|
23369
|
+
try {
|
|
23370
|
+
const s = await stat(path);
|
|
23371
|
+
return s.isDirectory();
|
|
23372
|
+
} catch {
|
|
23373
|
+
return false;
|
|
23374
|
+
}
|
|
23375
|
+
}
|
|
23376
|
+
async function fileExists2(path) {
|
|
23377
|
+
try {
|
|
23378
|
+
const s = await stat(path);
|
|
23379
|
+
return s.isFile();
|
|
23380
|
+
} catch {
|
|
23381
|
+
return false;
|
|
23382
|
+
}
|
|
23383
|
+
}
|
|
23384
|
+
async function newestAuditMtime(storagePath) {
|
|
23385
|
+
const auditDir = join(storagePath, "state", "_audit");
|
|
23386
|
+
let entries = [];
|
|
23387
|
+
try {
|
|
23388
|
+
entries = await readdir(auditDir);
|
|
23389
|
+
} catch {
|
|
23390
|
+
return null;
|
|
23391
|
+
}
|
|
23392
|
+
let newest = 0;
|
|
23393
|
+
for (const name of entries) {
|
|
23394
|
+
try {
|
|
23395
|
+
const s = await stat(join(auditDir, name));
|
|
23396
|
+
if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
|
|
23397
|
+
} catch {
|
|
23398
|
+
}
|
|
23399
|
+
}
|
|
23400
|
+
if (newest === 0) return null;
|
|
23401
|
+
return new Date(newest).toISOString();
|
|
23402
|
+
}
|
|
23403
|
+
async function readExtraPaths(root, env) {
|
|
23404
|
+
const out = [];
|
|
23405
|
+
const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
|
|
23406
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
23407
|
+
for (const part of fromEnv.split(":")) {
|
|
23408
|
+
const trimmed = part.trim();
|
|
23409
|
+
if (trimmed.length > 0) out.push(resolve(trimmed));
|
|
23410
|
+
}
|
|
23411
|
+
}
|
|
23412
|
+
try {
|
|
23413
|
+
const raw = await readFile(join(root, EXTRAS_FILE_NAME), "utf-8");
|
|
23414
|
+
const parsed = JSON.parse(raw);
|
|
23415
|
+
if (Array.isArray(parsed)) {
|
|
23416
|
+
for (const p of parsed) {
|
|
23417
|
+
if (typeof p === "string" && p.trim().length > 0) out.push(resolve(p));
|
|
23418
|
+
}
|
|
23419
|
+
}
|
|
23420
|
+
} catch {
|
|
23421
|
+
}
|
|
23422
|
+
return Array.from(new Set(out));
|
|
23423
|
+
}
|
|
23424
|
+
async function describeTenant(name, storagePath, home) {
|
|
23425
|
+
const exists = await dirExists(storagePath);
|
|
23426
|
+
if (!exists) return null;
|
|
23427
|
+
const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
|
|
23428
|
+
if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
|
|
23429
|
+
return null;
|
|
23430
|
+
}
|
|
23431
|
+
const last_activity = await newestAuditMtime(storagePath);
|
|
23432
|
+
const runtime = await readTenantRuntime(storagePath);
|
|
23433
|
+
return {
|
|
23434
|
+
name,
|
|
23435
|
+
storage_path: storagePath,
|
|
23436
|
+
exists: true,
|
|
23437
|
+
initialized,
|
|
23438
|
+
has_cocoon_profile: hasProfile,
|
|
23439
|
+
keychain_service: keychainServiceFor(storagePath, home),
|
|
23440
|
+
passphrase_status: passphraseStatus,
|
|
23441
|
+
last_activity,
|
|
23442
|
+
runtime
|
|
23443
|
+
};
|
|
23444
|
+
}
|
|
23445
|
+
async function discoverTenants(options = {}) {
|
|
23446
|
+
const home = options.home ?? homedir();
|
|
23447
|
+
const env = options.env ?? process.env;
|
|
23448
|
+
const root = options.root ?? join(home, DEFAULT_STORAGE_DIR);
|
|
23449
|
+
const tenants = [];
|
|
23450
|
+
const rootTenant = await describeTenant("default", root, home);
|
|
23451
|
+
if (rootTenant) tenants.push(rootTenant);
|
|
23452
|
+
let children = [];
|
|
23453
|
+
try {
|
|
23454
|
+
children = await readdir(root);
|
|
23455
|
+
} catch {
|
|
23456
|
+
}
|
|
23457
|
+
for (const child of children) {
|
|
23458
|
+
const childPath = join(root, child);
|
|
23459
|
+
if (child.startsWith(".")) continue;
|
|
23460
|
+
if (child === "state" || child === "backup" || child === "config") continue;
|
|
23461
|
+
const s = await stat(childPath).catch(() => null);
|
|
23462
|
+
if (!s || !s.isDirectory()) continue;
|
|
23463
|
+
const desc = await describeTenant(child, childPath, home);
|
|
23464
|
+
if (desc) tenants.push(desc);
|
|
23465
|
+
}
|
|
23466
|
+
const extras = await readExtraPaths(root, env);
|
|
23467
|
+
for (const extra of extras) {
|
|
23468
|
+
if (tenants.some((t) => t.storage_path === extra)) continue;
|
|
23469
|
+
const desc = await describeTenant(basename(extra), extra, home);
|
|
23470
|
+
if (desc) tenants.push(desc);
|
|
23471
|
+
}
|
|
23472
|
+
tenants.sort((a, b) => {
|
|
23473
|
+
if (a.name === "default") return -1;
|
|
23474
|
+
if (b.name === "default") return 1;
|
|
23475
|
+
return a.name.localeCompare(b.name);
|
|
23476
|
+
});
|
|
23477
|
+
return tenants;
|
|
23478
|
+
}
|
|
23479
|
+
async function findTenant(name, options = {}) {
|
|
23480
|
+
const tenants = await discoverTenants(options);
|
|
23481
|
+
return tenants.find((t) => t.name === name) ?? null;
|
|
23482
|
+
}
|
|
23483
|
+
|
|
22220
23484
|
// src/dashboard/api.ts
|
|
22221
23485
|
function constantTimeEquals(a, b) {
|
|
22222
23486
|
if (a.length !== b.length) return false;
|
|
@@ -22247,6 +23511,22 @@ function writeJSON(res, status, payload) {
|
|
|
22247
23511
|
});
|
|
22248
23512
|
res.end(JSON.stringify(payload));
|
|
22249
23513
|
}
|
|
23514
|
+
async function readJSONBody(req) {
|
|
23515
|
+
const chunks = [];
|
|
23516
|
+
let size = 0;
|
|
23517
|
+
const MAX = 256 * 1024;
|
|
23518
|
+
for await (const chunk of req) {
|
|
23519
|
+
size += chunk.length;
|
|
23520
|
+
if (size > MAX) throw new Error("request body too large");
|
|
23521
|
+
chunks.push(chunk);
|
|
23522
|
+
}
|
|
23523
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
23524
|
+
if (!body) return {};
|
|
23525
|
+
return JSON.parse(body);
|
|
23526
|
+
}
|
|
23527
|
+
function generateEphemeralKey() {
|
|
23528
|
+
return new Uint8Array(randomBytes$1(32));
|
|
23529
|
+
}
|
|
22250
23530
|
function writeText(res, status, body, contentType = "text/plain") {
|
|
22251
23531
|
res.writeHead(status, {
|
|
22252
23532
|
"Content-Type": contentType,
|
|
@@ -22299,6 +23579,102 @@ async function handleRequest(deps, req, res) {
|
|
|
22299
23579
|
await handleStream(deps, res);
|
|
22300
23580
|
return true;
|
|
22301
23581
|
}
|
|
23582
|
+
if (method === "GET" && path === "/api/templates") {
|
|
23583
|
+
try {
|
|
23584
|
+
const templates = listTemplates();
|
|
23585
|
+
writeJSON(res, 200, { templates });
|
|
23586
|
+
} catch (err) {
|
|
23587
|
+
writeJSON(res, 500, {
|
|
23588
|
+
error: "template_load_failed",
|
|
23589
|
+
message: err.message
|
|
23590
|
+
});
|
|
23591
|
+
}
|
|
23592
|
+
return true;
|
|
23593
|
+
}
|
|
23594
|
+
const templateMatch = /^\/api\/templates\/([^/]+)$/.exec(path);
|
|
23595
|
+
if (method === "GET" && templateMatch) {
|
|
23596
|
+
const name = decodeURIComponent(templateMatch[1]);
|
|
23597
|
+
try {
|
|
23598
|
+
const entry = getTemplateEntry(name);
|
|
23599
|
+
if (!entry) {
|
|
23600
|
+
writeJSON(res, 404, { error: "template_not_found", name });
|
|
23601
|
+
return true;
|
|
23602
|
+
}
|
|
23603
|
+
writeJSON(res, 200, entry);
|
|
23604
|
+
} catch (err) {
|
|
23605
|
+
writeJSON(res, 500, {
|
|
23606
|
+
error: "template_load_failed",
|
|
23607
|
+
message: err.message
|
|
23608
|
+
});
|
|
23609
|
+
}
|
|
23610
|
+
return true;
|
|
23611
|
+
}
|
|
23612
|
+
const initMatch = /^\/api\/templates\/([^/]+)\/init$/.exec(path);
|
|
23613
|
+
if (method === "POST" && initMatch) {
|
|
23614
|
+
const name = decodeURIComponent(initMatch[1]);
|
|
23615
|
+
try {
|
|
23616
|
+
const bundle = getTemplate2(name);
|
|
23617
|
+
if (!bundle) {
|
|
23618
|
+
writeJSON(res, 404, { error: "template_not_found", name });
|
|
23619
|
+
return true;
|
|
23620
|
+
}
|
|
23621
|
+
const body = await readJSONBody(req);
|
|
23622
|
+
if (!body.agent_name || typeof body.agent_name !== "string") {
|
|
23623
|
+
writeJSON(res, 400, {
|
|
23624
|
+
error: "validation_error",
|
|
23625
|
+
message: "agent_name is required and must be a string"
|
|
23626
|
+
});
|
|
23627
|
+
return true;
|
|
23628
|
+
}
|
|
23629
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(body.agent_name)) {
|
|
23630
|
+
writeJSON(res, 400, {
|
|
23631
|
+
error: "validation_error",
|
|
23632
|
+
message: "agent_name must contain only alphanumeric characters, hyphens, and underscores"
|
|
23633
|
+
});
|
|
23634
|
+
return true;
|
|
23635
|
+
}
|
|
23636
|
+
const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
|
|
23637
|
+
const tenant = await findTenant(agentId);
|
|
23638
|
+
if (!tenant) return false;
|
|
23639
|
+
return tenant.initialized || tenant.has_cocoon_profile;
|
|
23640
|
+
});
|
|
23641
|
+
if (!await isAgentWrapped(body.agent_name)) {
|
|
23642
|
+
writeJSON(res, 400, {
|
|
23643
|
+
error: "orphan_agent_id",
|
|
23644
|
+
message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
|
|
23645
|
+
});
|
|
23646
|
+
return true;
|
|
23647
|
+
}
|
|
23648
|
+
const nodeId = deps.nodeId ?? "dashboard-node";
|
|
23649
|
+
const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
|
|
23650
|
+
const principalId = deps.principalId ?? "dashboard-principal";
|
|
23651
|
+
const fortressId = deps.fortressId ?? "default";
|
|
23652
|
+
const result = initTemplate({
|
|
23653
|
+
template_name: name,
|
|
23654
|
+
agent_id: body.agent_name,
|
|
23655
|
+
fortress_id: fortressId,
|
|
23656
|
+
counterparty: "*",
|
|
23657
|
+
policy_version: 1,
|
|
23658
|
+
emitter_node: nodeId,
|
|
23659
|
+
emitter_principal: principalId,
|
|
23660
|
+
monotonic_seq: 1,
|
|
23661
|
+
node_private_key: nodePrivateKey
|
|
23662
|
+
});
|
|
23663
|
+
writeJSON(res, 200, {
|
|
23664
|
+
agent_id: body.agent_name,
|
|
23665
|
+
signed_event_id: result.signed_event.event_id,
|
|
23666
|
+
policy_version: result.compiled.policy_version,
|
|
23667
|
+
template_name: name,
|
|
23668
|
+
attestation_panel_url: `/console#agent_roster`
|
|
23669
|
+
});
|
|
23670
|
+
} catch (err) {
|
|
23671
|
+
writeJSON(res, 500, {
|
|
23672
|
+
error: "template_init_failed",
|
|
23673
|
+
message: err.message
|
|
23674
|
+
});
|
|
23675
|
+
}
|
|
23676
|
+
return true;
|
|
23677
|
+
}
|
|
22302
23678
|
return false;
|
|
22303
23679
|
}
|
|
22304
23680
|
async function handleStream(deps, res) {
|
|
@@ -22377,11 +23753,11 @@ async function startDashboardServer(options) {
|
|
|
22377
23753
|
}
|
|
22378
23754
|
}
|
|
22379
23755
|
});
|
|
22380
|
-
await new Promise((
|
|
23756
|
+
await new Promise((resolve2, reject) => {
|
|
22381
23757
|
server.once("error", reject);
|
|
22382
23758
|
server.listen(port, host, () => {
|
|
22383
23759
|
server.off("error", reject);
|
|
22384
|
-
|
|
23760
|
+
resolve2();
|
|
22385
23761
|
});
|
|
22386
23762
|
});
|
|
22387
23763
|
const actualPort = (() => {
|
|
@@ -22394,8 +23770,8 @@ async function startDashboardServer(options) {
|
|
|
22394
23770
|
url,
|
|
22395
23771
|
port: actualPort,
|
|
22396
23772
|
host,
|
|
22397
|
-
stop: () => new Promise((
|
|
22398
|
-
server.close((err) => err ? reject(err) :
|
|
23773
|
+
stop: () => new Promise((resolve2, reject) => {
|
|
23774
|
+
server.close((err) => err ? reject(err) : resolve2());
|
|
22399
23775
|
}),
|
|
22400
23776
|
publish,
|
|
22401
23777
|
publishActivity: (entry) => publish({ type: "activity", data: entry }),
|
|
@@ -22996,7 +24372,7 @@ async function createSanctuaryServer(options) {
|
|
|
22996
24372
|
clientManager.configure(enabledServers).catch((err) => {
|
|
22997
24373
|
console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
|
|
22998
24374
|
});
|
|
22999
|
-
await new Promise((
|
|
24375
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
23000
24376
|
const proxiedTools = proxyRouter.getProxiedTools();
|
|
23001
24377
|
if (proxiedTools.length > 0) {
|
|
23002
24378
|
allTools.push(...proxiedTools);
|