@sanctuary-framework/mcp-server 1.2.9 → 1.2.11
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 +1242 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1242 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +822 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +243 -81
- package/dist/index.d.ts +243 -81
- package/dist/index.js +822 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -17480,6 +17480,318 @@ var init_sentinel_routes = __esm({
|
|
|
17480
17480
|
FINDINGS_MAX_LIMIT = 500;
|
|
17481
17481
|
}
|
|
17482
17482
|
});
|
|
17483
|
+
function makeEntryId(auditEventId) {
|
|
17484
|
+
return crypto.createHash("sha256").update(auditEventId).digest("hex").slice(0, 32);
|
|
17485
|
+
}
|
|
17486
|
+
function optString(details, key) {
|
|
17487
|
+
if (!details) return null;
|
|
17488
|
+
const value = details[key];
|
|
17489
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
17490
|
+
return value;
|
|
17491
|
+
}
|
|
17492
|
+
function auditEventIdFallback(audit) {
|
|
17493
|
+
return `${audit.timestamp}:${audit.operation}`;
|
|
17494
|
+
}
|
|
17495
|
+
function localHandoffSummary(details, sender, recipient) {
|
|
17496
|
+
const taskScope = optString(details, "task_scope");
|
|
17497
|
+
const reasonClass = optString(details, "reason_class");
|
|
17498
|
+
if (taskScope) {
|
|
17499
|
+
return `${sender} -> ${recipient} handoff: ${taskScope}`;
|
|
17500
|
+
}
|
|
17501
|
+
if (reasonClass) {
|
|
17502
|
+
return `${sender} -> ${recipient} handoff (${reasonClass})`;
|
|
17503
|
+
}
|
|
17504
|
+
return `${sender} -> ${recipient} handoff`;
|
|
17505
|
+
}
|
|
17506
|
+
function crossHarnessSummary(details, sender) {
|
|
17507
|
+
const policyRule = optString(details, "policy_rule_id");
|
|
17508
|
+
if (policyRule) {
|
|
17509
|
+
return `${sender} -> operator approval (${policyRule})`;
|
|
17510
|
+
}
|
|
17511
|
+
return `${sender} -> operator approval`;
|
|
17512
|
+
}
|
|
17513
|
+
var HANDOFF_LOG_OBSERVED_OPS, OBSERVED_OP_LIST, OPERATOR_PSEUDO_AGENT, DEFAULT_LIMIT, MAX_LIMIT, AUDIT_QUERY_LIMIT, HandoffLog, COORDINATION_VIEW_AUDIT_OPS;
|
|
17514
|
+
var init_handoff_log = __esm({
|
|
17515
|
+
"src/coordination/handoff-log.ts"() {
|
|
17516
|
+
HANDOFF_LOG_OBSERVED_OPS = {
|
|
17517
|
+
/** Tau-3 in-process coordination handoff. */
|
|
17518
|
+
LOCAL_HANDOFF: "v1.1_local_handoff",
|
|
17519
|
+
/** Upsilon-1 cross-harness approval (wrapped-agent -> operator). */
|
|
17520
|
+
CROSS_HARNESS_APPROVAL: "cross_harness_approval_aggregated"
|
|
17521
|
+
};
|
|
17522
|
+
OBSERVED_OP_LIST = [
|
|
17523
|
+
HANDOFF_LOG_OBSERVED_OPS.LOCAL_HANDOFF,
|
|
17524
|
+
HANDOFF_LOG_OBSERVED_OPS.CROSS_HARNESS_APPROVAL
|
|
17525
|
+
];
|
|
17526
|
+
OPERATOR_PSEUDO_AGENT = "operator";
|
|
17527
|
+
DEFAULT_LIMIT = 50;
|
|
17528
|
+
MAX_LIMIT = 500;
|
|
17529
|
+
AUDIT_QUERY_LIMIT = 1e4;
|
|
17530
|
+
HandoffLog = class {
|
|
17531
|
+
auditLog;
|
|
17532
|
+
fortressId;
|
|
17533
|
+
constructor(opts) {
|
|
17534
|
+
this.auditLog = opts.auditLog;
|
|
17535
|
+
this.fortressId = opts.fortressId;
|
|
17536
|
+
}
|
|
17537
|
+
/** Stable fortress id this HandoffLog reads. */
|
|
17538
|
+
getFortressId() {
|
|
17539
|
+
return this.fortressId;
|
|
17540
|
+
}
|
|
17541
|
+
/**
|
|
17542
|
+
* Query handoffs in chronological-newest-first order. Filters
|
|
17543
|
+
* applied after normalization so the per-event-class shape
|
|
17544
|
+
* differences (sender field name, recipient inference) are handled
|
|
17545
|
+
* once.
|
|
17546
|
+
*/
|
|
17547
|
+
async query(opts) {
|
|
17548
|
+
const limit = Math.min(opts.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
|
|
17549
|
+
const queryResult = await this.auditLog.query({
|
|
17550
|
+
...opts.since !== void 0 ? { since: opts.since } : {},
|
|
17551
|
+
layer: "l2",
|
|
17552
|
+
limit: AUDIT_QUERY_LIMIT
|
|
17553
|
+
});
|
|
17554
|
+
const normalized = [];
|
|
17555
|
+
for (const entry of queryResult.entries) {
|
|
17556
|
+
if (!OBSERVED_OP_LIST.includes(entry.operation)) continue;
|
|
17557
|
+
const handoff = this.normalize(entry);
|
|
17558
|
+
if (!handoff) continue;
|
|
17559
|
+
if (opts.until && handoff.observed_at > opts.until) continue;
|
|
17560
|
+
if (opts.since && handoff.observed_at < opts.since) continue;
|
|
17561
|
+
if (opts.agent_id) {
|
|
17562
|
+
if (handoff.source_agent_id !== opts.agent_id && handoff.target_agent_id !== opts.agent_id) {
|
|
17563
|
+
continue;
|
|
17564
|
+
}
|
|
17565
|
+
}
|
|
17566
|
+
normalized.push(handoff);
|
|
17567
|
+
}
|
|
17568
|
+
normalized.sort((a, b) => a.observed_at < b.observed_at ? 1 : -1);
|
|
17569
|
+
return normalized.slice(0, limit);
|
|
17570
|
+
}
|
|
17571
|
+
/**
|
|
17572
|
+
* Look up a single entry by id. Returns the normalized entry +
|
|
17573
|
+
* the source audit payload for operator-facing detail rendering.
|
|
17574
|
+
* Returns null when no audit entry maps to the given id.
|
|
17575
|
+
*/
|
|
17576
|
+
async getEntry(entryId) {
|
|
17577
|
+
const queryResult = await this.auditLog.query({
|
|
17578
|
+
layer: "l2",
|
|
17579
|
+
limit: AUDIT_QUERY_LIMIT
|
|
17580
|
+
});
|
|
17581
|
+
for (const audit of queryResult.entries) {
|
|
17582
|
+
if (!OBSERVED_OP_LIST.includes(audit.operation)) continue;
|
|
17583
|
+
const handoff = this.normalize(audit);
|
|
17584
|
+
if (!handoff) continue;
|
|
17585
|
+
if (handoff.entry_id === entryId) {
|
|
17586
|
+
return { entry: handoff, source_audit_entry: audit };
|
|
17587
|
+
}
|
|
17588
|
+
}
|
|
17589
|
+
return null;
|
|
17590
|
+
}
|
|
17591
|
+
normalize(audit) {
|
|
17592
|
+
const details = audit.details;
|
|
17593
|
+
if (audit.operation === HANDOFF_LOG_OBSERVED_OPS.LOCAL_HANDOFF) {
|
|
17594
|
+
const sender = optString(details, "sender_agent_id");
|
|
17595
|
+
const recipient = optString(details, "recipient_agent_id");
|
|
17596
|
+
if (!sender || !recipient || sender === recipient) return null;
|
|
17597
|
+
const auditEventId = optString(details, "event_id") ?? auditEventIdFallback(audit);
|
|
17598
|
+
return {
|
|
17599
|
+
entry_id: makeEntryId(auditEventId),
|
|
17600
|
+
audit_event_id: auditEventId,
|
|
17601
|
+
source_agent_id: sender,
|
|
17602
|
+
target_agent_id: recipient,
|
|
17603
|
+
observed_at: audit.timestamp,
|
|
17604
|
+
event_class: audit.operation,
|
|
17605
|
+
context_transfer_summary: localHandoffSummary(details, sender, recipient),
|
|
17606
|
+
workflow_link: null
|
|
17607
|
+
};
|
|
17608
|
+
}
|
|
17609
|
+
if (audit.operation === HANDOFF_LOG_OBSERVED_OPS.CROSS_HARNESS_APPROVAL) {
|
|
17610
|
+
const sender = optString(details, "source_harness") ?? optString(details, "source_agent_id");
|
|
17611
|
+
if (!sender) return null;
|
|
17612
|
+
const auditEventId = optString(details, "aggregator_id") ?? auditEventIdFallback(audit);
|
|
17613
|
+
return {
|
|
17614
|
+
entry_id: makeEntryId(auditEventId),
|
|
17615
|
+
audit_event_id: auditEventId,
|
|
17616
|
+
source_agent_id: sender,
|
|
17617
|
+
target_agent_id: OPERATOR_PSEUDO_AGENT,
|
|
17618
|
+
observed_at: audit.timestamp,
|
|
17619
|
+
event_class: audit.operation,
|
|
17620
|
+
context_transfer_summary: crossHarnessSummary(details, sender),
|
|
17621
|
+
workflow_link: null
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
return null;
|
|
17625
|
+
}
|
|
17626
|
+
};
|
|
17627
|
+
COORDINATION_VIEW_AUDIT_OPS = {
|
|
17628
|
+
VIEW_OPENED: "operator_coordination_view_opened",
|
|
17629
|
+
ENTRY_DRILLED: "operator_handoff_entry_drilled"
|
|
17630
|
+
};
|
|
17631
|
+
}
|
|
17632
|
+
});
|
|
17633
|
+
|
|
17634
|
+
// src/coordination/handoff-routes.ts
|
|
17635
|
+
function writeJSON6(res, status, payload) {
|
|
17636
|
+
res.writeHead(status, {
|
|
17637
|
+
"Content-Type": "application/json",
|
|
17638
|
+
"Cache-Control": "no-store"
|
|
17639
|
+
});
|
|
17640
|
+
res.end(JSON.stringify(payload));
|
|
17641
|
+
}
|
|
17642
|
+
function parseLimit4(raw, defaultValue, max) {
|
|
17643
|
+
if (raw === null || raw === "") return defaultValue;
|
|
17644
|
+
const parsed = Number.parseInt(raw, 10);
|
|
17645
|
+
if (Number.isNaN(parsed) || parsed < 0) return defaultValue;
|
|
17646
|
+
return Math.min(parsed, max);
|
|
17647
|
+
}
|
|
17648
|
+
function matchEntryRoute2(path) {
|
|
17649
|
+
const prefix = `${COORDINATION_HANDOFFS_PREFIX}/`;
|
|
17650
|
+
if (!path.startsWith(prefix)) return null;
|
|
17651
|
+
const rest = path.slice(prefix.length);
|
|
17652
|
+
if (rest.length === 0 || rest === "stream") return null;
|
|
17653
|
+
if (rest.includes("/")) return null;
|
|
17654
|
+
return { entryId: decodeURIComponent(rest) };
|
|
17655
|
+
}
|
|
17656
|
+
async function handleStream3(deps, res) {
|
|
17657
|
+
res.writeHead(200, {
|
|
17658
|
+
"Content-Type": "text/event-stream",
|
|
17659
|
+
"Cache-Control": "no-cache, no-transform",
|
|
17660
|
+
Connection: "keep-alive",
|
|
17661
|
+
"X-Accel-Buffering": "no"
|
|
17662
|
+
});
|
|
17663
|
+
const snapshot = await deps.handoffLog.query({ limit: 50 });
|
|
17664
|
+
res.write(
|
|
17665
|
+
`event: handoff_snapshot
|
|
17666
|
+
data: ${JSON.stringify({ entries: snapshot })}
|
|
17667
|
+
|
|
17668
|
+
`
|
|
17669
|
+
);
|
|
17670
|
+
const unsubscribe = deps.events.subscribe((entry) => {
|
|
17671
|
+
try {
|
|
17672
|
+
res.write(
|
|
17673
|
+
`event: handoff_added
|
|
17674
|
+
data: ${JSON.stringify(entry)}
|
|
17675
|
+
|
|
17676
|
+
`
|
|
17677
|
+
);
|
|
17678
|
+
} catch {
|
|
17679
|
+
}
|
|
17680
|
+
});
|
|
17681
|
+
const keepAlive = setInterval(() => {
|
|
17682
|
+
try {
|
|
17683
|
+
res.write(": keepalive\n\n");
|
|
17684
|
+
} catch {
|
|
17685
|
+
}
|
|
17686
|
+
}, 25e3);
|
|
17687
|
+
const cleanup = () => {
|
|
17688
|
+
clearInterval(keepAlive);
|
|
17689
|
+
unsubscribe();
|
|
17690
|
+
};
|
|
17691
|
+
res.on("close", cleanup);
|
|
17692
|
+
res.on("error", cleanup);
|
|
17693
|
+
}
|
|
17694
|
+
async function handleCoordinationRoute(deps, req, res) {
|
|
17695
|
+
const host = req.headers.host || "localhost";
|
|
17696
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
17697
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
17698
|
+
const path = url.pathname;
|
|
17699
|
+
if (path !== COORDINATION_API_PREFIX && !path.startsWith(`${COORDINATION_API_PREFIX}/`)) {
|
|
17700
|
+
return false;
|
|
17701
|
+
}
|
|
17702
|
+
const checkAuth = authMiddleware(deps.authConfig);
|
|
17703
|
+
if (!checkAuth(req, res, url)) return true;
|
|
17704
|
+
try {
|
|
17705
|
+
if (method === "GET" && path === `${COORDINATION_HANDOFFS_PREFIX}/stream`) {
|
|
17706
|
+
await handleStream3(deps, res);
|
|
17707
|
+
return true;
|
|
17708
|
+
}
|
|
17709
|
+
if (method === "GET" && path === COORDINATION_HANDOFFS_PREFIX) {
|
|
17710
|
+
const limit = parseLimit4(
|
|
17711
|
+
url.searchParams.get("limit"),
|
|
17712
|
+
COORDINATION_LIST_DEFAULT_LIMIT,
|
|
17713
|
+
COORDINATION_LIST_MAX_LIMIT
|
|
17714
|
+
);
|
|
17715
|
+
const since = url.searchParams.get("since") ?? void 0;
|
|
17716
|
+
const until = url.searchParams.get("until") ?? void 0;
|
|
17717
|
+
const agentId = url.searchParams.get("agent_id") ?? void 0;
|
|
17718
|
+
const entries = await deps.handoffLog.query({
|
|
17719
|
+
limit,
|
|
17720
|
+
...since !== void 0 ? { since } : {},
|
|
17721
|
+
...until !== void 0 ? { until } : {},
|
|
17722
|
+
...agentId !== void 0 ? { agent_id: agentId } : {}
|
|
17723
|
+
});
|
|
17724
|
+
deps.auditLog.append(
|
|
17725
|
+
"l2",
|
|
17726
|
+
COORDINATION_VIEW_AUDIT_OPS.VIEW_OPENED,
|
|
17727
|
+
deps.operatorId,
|
|
17728
|
+
{
|
|
17729
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
17730
|
+
result_count: entries.length,
|
|
17731
|
+
...since !== void 0 ? { since } : {},
|
|
17732
|
+
...until !== void 0 ? { until } : {},
|
|
17733
|
+
...agentId !== void 0 ? { agent_id: agentId } : {}
|
|
17734
|
+
}
|
|
17735
|
+
);
|
|
17736
|
+
writeJSON6(res, 200, { ok: true, data: { entries } });
|
|
17737
|
+
return true;
|
|
17738
|
+
}
|
|
17739
|
+
const entryMatch = matchEntryRoute2(path);
|
|
17740
|
+
if (method === "GET" && entryMatch) {
|
|
17741
|
+
const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
|
|
17742
|
+
if (!detail) {
|
|
17743
|
+
writeJSON6(res, 404, { ok: false, error: "not_found" });
|
|
17744
|
+
return true;
|
|
17745
|
+
}
|
|
17746
|
+
deps.auditLog.append(
|
|
17747
|
+
"l2",
|
|
17748
|
+
COORDINATION_VIEW_AUDIT_OPS.ENTRY_DRILLED,
|
|
17749
|
+
deps.operatorId,
|
|
17750
|
+
{
|
|
17751
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
17752
|
+
entry_id: detail.entry.entry_id,
|
|
17753
|
+
event_class: detail.entry.event_class,
|
|
17754
|
+
source_agent_id: detail.entry.source_agent_id,
|
|
17755
|
+
target_agent_id: detail.entry.target_agent_id
|
|
17756
|
+
}
|
|
17757
|
+
);
|
|
17758
|
+
writeJSON6(res, 200, { ok: true, data: detail });
|
|
17759
|
+
return true;
|
|
17760
|
+
}
|
|
17761
|
+
writeJSON6(res, 404, { ok: false, error: "not_found", path });
|
|
17762
|
+
return true;
|
|
17763
|
+
} catch (err) {
|
|
17764
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17765
|
+
writeJSON6(res, 500, { ok: false, error: "internal", detail: msg });
|
|
17766
|
+
return true;
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
|
|
17770
|
+
var init_handoff_routes = __esm({
|
|
17771
|
+
"src/coordination/handoff-routes.ts"() {
|
|
17772
|
+
init_auth_middleware();
|
|
17773
|
+
init_handoff_log();
|
|
17774
|
+
COORDINATION_API_PREFIX = "/api/coordination";
|
|
17775
|
+
COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
|
|
17776
|
+
COORDINATION_LIST_DEFAULT_LIMIT = 50;
|
|
17777
|
+
COORDINATION_LIST_MAX_LIMIT = 500;
|
|
17778
|
+
HandoffEventBridge = class {
|
|
17779
|
+
listeners = /* @__PURE__ */ new Set();
|
|
17780
|
+
subscribe(listener) {
|
|
17781
|
+
this.listeners.add(listener);
|
|
17782
|
+
return () => this.listeners.delete(listener);
|
|
17783
|
+
}
|
|
17784
|
+
emit(entry) {
|
|
17785
|
+
for (const listener of this.listeners) {
|
|
17786
|
+
try {
|
|
17787
|
+
listener(entry);
|
|
17788
|
+
} catch {
|
|
17789
|
+
}
|
|
17790
|
+
}
|
|
17791
|
+
}
|
|
17792
|
+
};
|
|
17793
|
+
}
|
|
17794
|
+
});
|
|
17483
17795
|
function isDashboardViewRoute(method, path) {
|
|
17484
17796
|
if (method !== "GET") return false;
|
|
17485
17797
|
return path === "/" || path === "/dashboard" || path === "/v1.0" || path === "/fortress" || path === "/events";
|
|
@@ -17495,6 +17807,7 @@ var init_dashboard = __esm({
|
|
|
17495
17807
|
init_dispatch();
|
|
17496
17808
|
init_approval_aggregator_routes();
|
|
17497
17809
|
init_sentinel_routes();
|
|
17810
|
+
init_handoff_routes();
|
|
17498
17811
|
SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
|
|
17499
17812
|
SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
|
|
17500
17813
|
MAX_SESSIONS = 1e3;
|
|
@@ -17569,6 +17882,17 @@ var init_dashboard = __esm({
|
|
|
17569
17882
|
* dispatcher's audited paths.
|
|
17570
17883
|
*/
|
|
17571
17884
|
sentinelDispatcher = null;
|
|
17885
|
+
/**
|
|
17886
|
+
* v1.3 WP-V1.3-3 Omega-1 Coordination Handoff Visualization.
|
|
17887
|
+
* Mounted additively at `/api/coordination/*` when set. Read-only
|
|
17888
|
+
* against the audit log; the only writes are operator-action audit
|
|
17889
|
+
* events (operator_coordination_view_opened,
|
|
17890
|
+
* operator_handoff_entry_drilled).
|
|
17891
|
+
*/
|
|
17892
|
+
handoffLog = null;
|
|
17893
|
+
handoffEventBridge = null;
|
|
17894
|
+
handoffAuditLog = null;
|
|
17895
|
+
handoffOperatorId = null;
|
|
17572
17896
|
constructor(config) {
|
|
17573
17897
|
this.config = config;
|
|
17574
17898
|
this.authToken = config.auth_token;
|
|
@@ -17636,6 +17960,18 @@ var init_dashboard = __esm({
|
|
|
17636
17960
|
setSentinelDispatcher(dispatcher) {
|
|
17637
17961
|
this.sentinelDispatcher = dispatcher;
|
|
17638
17962
|
}
|
|
17963
|
+
/**
|
|
17964
|
+
* v1.3 WP-V1.3-3 Omega-1: bind the Coordination handoff log +
|
|
17965
|
+
* event bridge + audit log + operator id. Once set, requests to
|
|
17966
|
+
* `/api/coordination/*` route through `handleCoordinationRoute`.
|
|
17967
|
+
* Pass `null` for any field to detach.
|
|
17968
|
+
*/
|
|
17969
|
+
setHandoffLog(opts) {
|
|
17970
|
+
this.handoffLog = opts.handoffLog;
|
|
17971
|
+
this.handoffEventBridge = opts.eventBridge ?? null;
|
|
17972
|
+
this.handoffAuditLog = opts.auditLog ?? null;
|
|
17973
|
+
this.handoffOperatorId = opts.operatorId ?? null;
|
|
17974
|
+
}
|
|
17639
17975
|
/**
|
|
17640
17976
|
* v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
|
|
17641
17977
|
* before the legacy approval route table. Returns true when served.
|
|
@@ -17674,6 +18010,30 @@ var init_dashboard = __esm({
|
|
|
17674
18010
|
res
|
|
17675
18011
|
);
|
|
17676
18012
|
}
|
|
18013
|
+
/**
|
|
18014
|
+
* v1.3 WP-V1.3-3 Omega-1 dispatch entry point. Routes
|
|
18015
|
+
* `/api/coordination/*` requests through the coordination router
|
|
18016
|
+
* when a HandoffLog has been bound. Returns true when served.
|
|
18017
|
+
*/
|
|
18018
|
+
async dispatchCoordination(req, res) {
|
|
18019
|
+
if (!this.handoffLog || !this.handoffEventBridge || !this.handoffAuditLog) {
|
|
18020
|
+
return false;
|
|
18021
|
+
}
|
|
18022
|
+
return handleCoordinationRoute(
|
|
18023
|
+
{
|
|
18024
|
+
authConfig: {
|
|
18025
|
+
loopbackAutoAuth: this._autoAuthLocalhost,
|
|
18026
|
+
...this.authToken !== void 0 ? { authToken: this.authToken } : {}
|
|
18027
|
+
},
|
|
18028
|
+
handoffLog: this.handoffLog,
|
|
18029
|
+
auditLog: this.handoffAuditLog,
|
|
18030
|
+
operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
|
|
18031
|
+
events: this.handoffEventBridge
|
|
18032
|
+
},
|
|
18033
|
+
req,
|
|
18034
|
+
res
|
|
18035
|
+
);
|
|
18036
|
+
}
|
|
17677
18037
|
/**
|
|
17678
18038
|
* v1.1 dispatch entry point. Called from `handleRequest` before the
|
|
17679
18039
|
* legacy route table. Returns true when the request was served by v1.1
|
|
@@ -18073,6 +18433,18 @@ var init_dashboard = __esm({
|
|
|
18073
18433
|
});
|
|
18074
18434
|
return;
|
|
18075
18435
|
}
|
|
18436
|
+
if (this.handoffLog && url.pathname.startsWith(COORDINATION_API_PREFIX)) {
|
|
18437
|
+
this.dispatchCoordination(req, res).then((handled) => {
|
|
18438
|
+
if (handled) return;
|
|
18439
|
+
this.handleLegacyRequest(req, res, url, method);
|
|
18440
|
+
}).catch(() => {
|
|
18441
|
+
if (!res.headersSent) {
|
|
18442
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
18443
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
18444
|
+
}
|
|
18445
|
+
});
|
|
18446
|
+
return;
|
|
18447
|
+
}
|
|
18076
18448
|
if (this.v11Bindings) {
|
|
18077
18449
|
this.dispatchV11(req, res, url, method).then((handled) => {
|
|
18078
18450
|
if (handled) return;
|
|
@@ -21794,6 +22166,11 @@ var init_sentinel_dispatcher = __esm({
|
|
|
21794
22166
|
fortressId: this.fortressId,
|
|
21795
22167
|
auditLog: this.auditLog,
|
|
21796
22168
|
now: this.now,
|
|
22169
|
+
// Phi-5 meta-sentinel reads the per-fortress finding store to
|
|
22170
|
+
// detect patterns across other sentinels' findings. First-order
|
|
22171
|
+
// sentinels ignore the field; the dispatcher always attaches it
|
|
22172
|
+
// because the store is already in scope here.
|
|
22173
|
+
findingStore: this.findingStore,
|
|
21797
22174
|
...contextOverrides ?? {}
|
|
21798
22175
|
};
|
|
21799
22176
|
const sentinel = await this.registry.subscribe(sentinelId, context);
|
|
@@ -21895,28 +22272,252 @@ var init_sentinel_dispatcher = __esm({
|
|
|
21895
22272
|
*/
|
|
21896
22273
|
async dispose() {
|
|
21897
22274
|
this.stop();
|
|
21898
|
-
await this.registry.unsubscribeAll();
|
|
22275
|
+
await this.registry.unsubscribeAll();
|
|
22276
|
+
this.listeners.clear();
|
|
22277
|
+
}
|
|
22278
|
+
async routeFinding(sentinelId, raw) {
|
|
22279
|
+
const stamped = {
|
|
22280
|
+
...raw,
|
|
22281
|
+
finding_id: raw.finding_id || crypto.randomUUID(),
|
|
22282
|
+
sentinel_id: sentinelId,
|
|
22283
|
+
fortress_id: this.fortressId,
|
|
22284
|
+
observed_at: raw.observed_at || this.now().toISOString()
|
|
22285
|
+
};
|
|
22286
|
+
await this.findingStore.saveFinding(stamped);
|
|
22287
|
+
this.auditLog.append(
|
|
22288
|
+
"l2",
|
|
22289
|
+
SENTINEL_AUDIT_OPS.FINDING_EMITTED,
|
|
22290
|
+
this.identityId,
|
|
22291
|
+
{
|
|
22292
|
+
sentinel_id: sentinelId,
|
|
22293
|
+
finding_id: stamped.finding_id,
|
|
22294
|
+
severity: stamped.severity,
|
|
22295
|
+
...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
|
|
22296
|
+
evidence_audit_ids: stamped.evidence_audit_ids,
|
|
22297
|
+
fortress_id: this.fortressId
|
|
22298
|
+
}
|
|
22299
|
+
);
|
|
22300
|
+
this.emit({ type: "finding", finding: stamped });
|
|
22301
|
+
return stamped;
|
|
22302
|
+
}
|
|
22303
|
+
emit(event) {
|
|
22304
|
+
for (const listener of this.listeners) {
|
|
22305
|
+
try {
|
|
22306
|
+
listener(event);
|
|
22307
|
+
} catch {
|
|
22308
|
+
}
|
|
22309
|
+
}
|
|
22310
|
+
}
|
|
22311
|
+
};
|
|
22312
|
+
}
|
|
22313
|
+
});
|
|
22314
|
+
|
|
22315
|
+
// src/anomaly-detection/types.ts
|
|
22316
|
+
var init_types4 = __esm({
|
|
22317
|
+
"src/anomaly-detection/types.ts"() {
|
|
22318
|
+
}
|
|
22319
|
+
});
|
|
22320
|
+
var ANOMALY_AUDIT_OPS, DEFAULT_TICK_INTERVAL_MS2, AnomalyPipelineDispatcher;
|
|
22321
|
+
var init_anomaly_pipeline = __esm({
|
|
22322
|
+
"src/anomaly-detection/anomaly-pipeline.ts"() {
|
|
22323
|
+
init_types4();
|
|
22324
|
+
ANOMALY_AUDIT_OPS = {
|
|
22325
|
+
DETECTOR_REGISTERED: "anomaly_detector_registered",
|
|
22326
|
+
DETECTOR_UNREGISTERED: "anomaly_detector_unregistered",
|
|
22327
|
+
FINDING_EMITTED: "anomaly_finding_emitted",
|
|
22328
|
+
EVALUATION_FAILED: "anomaly_evaluation_failed",
|
|
22329
|
+
TRAINING_COMPLETED: "anomaly_training_completed",
|
|
22330
|
+
TRAINING_FAILED: "anomaly_training_failed"
|
|
22331
|
+
};
|
|
22332
|
+
DEFAULT_TICK_INTERVAL_MS2 = 6e4;
|
|
22333
|
+
AnomalyPipelineDispatcher = class {
|
|
22334
|
+
findingStore;
|
|
22335
|
+
auditLog;
|
|
22336
|
+
storage;
|
|
22337
|
+
masterKey;
|
|
22338
|
+
fortressId;
|
|
22339
|
+
identityId;
|
|
22340
|
+
now;
|
|
22341
|
+
tickIntervalMs;
|
|
22342
|
+
detectors = /* @__PURE__ */ new Map();
|
|
22343
|
+
listeners = /* @__PURE__ */ new Set();
|
|
22344
|
+
tickTimer = null;
|
|
22345
|
+
tickInFlight = false;
|
|
22346
|
+
constructor(deps) {
|
|
22347
|
+
this.findingStore = deps.findingStore;
|
|
22348
|
+
this.auditLog = deps.auditLog;
|
|
22349
|
+
this.storage = deps.storage;
|
|
22350
|
+
this.masterKey = deps.masterKey;
|
|
22351
|
+
this.fortressId = deps.fortressId;
|
|
22352
|
+
this.identityId = deps.identityId;
|
|
22353
|
+
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
22354
|
+
this.tickIntervalMs = deps.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS2;
|
|
22355
|
+
}
|
|
22356
|
+
onEvent(listener) {
|
|
22357
|
+
this.listeners.add(listener);
|
|
22358
|
+
return () => this.listeners.delete(listener);
|
|
22359
|
+
}
|
|
22360
|
+
/**
|
|
22361
|
+
* Register + subscribe a detector to this fortress. Idempotent: a
|
|
22362
|
+
* second call with the same detectorId returns the already-
|
|
22363
|
+
* registered instance without re-subscribing.
|
|
22364
|
+
*/
|
|
22365
|
+
async registerDetector(detector) {
|
|
22366
|
+
const existing = this.detectors.get(detector.detectorId);
|
|
22367
|
+
if (existing) return existing;
|
|
22368
|
+
const context = {
|
|
22369
|
+
fortressId: this.fortressId,
|
|
22370
|
+
auditLog: this.auditLog,
|
|
22371
|
+
storage: this.storage,
|
|
22372
|
+
masterKey: this.masterKey,
|
|
22373
|
+
now: this.now
|
|
22374
|
+
};
|
|
22375
|
+
await detector.subscribe(context);
|
|
22376
|
+
this.detectors.set(detector.detectorId, detector);
|
|
22377
|
+
this.auditLog.append(
|
|
22378
|
+
"l2",
|
|
22379
|
+
ANOMALY_AUDIT_OPS.DETECTOR_REGISTERED,
|
|
22380
|
+
this.identityId,
|
|
22381
|
+
{ detector_id: detector.detectorId, fortress_id: this.fortressId }
|
|
22382
|
+
);
|
|
22383
|
+
return detector;
|
|
22384
|
+
}
|
|
22385
|
+
/**
|
|
22386
|
+
* Unregister + tear down a detector. Idempotent. Returns true when
|
|
22387
|
+
* an active registration was removed.
|
|
22388
|
+
*/
|
|
22389
|
+
async unregisterDetector(detectorId) {
|
|
22390
|
+
const detector = this.detectors.get(detectorId);
|
|
22391
|
+
if (!detector) return false;
|
|
22392
|
+
try {
|
|
22393
|
+
await detector.unsubscribe();
|
|
22394
|
+
} finally {
|
|
22395
|
+
this.detectors.delete(detectorId);
|
|
22396
|
+
}
|
|
22397
|
+
this.auditLog.append(
|
|
22398
|
+
"l2",
|
|
22399
|
+
ANOMALY_AUDIT_OPS.DETECTOR_UNREGISTERED,
|
|
22400
|
+
this.identityId,
|
|
22401
|
+
{ detector_id: detectorId, fortress_id: this.fortressId }
|
|
22402
|
+
);
|
|
22403
|
+
return true;
|
|
22404
|
+
}
|
|
22405
|
+
listDetectors() {
|
|
22406
|
+
return [...this.detectors.keys()];
|
|
22407
|
+
}
|
|
22408
|
+
/** Run one evaluation pass over every registered detector. */
|
|
22409
|
+
async tick() {
|
|
22410
|
+
if (this.tickInFlight) return [];
|
|
22411
|
+
this.tickInFlight = true;
|
|
22412
|
+
try {
|
|
22413
|
+
const findings = [];
|
|
22414
|
+
for (const [detectorId, detector] of this.detectors.entries()) {
|
|
22415
|
+
try {
|
|
22416
|
+
const detectorFindings = await detector.evaluate();
|
|
22417
|
+
for (const raw of detectorFindings) {
|
|
22418
|
+
const stamped = await this.routeFinding(detectorId, raw);
|
|
22419
|
+
findings.push(stamped);
|
|
22420
|
+
}
|
|
22421
|
+
try {
|
|
22422
|
+
const trainingResult = await detector.classifier.train();
|
|
22423
|
+
this.auditLog.append(
|
|
22424
|
+
"l2",
|
|
22425
|
+
ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
|
|
22426
|
+
this.identityId,
|
|
22427
|
+
{
|
|
22428
|
+
detector_id: detectorId,
|
|
22429
|
+
classifier_id: detector.classifier.classifierId,
|
|
22430
|
+
trained_at: trainingResult.trained_at,
|
|
22431
|
+
sample_count: trainingResult.sample_count,
|
|
22432
|
+
agent_count: trainingResult.agent_count,
|
|
22433
|
+
fortress_id: this.fortressId
|
|
22434
|
+
}
|
|
22435
|
+
);
|
|
22436
|
+
} catch (trainErr) {
|
|
22437
|
+
const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
|
|
22438
|
+
this.auditLog.append(
|
|
22439
|
+
"l2",
|
|
22440
|
+
ANOMALY_AUDIT_OPS.TRAINING_FAILED,
|
|
22441
|
+
this.identityId,
|
|
22442
|
+
{
|
|
22443
|
+
detector_id: detectorId,
|
|
22444
|
+
error_message: message,
|
|
22445
|
+
fortress_id: this.fortressId
|
|
22446
|
+
},
|
|
22447
|
+
"failure"
|
|
22448
|
+
);
|
|
22449
|
+
}
|
|
22450
|
+
} catch (err) {
|
|
22451
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22452
|
+
const observedAt = this.now().toISOString();
|
|
22453
|
+
this.auditLog.append(
|
|
22454
|
+
"l2",
|
|
22455
|
+
ANOMALY_AUDIT_OPS.EVALUATION_FAILED,
|
|
22456
|
+
this.identityId,
|
|
22457
|
+
{
|
|
22458
|
+
detector_id: detectorId,
|
|
22459
|
+
error_message: message,
|
|
22460
|
+
fortress_id: this.fortressId
|
|
22461
|
+
},
|
|
22462
|
+
"failure"
|
|
22463
|
+
);
|
|
22464
|
+
this.emit({
|
|
22465
|
+
type: "evaluation_failed",
|
|
22466
|
+
detector_id: detectorId,
|
|
22467
|
+
error_message: message,
|
|
22468
|
+
observed_at: observedAt
|
|
22469
|
+
});
|
|
22470
|
+
}
|
|
22471
|
+
}
|
|
22472
|
+
return findings;
|
|
22473
|
+
} finally {
|
|
22474
|
+
this.tickInFlight = false;
|
|
22475
|
+
}
|
|
22476
|
+
}
|
|
22477
|
+
start() {
|
|
22478
|
+
if (this.tickTimer !== null) return;
|
|
22479
|
+
if (this.tickIntervalMs <= 0) return;
|
|
22480
|
+
this.tickTimer = setInterval(() => {
|
|
22481
|
+
void this.tick();
|
|
22482
|
+
}, this.tickIntervalMs);
|
|
22483
|
+
if (typeof this.tickTimer.unref === "function") {
|
|
22484
|
+
this.tickTimer.unref();
|
|
22485
|
+
}
|
|
22486
|
+
}
|
|
22487
|
+
stop() {
|
|
22488
|
+
if (this.tickTimer === null) return;
|
|
22489
|
+
clearInterval(this.tickTimer);
|
|
22490
|
+
this.tickTimer = null;
|
|
22491
|
+
}
|
|
22492
|
+
async dispose() {
|
|
22493
|
+
this.stop();
|
|
22494
|
+
const ids = [...this.detectors.keys()];
|
|
22495
|
+
for (const id of ids) {
|
|
22496
|
+
try {
|
|
22497
|
+
await this.unregisterDetector(id);
|
|
22498
|
+
} catch {
|
|
22499
|
+
}
|
|
22500
|
+
}
|
|
21899
22501
|
this.listeners.clear();
|
|
21900
22502
|
}
|
|
21901
|
-
async routeFinding(
|
|
22503
|
+
async routeFinding(detectorId, raw) {
|
|
21902
22504
|
const stamped = {
|
|
21903
22505
|
...raw,
|
|
21904
22506
|
finding_id: raw.finding_id || crypto.randomUUID(),
|
|
21905
|
-
sentinel_id: sentinelId,
|
|
21906
22507
|
fortress_id: this.fortressId,
|
|
21907
22508
|
observed_at: raw.observed_at || this.now().toISOString()
|
|
21908
22509
|
};
|
|
21909
22510
|
await this.findingStore.saveFinding(stamped);
|
|
21910
22511
|
this.auditLog.append(
|
|
21911
22512
|
"l2",
|
|
21912
|
-
|
|
22513
|
+
ANOMALY_AUDIT_OPS.FINDING_EMITTED,
|
|
21913
22514
|
this.identityId,
|
|
21914
22515
|
{
|
|
21915
|
-
|
|
22516
|
+
detector_id: detectorId,
|
|
21916
22517
|
finding_id: stamped.finding_id,
|
|
21917
22518
|
severity: stamped.severity,
|
|
22519
|
+
anomaly_score: stamped.details["anomaly_score"] ?? null,
|
|
21918
22520
|
...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
|
|
21919
|
-
evidence_audit_ids: stamped.evidence_audit_ids,
|
|
21920
22521
|
fortress_id: this.fortressId
|
|
21921
22522
|
}
|
|
21922
22523
|
);
|
|
@@ -22199,7 +22800,7 @@ function extractInterAgentEvents(entries) {
|
|
|
22199
22800
|
if (!sender) continue;
|
|
22200
22801
|
out.push({
|
|
22201
22802
|
sender,
|
|
22202
|
-
recipient:
|
|
22803
|
+
recipient: OPERATOR_PSEUDO_AGENT2,
|
|
22203
22804
|
timestampMs: Date.parse(entry.timestamp),
|
|
22204
22805
|
auditId: `${entry.timestamp}:${entry.operation}`
|
|
22205
22806
|
});
|
|
@@ -22213,7 +22814,7 @@ function optionalString(details, key) {
|
|
|
22213
22814
|
if (typeof value !== "string" || value.length === 0) return null;
|
|
22214
22815
|
return value;
|
|
22215
22816
|
}
|
|
22216
|
-
var CROSS_AGENT_CHATTER_SENTINEL_ID, WARN_SIGMA2, ALERT_SIGMA2, BASELINE_WINDOWS2, QUERY_LIMIT2, MULTI_NEW_PARTNER_ALERT_THRESHOLD,
|
|
22817
|
+
var CROSS_AGENT_CHATTER_SENTINEL_ID, WARN_SIGMA2, ALERT_SIGMA2, BASELINE_WINDOWS2, QUERY_LIMIT2, MULTI_NEW_PARTNER_ALERT_THRESHOLD, OPERATOR_PSEUDO_AGENT2, HANDOFF_OP, CROSS_HARNESS_OPS, CrossAgentChatterWatcher;
|
|
22217
22818
|
var init_cross_agent_chatter_watcher = __esm({
|
|
22218
22819
|
"src/sentinel/sentinels/cross-agent-chatter-watcher.ts"() {
|
|
22219
22820
|
init_sentinel();
|
|
@@ -22223,7 +22824,7 @@ var init_cross_agent_chatter_watcher = __esm({
|
|
|
22223
22824
|
BASELINE_WINDOWS2 = 7;
|
|
22224
22825
|
QUERY_LIMIT2 = 1e4;
|
|
22225
22826
|
MULTI_NEW_PARTNER_ALERT_THRESHOLD = 3;
|
|
22226
|
-
|
|
22827
|
+
OPERATOR_PSEUDO_AGENT2 = "operator";
|
|
22227
22828
|
HANDOFF_OP = "v1.1_local_handoff";
|
|
22228
22829
|
CROSS_HARNESS_OPS = /* @__PURE__ */ new Set([
|
|
22229
22830
|
"cross_harness_approval_aggregated",
|
|
@@ -23104,6 +23705,229 @@ var init_suspicious_tool_call_detector = __esm({
|
|
|
23104
23705
|
}
|
|
23105
23706
|
});
|
|
23106
23707
|
|
|
23708
|
+
// src/sentinel/sentinels/anomaly-trigger.ts
|
|
23709
|
+
function computeCompoundFindings(windowZero, now) {
|
|
23710
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
23711
|
+
for (const f of windowZero) {
|
|
23712
|
+
if (!f.agent_id) continue;
|
|
23713
|
+
if (!isWarnOrAlert(f.severity)) continue;
|
|
23714
|
+
let bucket = byAgent.get(f.agent_id);
|
|
23715
|
+
if (!bucket) {
|
|
23716
|
+
bucket = [];
|
|
23717
|
+
byAgent.set(f.agent_id, bucket);
|
|
23718
|
+
}
|
|
23719
|
+
bucket.push(f);
|
|
23720
|
+
}
|
|
23721
|
+
const out = [];
|
|
23722
|
+
for (const [agentId, group] of byAgent.entries()) {
|
|
23723
|
+
const distinctSentinels = new Set(group.map((f) => f.sentinel_id));
|
|
23724
|
+
if (distinctSentinels.size < COMPOUND_TRIGGER_MIN_SENTINELS) continue;
|
|
23725
|
+
const contributingSentinels = [...distinctSentinels].sort();
|
|
23726
|
+
const evidence = group.map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23727
|
+
const summary = `${agentId} agent triggered ${distinctSentinels.size} distinct sentinels in the last 24h: ${contributingSentinels.join(", ")}. Compound suspicious behavior; review the contributing findings.`;
|
|
23728
|
+
out.push({
|
|
23729
|
+
finding_id: "",
|
|
23730
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23731
|
+
severity: "alert",
|
|
23732
|
+
agent_id: agentId,
|
|
23733
|
+
summary,
|
|
23734
|
+
details: {
|
|
23735
|
+
trigger: "compound",
|
|
23736
|
+
agent_id: agentId,
|
|
23737
|
+
contributing_sentinels: contributingSentinels,
|
|
23738
|
+
contributing_finding_count: group.length
|
|
23739
|
+
},
|
|
23740
|
+
observed_at: now.toISOString(),
|
|
23741
|
+
evidence_audit_ids: evidence,
|
|
23742
|
+
fortress_id: ""
|
|
23743
|
+
});
|
|
23744
|
+
}
|
|
23745
|
+
return out;
|
|
23746
|
+
}
|
|
23747
|
+
function computeCountSpikeFinding(windowed, now) {
|
|
23748
|
+
const currentCount = (windowed[0] ?? []).length;
|
|
23749
|
+
const baselineCounts = [];
|
|
23750
|
+
for (let i = 1; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23751
|
+
baselineCounts.push((windowed[i] ?? []).length);
|
|
23752
|
+
}
|
|
23753
|
+
const populated = baselineCounts.filter((c) => c > 0).length;
|
|
23754
|
+
if (populated < BASELINE_WINDOWS5) {
|
|
23755
|
+
return null;
|
|
23756
|
+
}
|
|
23757
|
+
const mean = baselineCounts.reduce((sum, c) => sum + c, 0) / baselineCounts.length;
|
|
23758
|
+
const variance = baselineCounts.reduce((sum, c) => sum + (c - mean) ** 2, 0) / baselineCounts.length;
|
|
23759
|
+
const stddev = Math.sqrt(variance);
|
|
23760
|
+
const warnThreshold = mean + WARN_SIGMA5 * stddev;
|
|
23761
|
+
const alertThreshold = mean + ALERT_SIGMA5 * stddev;
|
|
23762
|
+
if (currentCount > alertThreshold) {
|
|
23763
|
+
return buildCountFinding(
|
|
23764
|
+
currentCount,
|
|
23765
|
+
mean,
|
|
23766
|
+
stddev,
|
|
23767
|
+
ALERT_SIGMA5,
|
|
23768
|
+
"alert",
|
|
23769
|
+
windowed[0] ?? [],
|
|
23770
|
+
now
|
|
23771
|
+
);
|
|
23772
|
+
}
|
|
23773
|
+
if (currentCount > warnThreshold) {
|
|
23774
|
+
return buildCountFinding(
|
|
23775
|
+
currentCount,
|
|
23776
|
+
mean,
|
|
23777
|
+
stddev,
|
|
23778
|
+
WARN_SIGMA5,
|
|
23779
|
+
"warn",
|
|
23780
|
+
windowed[0] ?? [],
|
|
23781
|
+
now
|
|
23782
|
+
);
|
|
23783
|
+
}
|
|
23784
|
+
return null;
|
|
23785
|
+
}
|
|
23786
|
+
function buildCountFinding(currentCount, mean, stddev, sigma, severity, windowZero, now) {
|
|
23787
|
+
const ratio = mean === 0 ? Number.POSITIVE_INFINITY : currentCount / mean;
|
|
23788
|
+
const ratioStr = Number.isFinite(ratio) ? `${ratio.toFixed(1)}x normal` : "no prior baseline";
|
|
23789
|
+
const summary = `Fortress finding count is ${ratioStr}: ${currentCount} findings in last 24h, baseline ${mean.toFixed(1)} +/- ${stddev.toFixed(1)}. Crossed +${sigma} sigma threshold across all sentinels.`;
|
|
23790
|
+
const evidence = windowZero.map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23791
|
+
return {
|
|
23792
|
+
finding_id: "",
|
|
23793
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23794
|
+
severity,
|
|
23795
|
+
summary,
|
|
23796
|
+
details: {
|
|
23797
|
+
trigger: "count_spike",
|
|
23798
|
+
current_count: currentCount,
|
|
23799
|
+
baseline_mean: mean,
|
|
23800
|
+
baseline_stddev: stddev,
|
|
23801
|
+
sigma_threshold: sigma,
|
|
23802
|
+
ratio: Number.isFinite(ratio) ? ratio : null
|
|
23803
|
+
},
|
|
23804
|
+
observed_at: now.toISOString(),
|
|
23805
|
+
evidence_audit_ids: evidence,
|
|
23806
|
+
fortress_id: ""
|
|
23807
|
+
};
|
|
23808
|
+
}
|
|
23809
|
+
function computeNovelComboFinding(windowed, now) {
|
|
23810
|
+
const distinctByWindow = [];
|
|
23811
|
+
for (let i = 0; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23812
|
+
const set = /* @__PURE__ */ new Set();
|
|
23813
|
+
for (const f of windowed[i] ?? []) {
|
|
23814
|
+
set.add(f.sentinel_id);
|
|
23815
|
+
}
|
|
23816
|
+
distinctByWindow.push(set);
|
|
23817
|
+
}
|
|
23818
|
+
const populatedBaselineWindows = distinctByWindow.slice(1).filter((s) => s.size > 0).length;
|
|
23819
|
+
if (populatedBaselineWindows < BASELINE_WINDOWS5) {
|
|
23820
|
+
return null;
|
|
23821
|
+
}
|
|
23822
|
+
const currentCombo = distinctByWindow[0];
|
|
23823
|
+
if (currentCombo.size < 2) return null;
|
|
23824
|
+
const currentKey = comboKey(currentCombo);
|
|
23825
|
+
for (let i = 1; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23826
|
+
if (comboKey(distinctByWindow[i]) === currentKey) {
|
|
23827
|
+
return null;
|
|
23828
|
+
}
|
|
23829
|
+
}
|
|
23830
|
+
const sentinelIds = [...currentCombo].sort();
|
|
23831
|
+
const summary = `Novel sentinel-ID combination this 24h window: ${sentinelIds.join(" + ")}. This co-occurrence pattern has not appeared in the prior ${BASELINE_WINDOWS5} baseline windows.`;
|
|
23832
|
+
const evidence = (windowed[0] ?? []).map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23833
|
+
return {
|
|
23834
|
+
finding_id: "",
|
|
23835
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23836
|
+
severity: "info",
|
|
23837
|
+
summary,
|
|
23838
|
+
details: {
|
|
23839
|
+
trigger: "novel_combo",
|
|
23840
|
+
sentinel_ids: sentinelIds,
|
|
23841
|
+
baseline_window_count: BASELINE_WINDOWS5
|
|
23842
|
+
},
|
|
23843
|
+
observed_at: now.toISOString(),
|
|
23844
|
+
evidence_audit_ids: evidence,
|
|
23845
|
+
fortress_id: ""
|
|
23846
|
+
};
|
|
23847
|
+
}
|
|
23848
|
+
function isWarnOrAlert(s) {
|
|
23849
|
+
return s === "warn" || s === "alert";
|
|
23850
|
+
}
|
|
23851
|
+
function bucketByWindow(findings, nowMs) {
|
|
23852
|
+
const buckets = Array.from(
|
|
23853
|
+
{ length: BASELINE_WINDOWS5 + 1 },
|
|
23854
|
+
() => []
|
|
23855
|
+
);
|
|
23856
|
+
for (const f of findings) {
|
|
23857
|
+
const ts = Date.parse(f.observed_at);
|
|
23858
|
+
if (!Number.isFinite(ts)) continue;
|
|
23859
|
+
const age = nowMs - ts;
|
|
23860
|
+
if (age < 0) continue;
|
|
23861
|
+
const idx = Math.floor(age / WINDOW_MS);
|
|
23862
|
+
if (idx > BASELINE_WINDOWS5) continue;
|
|
23863
|
+
buckets[idx].push(f);
|
|
23864
|
+
}
|
|
23865
|
+
return buckets;
|
|
23866
|
+
}
|
|
23867
|
+
function comboKey(set) {
|
|
23868
|
+
return [...set].sort().join("|");
|
|
23869
|
+
}
|
|
23870
|
+
var ANOMALY_TRIGGER_SENTINEL_ID, WARN_SIGMA5, ALERT_SIGMA5, BASELINE_WINDOWS5, QUERY_LIMIT5, WINDOW_MS, COMPOUND_TRIGGER_MIN_SENTINELS, AnomalyTriggerWatcher;
|
|
23871
|
+
var init_anomaly_trigger = __esm({
|
|
23872
|
+
"src/sentinel/sentinels/anomaly-trigger.ts"() {
|
|
23873
|
+
init_sentinel();
|
|
23874
|
+
ANOMALY_TRIGGER_SENTINEL_ID = "anomaly-trigger";
|
|
23875
|
+
WARN_SIGMA5 = 3;
|
|
23876
|
+
ALERT_SIGMA5 = 6;
|
|
23877
|
+
BASELINE_WINDOWS5 = 7;
|
|
23878
|
+
QUERY_LIMIT5 = 5e3;
|
|
23879
|
+
WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
23880
|
+
COMPOUND_TRIGGER_MIN_SENTINELS = 2;
|
|
23881
|
+
AnomalyTriggerWatcher = class extends Sentinel {
|
|
23882
|
+
sentinelId = ANOMALY_TRIGGER_SENTINEL_ID;
|
|
23883
|
+
description = "Meta-sentinel. Watches for patterns ACROSS other sentinels' findings: compound suspicious behavior on one agent, fortress-wide finding-count spikes, and novel cross-sentinel combinations. Closes WP-V1.3-1 Sentinel Baseline Pack.";
|
|
23884
|
+
async subscribe(context) {
|
|
23885
|
+
if (!context.findingStore) {
|
|
23886
|
+
throw new Error(
|
|
23887
|
+
`${ANOMALY_TRIGGER_SENTINEL_ID}: findingStore missing from SentinelContext; this meta-sentinel requires the Phi-1 finding store`
|
|
23888
|
+
);
|
|
23889
|
+
}
|
|
23890
|
+
await super.subscribe(context);
|
|
23891
|
+
}
|
|
23892
|
+
async evaluate() {
|
|
23893
|
+
const ctx = this.requireContext();
|
|
23894
|
+
const findingStore = ctx.findingStore;
|
|
23895
|
+
if (!findingStore) {
|
|
23896
|
+
return [];
|
|
23897
|
+
}
|
|
23898
|
+
const now = ctx.now();
|
|
23899
|
+
const nowMs = now.getTime();
|
|
23900
|
+
const windowSpanMs = (BASELINE_WINDOWS5 + 1) * WINDOW_MS;
|
|
23901
|
+
const sinceIso = new Date(nowMs - windowSpanMs).toISOString();
|
|
23902
|
+
let findings;
|
|
23903
|
+
try {
|
|
23904
|
+
findings = await findingStore.listFindings({
|
|
23905
|
+
since: sinceIso,
|
|
23906
|
+
limit: QUERY_LIMIT5
|
|
23907
|
+
});
|
|
23908
|
+
} catch {
|
|
23909
|
+
return [];
|
|
23910
|
+
}
|
|
23911
|
+
const firstOrderFindings = findings.filter(
|
|
23912
|
+
(f) => f.sentinel_id !== ANOMALY_TRIGGER_SENTINEL_ID
|
|
23913
|
+
);
|
|
23914
|
+
const windowed = bucketByWindow(firstOrderFindings, nowMs);
|
|
23915
|
+
const out = [];
|
|
23916
|
+
const compoundFindings = computeCompoundFindings(
|
|
23917
|
+
windowed[0] ?? [],
|
|
23918
|
+
now
|
|
23919
|
+
);
|
|
23920
|
+
out.push(...compoundFindings);
|
|
23921
|
+
const countSpikeFinding = computeCountSpikeFinding(windowed, now);
|
|
23922
|
+
if (countSpikeFinding) out.push(countSpikeFinding);
|
|
23923
|
+
const novelComboFinding = computeNovelComboFinding(windowed, now);
|
|
23924
|
+
if (novelComboFinding) out.push(novelComboFinding);
|
|
23925
|
+
return out;
|
|
23926
|
+
}
|
|
23927
|
+
};
|
|
23928
|
+
}
|
|
23929
|
+
});
|
|
23930
|
+
|
|
23107
23931
|
// src/sentinel/sentinels/index.ts
|
|
23108
23932
|
var PHI1_BASELINE_CATALOG;
|
|
23109
23933
|
var init_sentinels = __esm({
|
|
@@ -23112,6 +23936,7 @@ var init_sentinels = __esm({
|
|
|
23112
23936
|
init_cross_agent_chatter_watcher();
|
|
23113
23937
|
init_credential_usage_watcher();
|
|
23114
23938
|
init_suspicious_tool_call_detector();
|
|
23939
|
+
init_anomaly_trigger();
|
|
23115
23940
|
PHI1_BASELINE_CATALOG = [
|
|
23116
23941
|
{
|
|
23117
23942
|
sentinelId: EGRESS_VOLUME_SENTINEL_ID,
|
|
@@ -23132,6 +23957,11 @@ var init_sentinels = __esm({
|
|
|
23132
23957
|
sentinelId: SUSPICIOUS_TOOL_CALL_SENTINEL_ID,
|
|
23133
23958
|
description: "Surfaces tool calls whose argument shape, call frequency, or permission combination looks unusual for the fortress's recent history.",
|
|
23134
23959
|
factory: () => new SuspiciousToolCallDetector()
|
|
23960
|
+
},
|
|
23961
|
+
{
|
|
23962
|
+
sentinelId: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23963
|
+
description: "Meta-sentinel. Watches for patterns ACROSS other sentinels' findings: compound suspicious behavior on one agent, fortress-wide finding-count spikes, and novel cross-sentinel combinations. Closes WP-V1.3-1 Sentinel Baseline Pack.",
|
|
23964
|
+
factory: () => new AnomalyTriggerWatcher()
|
|
23135
23965
|
}
|
|
23136
23966
|
];
|
|
23137
23967
|
}
|
|
@@ -25056,7 +25886,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
25056
25886
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25057
25887
|
const canonicalBytes = canonicalize2(outcome);
|
|
25058
25888
|
const canonicalString = new TextDecoder().decode(canonicalBytes);
|
|
25059
|
-
const
|
|
25889
|
+
const sha25612 = createCommitment(canonicalString);
|
|
25060
25890
|
let pedersenData;
|
|
25061
25891
|
if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
|
|
25062
25892
|
const pedersen = createPedersenCommitment(outcome.rounds);
|
|
@@ -25068,7 +25898,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
25068
25898
|
const commitmentPayload = {
|
|
25069
25899
|
bridge_commitment_id: commitmentId,
|
|
25070
25900
|
session_id: outcome.session_id,
|
|
25071
|
-
sha256_commitment:
|
|
25901
|
+
sha256_commitment: sha25612.commitment,
|
|
25072
25902
|
terms_hash: outcome.terms_hash,
|
|
25073
25903
|
committer_did: identity.did,
|
|
25074
25904
|
committed_at: now,
|
|
@@ -25079,8 +25909,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
25079
25909
|
return {
|
|
25080
25910
|
bridge_commitment_id: commitmentId,
|
|
25081
25911
|
session_id: outcome.session_id,
|
|
25082
|
-
sha256_commitment:
|
|
25083
|
-
blinding_factor:
|
|
25912
|
+
sha256_commitment: sha25612.commitment,
|
|
25913
|
+
blinding_factor: sha25612.blinding_factor,
|
|
25084
25914
|
committer_did: identity.did,
|
|
25085
25915
|
signature: toBase64url(signature),
|
|
25086
25916
|
pedersen_commitment: pedersenData,
|
|
@@ -34650,7 +35480,7 @@ var init_recovery_key_disclosure = __esm({
|
|
|
34650
35480
|
});
|
|
34651
35481
|
|
|
34652
35482
|
// src/hub/types.ts
|
|
34653
|
-
var
|
|
35483
|
+
var init_types5 = __esm({
|
|
34654
35484
|
"src/hub/types.ts"() {
|
|
34655
35485
|
}
|
|
34656
35486
|
});
|
|
@@ -35689,7 +36519,7 @@ var init_hub = __esm({
|
|
|
35689
36519
|
"src/hub/index.ts"() {
|
|
35690
36520
|
init_constants3();
|
|
35691
36521
|
init_errors4();
|
|
35692
|
-
|
|
36522
|
+
init_types5();
|
|
35693
36523
|
init_agent_registry();
|
|
35694
36524
|
init_inbox_store();
|
|
35695
36525
|
init_inbox_aggregator();
|
|
@@ -42193,6 +43023,28 @@ ${err.message}
|
|
|
42193
43023
|
if (dashboard) {
|
|
42194
43024
|
dashboard.setSentinelDispatcher(sentinelDispatcher);
|
|
42195
43025
|
}
|
|
43026
|
+
const anomalyDispatcher = new AnomalyPipelineDispatcher({
|
|
43027
|
+
findingStore: sentinelFindingStore,
|
|
43028
|
+
auditLog,
|
|
43029
|
+
storage,
|
|
43030
|
+
masterKey,
|
|
43031
|
+
fortressId: fortressIdForAggregator,
|
|
43032
|
+
identityId: aggregatorIdentityId
|
|
43033
|
+
});
|
|
43034
|
+
anomalyDispatcher.start();
|
|
43035
|
+
const handoffLog = new HandoffLog({
|
|
43036
|
+
auditLog,
|
|
43037
|
+
fortressId: fortressIdForAggregator
|
|
43038
|
+
});
|
|
43039
|
+
const handoffEventBridge = new HandoffEventBridge();
|
|
43040
|
+
if (dashboard) {
|
|
43041
|
+
dashboard.setHandoffLog({
|
|
43042
|
+
handoffLog,
|
|
43043
|
+
eventBridge: handoffEventBridge,
|
|
43044
|
+
auditLog,
|
|
43045
|
+
operatorId: aggregatorIdentityId
|
|
43046
|
+
});
|
|
43047
|
+
}
|
|
42196
43048
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
42197
43049
|
const { tools: sanctuaryMetaTools } = createSanctuaryTools({
|
|
42198
43050
|
config,
|
|
@@ -42389,6 +43241,9 @@ var init_src = __esm({
|
|
|
42389
43241
|
init_sentinel_finding_store();
|
|
42390
43242
|
init_sentinel_registry();
|
|
42391
43243
|
init_sentinel_dispatcher();
|
|
43244
|
+
init_anomaly_pipeline();
|
|
43245
|
+
init_handoff_log();
|
|
43246
|
+
init_handoff_routes();
|
|
42392
43247
|
init_sentinels();
|
|
42393
43248
|
init_subscription_store();
|
|
42394
43249
|
init_tools4();
|
|
@@ -44489,32 +45344,32 @@ endstream`;
|
|
|
44489
45344
|
const offsets = new Array(totalObjects + 1).fill(0);
|
|
44490
45345
|
const chunks = [];
|
|
44491
45346
|
let bytePos = 0;
|
|
44492
|
-
const
|
|
45347
|
+
const write4 = (s) => {
|
|
44493
45348
|
const buf = Buffer.from(s, "latin1");
|
|
44494
45349
|
chunks.push(buf);
|
|
44495
45350
|
bytePos += buf.length;
|
|
44496
45351
|
};
|
|
44497
|
-
|
|
45352
|
+
write4("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
|
|
44498
45353
|
for (let i = 1; i <= totalObjects; i++) {
|
|
44499
45354
|
offsets[i] = bytePos;
|
|
44500
|
-
|
|
45355
|
+
write4(`${i} 0 obj
|
|
44501
45356
|
${objectBodies[i]}
|
|
44502
45357
|
endobj
|
|
44503
45358
|
`);
|
|
44504
45359
|
}
|
|
44505
45360
|
const xrefPos = bytePos;
|
|
44506
|
-
|
|
45361
|
+
write4(`xref
|
|
44507
45362
|
0 ${totalObjects + 1}
|
|
44508
45363
|
`);
|
|
44509
|
-
|
|
45364
|
+
write4("0000000000 65535 f \n");
|
|
44510
45365
|
for (let i = 1; i <= totalObjects; i++) {
|
|
44511
|
-
|
|
45366
|
+
write4(`${offsets[i].toString().padStart(10, "0")} 00000 n
|
|
44512
45367
|
`);
|
|
44513
45368
|
}
|
|
44514
|
-
|
|
45369
|
+
write4(`trailer
|
|
44515
45370
|
<< /Size ${totalObjects + 1} /Root 1 0 R >>
|
|
44516
45371
|
`);
|
|
44517
|
-
|
|
45372
|
+
write4(`startxref
|
|
44518
45373
|
${xrefPos}
|
|
44519
45374
|
%%EOF
|
|
44520
45375
|
`);
|
|
@@ -47785,6 +48640,365 @@ var init_sentinel2 = __esm({
|
|
|
47785
48640
|
init_sentinels();
|
|
47786
48641
|
}
|
|
47787
48642
|
});
|
|
48643
|
+
async function issueDidWeb(opts) {
|
|
48644
|
+
if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
|
|
48645
|
+
throw new Error(
|
|
48646
|
+
`did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
|
|
48647
|
+
);
|
|
48648
|
+
}
|
|
48649
|
+
if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
|
|
48650
|
+
throw new Error(
|
|
48651
|
+
`did-web: fortress_id '${opts.fortress_id}' is not a valid label`
|
|
48652
|
+
);
|
|
48653
|
+
}
|
|
48654
|
+
if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
|
|
48655
|
+
throw new Error(
|
|
48656
|
+
`did-web: agent_label '${opts.agent_label}' is not a valid label`
|
|
48657
|
+
);
|
|
48658
|
+
}
|
|
48659
|
+
if (opts.public_key.length !== 32) {
|
|
48660
|
+
throw new Error(
|
|
48661
|
+
`did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
|
|
48662
|
+
);
|
|
48663
|
+
}
|
|
48664
|
+
const did = buildDid(opts);
|
|
48665
|
+
const verificationMethodId = `${did}#key-1`;
|
|
48666
|
+
const verificationMethod = {
|
|
48667
|
+
id: verificationMethodId,
|
|
48668
|
+
type: "JsonWebKey2020",
|
|
48669
|
+
controller: did,
|
|
48670
|
+
publicKeyJwk: {
|
|
48671
|
+
kty: "OKP",
|
|
48672
|
+
crv: "Ed25519",
|
|
48673
|
+
x: toBase64url(opts.public_key)
|
|
48674
|
+
}
|
|
48675
|
+
};
|
|
48676
|
+
const didDocument = {
|
|
48677
|
+
"@context": [...DID_CONTEXT],
|
|
48678
|
+
id: did,
|
|
48679
|
+
verificationMethod: [verificationMethod],
|
|
48680
|
+
authentication: [verificationMethodId],
|
|
48681
|
+
assertionMethod: [verificationMethodId]
|
|
48682
|
+
};
|
|
48683
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
48684
|
+
return {
|
|
48685
|
+
did,
|
|
48686
|
+
did_document: didDocument,
|
|
48687
|
+
public_key: opts.public_key,
|
|
48688
|
+
created_at: now.toISOString(),
|
|
48689
|
+
authority_host: opts.authority_host,
|
|
48690
|
+
fortress_id: opts.fortress_id,
|
|
48691
|
+
...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
|
|
48692
|
+
};
|
|
48693
|
+
}
|
|
48694
|
+
function publishDidWebDocument(identifier, opts = {}) {
|
|
48695
|
+
const path = opts.publish_path ?? canonicalPublishPath(identifier);
|
|
48696
|
+
const artifact = canonicalSerializeDidDocument(identifier.did_document);
|
|
48697
|
+
const digest = sha256.sha256(stringToBytes(artifact));
|
|
48698
|
+
const url = `https://${identifier.authority_host}${path}`;
|
|
48699
|
+
return {
|
|
48700
|
+
url,
|
|
48701
|
+
publish_path: path,
|
|
48702
|
+
artifact,
|
|
48703
|
+
sha256: hashToString(digest)
|
|
48704
|
+
};
|
|
48705
|
+
}
|
|
48706
|
+
function buildDid(opts) {
|
|
48707
|
+
if (opts.agent_label === void 0) {
|
|
48708
|
+
return `did:web:${opts.authority_host}`;
|
|
48709
|
+
}
|
|
48710
|
+
return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
|
|
48711
|
+
}
|
|
48712
|
+
function canonicalPublishPath(identifier) {
|
|
48713
|
+
if (identifier.agent_label === void 0) {
|
|
48714
|
+
return "/.well-known/did.json";
|
|
48715
|
+
}
|
|
48716
|
+
return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
|
|
48717
|
+
}
|
|
48718
|
+
function canonicalSerializeDidDocument(doc) {
|
|
48719
|
+
return JSON.stringify(
|
|
48720
|
+
{
|
|
48721
|
+
"@context": doc["@context"],
|
|
48722
|
+
id: doc.id,
|
|
48723
|
+
verificationMethod: doc.verificationMethod,
|
|
48724
|
+
authentication: doc.authentication,
|
|
48725
|
+
assertionMethod: doc.assertionMethod
|
|
48726
|
+
},
|
|
48727
|
+
null,
|
|
48728
|
+
2
|
|
48729
|
+
);
|
|
48730
|
+
}
|
|
48731
|
+
var DID_CONTEXT, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
|
|
48732
|
+
var init_did_web = __esm({
|
|
48733
|
+
"src/recognition/did-web.ts"() {
|
|
48734
|
+
init_encoding();
|
|
48735
|
+
init_hashing();
|
|
48736
|
+
DID_CONTEXT = [
|
|
48737
|
+
"https://www.w3.org/ns/did/v1",
|
|
48738
|
+
"https://w3id.org/security/suites/jws-2020/v1"
|
|
48739
|
+
];
|
|
48740
|
+
HOST_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
48741
|
+
FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
48742
|
+
AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
48743
|
+
}
|
|
48744
|
+
});
|
|
48745
|
+
|
|
48746
|
+
// src/cli/did-web.ts
|
|
48747
|
+
var did_web_exports = {};
|
|
48748
|
+
__export(did_web_exports, {
|
|
48749
|
+
runDidWebCommand: () => runDidWebCommand
|
|
48750
|
+
});
|
|
48751
|
+
function write3(stream, text) {
|
|
48752
|
+
stream.write(text);
|
|
48753
|
+
}
|
|
48754
|
+
function flagValue3(argv, name) {
|
|
48755
|
+
const i = argv.indexOf(name);
|
|
48756
|
+
if (i === -1) return void 0;
|
|
48757
|
+
return argv[i + 1];
|
|
48758
|
+
}
|
|
48759
|
+
function hasFlag3(argv, name) {
|
|
48760
|
+
return argv.includes(name);
|
|
48761
|
+
}
|
|
48762
|
+
function printUsage7(out) {
|
|
48763
|
+
write3(
|
|
48764
|
+
out,
|
|
48765
|
+
`Usage: sanctuary did-web <command> [options]
|
|
48766
|
+
|
|
48767
|
+
Commands:
|
|
48768
|
+
issue --authority-host <host> [--agent-label <label>] [--json]
|
|
48769
|
+
Generate a did:web identifier bound to the operator's
|
|
48770
|
+
fortress Ed25519 public key. Writes the DID Document
|
|
48771
|
+
artifact to <storage>/recognition/did-web.json and
|
|
48772
|
+
prints publication instructions for the operator's
|
|
48773
|
+
HTTPS server.
|
|
48774
|
+
|
|
48775
|
+
show [--json] Display the previously issued did:web identifier.
|
|
48776
|
+
Exits non-zero if none issued.
|
|
48777
|
+
|
|
48778
|
+
Options:
|
|
48779
|
+
--authority-host <host> HTTPS host the operator controls and will
|
|
48780
|
+
serve /.well-known/did.json from.
|
|
48781
|
+
--agent-label <label> Optional agent-scoped identifier (label-safe;
|
|
48782
|
+
alphanumeric + dash + underscore, 1-64 chars).
|
|
48783
|
+
--fortress <path> Override the storage path.
|
|
48784
|
+
--passphrase <val> Passphrase for master-key derivation.
|
|
48785
|
+
--json Output as JSON.
|
|
48786
|
+
--help, -h Show this help.
|
|
48787
|
+
|
|
48788
|
+
Castle-walking note: did:web resolution is outbound HTTPS by design.
|
|
48789
|
+
This CLI never opens an outbound socket. The opt-in surface is your
|
|
48790
|
+
choice to run "did-web issue" with --authority-host; the resulting
|
|
48791
|
+
artifact is yours to publish on your own infrastructure. Sanctuary
|
|
48792
|
+
does not phone home.
|
|
48793
|
+
`
|
|
48794
|
+
);
|
|
48795
|
+
}
|
|
48796
|
+
async function runDidWebCommand(args) {
|
|
48797
|
+
const argv = args.argv;
|
|
48798
|
+
const out = args.out ?? process.stdout;
|
|
48799
|
+
const err = args.err ?? process.stderr;
|
|
48800
|
+
const env = args.env ?? process.env;
|
|
48801
|
+
if (argv.length === 0 || hasFlag3(argv, "--help") || hasFlag3(argv, "-h")) {
|
|
48802
|
+
printUsage7(out);
|
|
48803
|
+
return 0;
|
|
48804
|
+
}
|
|
48805
|
+
const command = argv[0];
|
|
48806
|
+
if (command === "issue") {
|
|
48807
|
+
return await cmdIssue(argv.slice(1), out, err, env);
|
|
48808
|
+
}
|
|
48809
|
+
if (command === "show") {
|
|
48810
|
+
return await cmdShow3(argv.slice(1), out, err);
|
|
48811
|
+
}
|
|
48812
|
+
write3(err, `Unknown did-web command: ${command}
|
|
48813
|
+
`);
|
|
48814
|
+
write3(err, `Run "sanctuary did-web --help" for usage.
|
|
48815
|
+
`);
|
|
48816
|
+
return 2;
|
|
48817
|
+
}
|
|
48818
|
+
async function loadFortressIdentity(argv, env, err) {
|
|
48819
|
+
const fortressFlag = flagValue3(argv, "--fortress");
|
|
48820
|
+
if (fortressFlag) {
|
|
48821
|
+
process.env.SANCTUARY_STORAGE_PATH = fortressFlag;
|
|
48822
|
+
}
|
|
48823
|
+
const passphrase = flagValue3(argv, "--passphrase") ?? env.SANCTUARY_PASSPHRASE;
|
|
48824
|
+
const recoveryKey = env.SANCTUARY_RECOVERY_KEY;
|
|
48825
|
+
if (!passphrase && !recoveryKey) {
|
|
48826
|
+
write3(
|
|
48827
|
+
err,
|
|
48828
|
+
"Error: sanctuary did-web requires SANCTUARY_PASSPHRASE, --passphrase, or SANCTUARY_RECOVERY_KEY.\n"
|
|
48829
|
+
);
|
|
48830
|
+
return null;
|
|
48831
|
+
}
|
|
48832
|
+
const config = await loadConfig();
|
|
48833
|
+
await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
|
|
48834
|
+
const stateStoragePath = path.join(config.storage_path, "state");
|
|
48835
|
+
const storage = new FilesystemStorage(stateStoragePath);
|
|
48836
|
+
let masterKey;
|
|
48837
|
+
if (passphrase) {
|
|
48838
|
+
let existingParams;
|
|
48839
|
+
const raw = await storage.read("_meta", "key-params");
|
|
48840
|
+
if (raw) {
|
|
48841
|
+
existingParams = JSON.parse(bytesToString(raw));
|
|
48842
|
+
}
|
|
48843
|
+
const derivation = await deriveMasterKey(passphrase, existingParams);
|
|
48844
|
+
masterKey = derivation.key;
|
|
48845
|
+
} else if (recoveryKey) {
|
|
48846
|
+
masterKey = fromBase64url(recoveryKey);
|
|
48847
|
+
} else {
|
|
48848
|
+
return null;
|
|
48849
|
+
}
|
|
48850
|
+
const identityManager = new IdentityManager(storage, masterKey);
|
|
48851
|
+
const loadResult = await identityManager.load();
|
|
48852
|
+
if (loadResult.loaded === 0) {
|
|
48853
|
+
write3(
|
|
48854
|
+
err,
|
|
48855
|
+
loadResult.total > 0 ? "Error: identity files found but none could be decrypted. Wrong passphrase?\n" : "Error: no identities on this fortress yet. Run sanctuary wrap first.\n"
|
|
48856
|
+
);
|
|
48857
|
+
return null;
|
|
48858
|
+
}
|
|
48859
|
+
const primary = identityManager.getDefault();
|
|
48860
|
+
if (!primary) {
|
|
48861
|
+
write3(err, "Error: no primary identity on this fortress yet. Run sanctuary wrap first.\n");
|
|
48862
|
+
return null;
|
|
48863
|
+
}
|
|
48864
|
+
return {
|
|
48865
|
+
publicKey: fromBase64url(primary.public_key),
|
|
48866
|
+
identityId: primary.identity_id,
|
|
48867
|
+
storagePath: config.storage_path
|
|
48868
|
+
};
|
|
48869
|
+
}
|
|
48870
|
+
async function cmdIssue(argv, out, err, env) {
|
|
48871
|
+
const authorityHost = flagValue3(argv, "--authority-host");
|
|
48872
|
+
const agentLabel = flagValue3(argv, "--agent-label");
|
|
48873
|
+
const json = hasFlag3(argv, "--json");
|
|
48874
|
+
if (!authorityHost) {
|
|
48875
|
+
write3(err, "Error: --authority-host is required.\n");
|
|
48876
|
+
write3(err, "Example: sanctuary did-web issue --authority-host alice.example.com\n");
|
|
48877
|
+
return 1;
|
|
48878
|
+
}
|
|
48879
|
+
const snapshot = await loadFortressIdentity(argv, env, err);
|
|
48880
|
+
if (!snapshot) return 1;
|
|
48881
|
+
let identifier;
|
|
48882
|
+
try {
|
|
48883
|
+
identifier = await issueDidWeb({
|
|
48884
|
+
fortress_id: snapshot.identityId,
|
|
48885
|
+
authority_host: authorityHost,
|
|
48886
|
+
public_key: snapshot.publicKey,
|
|
48887
|
+
...agentLabel !== void 0 ? { agent_label: agentLabel } : {}
|
|
48888
|
+
});
|
|
48889
|
+
} catch (e) {
|
|
48890
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
48891
|
+
write3(err, `Error: ${message}
|
|
48892
|
+
`);
|
|
48893
|
+
return 1;
|
|
48894
|
+
}
|
|
48895
|
+
const artifact = publishDidWebDocument(identifier);
|
|
48896
|
+
const persistDir = path.join(snapshot.storagePath, "recognition");
|
|
48897
|
+
await promises.mkdir(persistDir, { recursive: true, mode: 448 });
|
|
48898
|
+
const persistPath = path.join(persistDir, "did-web.json");
|
|
48899
|
+
const record = {
|
|
48900
|
+
version: 1,
|
|
48901
|
+
identifier: {
|
|
48902
|
+
did: identifier.did,
|
|
48903
|
+
created_at: identifier.created_at,
|
|
48904
|
+
authority_host: identifier.authority_host,
|
|
48905
|
+
fortress_id: identifier.fortress_id,
|
|
48906
|
+
...identifier.agent_label !== void 0 ? { agent_label: identifier.agent_label } : {},
|
|
48907
|
+
did_document: identifier.did_document
|
|
48908
|
+
},
|
|
48909
|
+
artifact: {
|
|
48910
|
+
url: artifact.url,
|
|
48911
|
+
publish_path: artifact.publish_path,
|
|
48912
|
+
sha256: artifact.sha256
|
|
48913
|
+
}
|
|
48914
|
+
};
|
|
48915
|
+
await promises.writeFile(persistPath, JSON.stringify(record, null, 2), {
|
|
48916
|
+
mode: 384
|
|
48917
|
+
});
|
|
48918
|
+
if (json) {
|
|
48919
|
+
write3(out, JSON.stringify(record, null, 2) + "\n");
|
|
48920
|
+
return 0;
|
|
48921
|
+
}
|
|
48922
|
+
write3(out, `did:web identifier issued.
|
|
48923
|
+
`);
|
|
48924
|
+
write3(out, ` DID: ${identifier.did}
|
|
48925
|
+
`);
|
|
48926
|
+
write3(out, ` Authority host: ${identifier.authority_host}
|
|
48927
|
+
`);
|
|
48928
|
+
write3(out, ` Created at: ${identifier.created_at}
|
|
48929
|
+
`);
|
|
48930
|
+
write3(out, ` Persisted: ${persistPath}
|
|
48931
|
+
`);
|
|
48932
|
+
write3(out, `
|
|
48933
|
+
Next step: publish the DID Document to your HTTPS host.
|
|
48934
|
+
`);
|
|
48935
|
+
write3(out, ` Target URL: ${artifact.url}
|
|
48936
|
+
`);
|
|
48937
|
+
write3(out, ` SHA-256: ${artifact.sha256}
|
|
48938
|
+
`);
|
|
48939
|
+
write3(out, ` Artifact: ${path.join(persistDir, "did.json")}
|
|
48940
|
+
`);
|
|
48941
|
+
const artifactPath = path.join(persistDir, "did.json");
|
|
48942
|
+
await promises.writeFile(artifactPath, artifact.artifact, { mode: 420 });
|
|
48943
|
+
write3(out, `
|
|
48944
|
+
Castle-walking note: this CLI never opens an outbound socket.
|
|
48945
|
+
`);
|
|
48946
|
+
write3(out, `Publishing the DID Document is your operation; serve the artifact
|
|
48947
|
+
`);
|
|
48948
|
+
write3(out, `at the URL above from infrastructure you control.
|
|
48949
|
+
`);
|
|
48950
|
+
return 0;
|
|
48951
|
+
}
|
|
48952
|
+
async function cmdShow3(argv, out, err, _env) {
|
|
48953
|
+
const json = hasFlag3(argv, "--json");
|
|
48954
|
+
const fortressFlag = flagValue3(argv, "--fortress");
|
|
48955
|
+
if (fortressFlag) {
|
|
48956
|
+
process.env.SANCTUARY_STORAGE_PATH = fortressFlag;
|
|
48957
|
+
}
|
|
48958
|
+
const config = await loadConfig();
|
|
48959
|
+
const persistPath = path.join(config.storage_path, "recognition", "did-web.json");
|
|
48960
|
+
let bytes;
|
|
48961
|
+
try {
|
|
48962
|
+
bytes = await promises.readFile(persistPath);
|
|
48963
|
+
} catch {
|
|
48964
|
+
write3(
|
|
48965
|
+
err,
|
|
48966
|
+
`No did:web identifier configured on this fortress.
|
|
48967
|
+
Run "sanctuary did-web issue --authority-host <host>" to issue one.
|
|
48968
|
+
`
|
|
48969
|
+
);
|
|
48970
|
+
return 1;
|
|
48971
|
+
}
|
|
48972
|
+
if (json) {
|
|
48973
|
+
write3(out, bytes.toString("utf-8"));
|
|
48974
|
+
if (!bytes.toString("utf-8").endsWith("\n")) write3(out, "\n");
|
|
48975
|
+
return 0;
|
|
48976
|
+
}
|
|
48977
|
+
const parsed = JSON.parse(bytes.toString("utf-8"));
|
|
48978
|
+
write3(out, `did:web identifier on this fortress:
|
|
48979
|
+
`);
|
|
48980
|
+
write3(out, ` DID: ${parsed.identifier.did}
|
|
48981
|
+
`);
|
|
48982
|
+
write3(out, ` Authority host: ${parsed.identifier.authority_host}
|
|
48983
|
+
`);
|
|
48984
|
+
write3(out, ` Created at: ${parsed.identifier.created_at}
|
|
48985
|
+
`);
|
|
48986
|
+
write3(out, ` Publish URL: ${parsed.artifact.url}
|
|
48987
|
+
`);
|
|
48988
|
+
write3(out, ` SHA-256: ${parsed.artifact.sha256}
|
|
48989
|
+
`);
|
|
48990
|
+
return 0;
|
|
48991
|
+
}
|
|
48992
|
+
var init_did_web2 = __esm({
|
|
48993
|
+
"src/cli/did-web.ts"() {
|
|
48994
|
+
init_filesystem();
|
|
48995
|
+
init_tools();
|
|
48996
|
+
init_key_derivation();
|
|
48997
|
+
init_encoding();
|
|
48998
|
+
init_config();
|
|
48999
|
+
init_did_web();
|
|
49000
|
+
}
|
|
49001
|
+
});
|
|
47788
49002
|
|
|
47789
49003
|
// src/mcp/broker-server.ts
|
|
47790
49004
|
var broker_server_exports = {};
|
|
@@ -48650,6 +49864,11 @@ async function main() {
|
|
|
48650
49864
|
const code = await runSentinelCommand2({ argv: args.slice(1) });
|
|
48651
49865
|
process.exit(code);
|
|
48652
49866
|
}
|
|
49867
|
+
if (args[0] === "did-web") {
|
|
49868
|
+
const { runDidWebCommand: runDidWebCommand2 } = await Promise.resolve().then(() => (init_did_web2(), did_web_exports));
|
|
49869
|
+
const code = await runDidWebCommand2({ argv: args.slice(1) });
|
|
49870
|
+
process.exit(code);
|
|
49871
|
+
}
|
|
48653
49872
|
if (args[0] === "broker-server") {
|
|
48654
49873
|
const { openBroker: openBroker2 } = await Promise.resolve().then(() => (init_open(), open_exports));
|
|
48655
49874
|
const { createBrokerMcpServer: createBrokerMcpServer2 } = await Promise.resolve().then(() => (init_broker_server(), broker_server_exports));
|