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