@sanctuary-framework/mcp-server 1.2.12 → 1.2.13
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 +1735 -124
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1735 -124
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +671 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +388 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +671 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -17618,8 +17618,30 @@ var init_handoff_log = __esm({
|
|
|
17618
17618
|
}
|
|
17619
17619
|
};
|
|
17620
17620
|
COORDINATION_VIEW_AUDIT_OPS = {
|
|
17621
|
+
/** v1.3 Omega-1: operator opened the chronological handoff list. */
|
|
17621
17622
|
VIEW_OPENED: "operator_coordination_view_opened",
|
|
17622
|
-
|
|
17623
|
+
/** v1.3 Omega-1: operator drilled into a single handoff for detail. */
|
|
17624
|
+
ENTRY_DRILLED: "operator_handoff_entry_drilled",
|
|
17625
|
+
/**
|
|
17626
|
+
* v1.3 Omega-3: operator opened the Workflows sibling-view (list of
|
|
17627
|
+
* multi-handoff workflows grouped by `workflow-grouper`). Mirrors
|
|
17628
|
+
* VIEW_OPENED's shape so the dashboard activity feed can group both
|
|
17629
|
+
* as "operator coordination surfaces."
|
|
17630
|
+
*/
|
|
17631
|
+
WORKFLOW_VIEW_OPENED: "operator_workflow_view_opened",
|
|
17632
|
+
/**
|
|
17633
|
+
* v1.3 Omega-3: operator drilled into a single workflow for its
|
|
17634
|
+
* timeline + member-handoffs detail. Mirrors ENTRY_DRILLED's shape.
|
|
17635
|
+
*/
|
|
17636
|
+
WORKFLOW_DRILLED: "operator_workflow_drilled",
|
|
17637
|
+
/**
|
|
17638
|
+
* v1.3 Omega-3: server-side state transition observed on a
|
|
17639
|
+
* workflow (e.g., in_progress -> stalled). Emitted by the route
|
|
17640
|
+
* layer after the state tracker diffs against its prior snapshot.
|
|
17641
|
+
* Distinct from the operator-action events above: this records what
|
|
17642
|
+
* the workflow itself is doing, not what the operator clicked.
|
|
17643
|
+
*/
|
|
17644
|
+
WORKFLOW_STATE_CHANGED: "coordination_workflow_state_changed"
|
|
17623
17645
|
};
|
|
17624
17646
|
}
|
|
17625
17647
|
});
|
|
@@ -17898,6 +17920,124 @@ var init_context_transfer_extractor = __esm({
|
|
|
17898
17920
|
};
|
|
17899
17921
|
}
|
|
17900
17922
|
});
|
|
17923
|
+
function groupHandoffsIntoWorkflows(handoffs, opts) {
|
|
17924
|
+
if (handoffs.length === 0) return [];
|
|
17925
|
+
const now = opts?.now ?? /* @__PURE__ */ new Date();
|
|
17926
|
+
const linkedGroups = /* @__PURE__ */ new Map();
|
|
17927
|
+
const unlinked = [];
|
|
17928
|
+
for (const h of handoffs) {
|
|
17929
|
+
if (h.workflow_link !== null && h.workflow_link.length > 0) {
|
|
17930
|
+
let bucket = linkedGroups.get(h.workflow_link);
|
|
17931
|
+
if (!bucket) {
|
|
17932
|
+
bucket = [];
|
|
17933
|
+
linkedGroups.set(h.workflow_link, bucket);
|
|
17934
|
+
}
|
|
17935
|
+
bucket.push(h);
|
|
17936
|
+
} else {
|
|
17937
|
+
unlinked.push(h);
|
|
17938
|
+
}
|
|
17939
|
+
}
|
|
17940
|
+
const sortedUnlinked = [...unlinked].sort(
|
|
17941
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
17942
|
+
);
|
|
17943
|
+
const heuristicChains = [];
|
|
17944
|
+
for (const h of sortedUnlinked) {
|
|
17945
|
+
const joinedIdx = findExtendableChain(heuristicChains, h);
|
|
17946
|
+
if (joinedIdx !== null) {
|
|
17947
|
+
heuristicChains[joinedIdx].push(h);
|
|
17948
|
+
} else {
|
|
17949
|
+
heuristicChains.push([h]);
|
|
17950
|
+
}
|
|
17951
|
+
}
|
|
17952
|
+
const workflows = [];
|
|
17953
|
+
for (const members of linkedGroups.values()) {
|
|
17954
|
+
workflows.push(materialize(members, now));
|
|
17955
|
+
}
|
|
17956
|
+
for (const members of heuristicChains) {
|
|
17957
|
+
workflows.push(materialize(members, now));
|
|
17958
|
+
}
|
|
17959
|
+
workflows.sort(
|
|
17960
|
+
(a, b) => a.last_activity_at < b.last_activity_at ? 1 : -1
|
|
17961
|
+
);
|
|
17962
|
+
return workflows;
|
|
17963
|
+
}
|
|
17964
|
+
function determineWorkflowState(members, now) {
|
|
17965
|
+
if (members.length === 0) return "unknown";
|
|
17966
|
+
const sorted = [...members].sort(
|
|
17967
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
17968
|
+
);
|
|
17969
|
+
const last = sorted[sorted.length - 1];
|
|
17970
|
+
const root = sorted[0];
|
|
17971
|
+
const lastMs = Date.parse(last.observed_at);
|
|
17972
|
+
if (!Number.isFinite(lastMs)) return "unknown";
|
|
17973
|
+
if (last.target_agent_id === OPERATOR_PSEUDO_AGENT) {
|
|
17974
|
+
return "completed";
|
|
17975
|
+
}
|
|
17976
|
+
if (sorted.length > CYCLE_COMPLETION_MIN_HOPS && last.target_agent_id === root.source_agent_id) {
|
|
17977
|
+
return "completed";
|
|
17978
|
+
}
|
|
17979
|
+
const ageMs = now.getTime() - lastMs;
|
|
17980
|
+
if (ageMs > STALL_THRESHOLD_MS) {
|
|
17981
|
+
return "stalled";
|
|
17982
|
+
}
|
|
17983
|
+
return "in_progress";
|
|
17984
|
+
}
|
|
17985
|
+
function workflowIdFromRoot(rootEntryId) {
|
|
17986
|
+
return createHash("sha256").update(`workflow:${rootEntryId}`).digest("hex").slice(0, 32);
|
|
17987
|
+
}
|
|
17988
|
+
function findExtendableChain(chains, h) {
|
|
17989
|
+
const hMs = Date.parse(h.observed_at);
|
|
17990
|
+
if (!Number.isFinite(hMs)) return null;
|
|
17991
|
+
let bestIdx = null;
|
|
17992
|
+
let bestGapMs = Number.POSITIVE_INFINITY;
|
|
17993
|
+
for (let i = 0; i < chains.length; i += 1) {
|
|
17994
|
+
const chain = chains[i];
|
|
17995
|
+
const last = chain[chain.length - 1];
|
|
17996
|
+
const lastMs = Date.parse(last.observed_at);
|
|
17997
|
+
if (!Number.isFinite(lastMs)) continue;
|
|
17998
|
+
const gapMs = Math.abs(hMs - lastMs);
|
|
17999
|
+
if (gapMs > HEURISTIC_WINDOW_MS) continue;
|
|
18000
|
+
if (!sharesAgent(last, h)) continue;
|
|
18001
|
+
if (gapMs < bestGapMs) {
|
|
18002
|
+
bestGapMs = gapMs;
|
|
18003
|
+
bestIdx = i;
|
|
18004
|
+
}
|
|
18005
|
+
}
|
|
18006
|
+
return bestIdx;
|
|
18007
|
+
}
|
|
18008
|
+
function sharesAgent(a, b) {
|
|
18009
|
+
return a.source_agent_id === b.source_agent_id || a.source_agent_id === b.target_agent_id || a.target_agent_id === b.source_agent_id || a.target_agent_id === b.target_agent_id;
|
|
18010
|
+
}
|
|
18011
|
+
function materialize(members, now) {
|
|
18012
|
+
const sorted = [...members].sort(
|
|
18013
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
18014
|
+
);
|
|
18015
|
+
const root = sorted[0];
|
|
18016
|
+
const last = sorted[sorted.length - 1];
|
|
18017
|
+
const involved = /* @__PURE__ */ new Set();
|
|
18018
|
+
for (const h of sorted) {
|
|
18019
|
+
if (h.source_agent_id) involved.add(h.source_agent_id);
|
|
18020
|
+
if (h.target_agent_id) involved.add(h.target_agent_id);
|
|
18021
|
+
}
|
|
18022
|
+
return {
|
|
18023
|
+
workflow_id: workflowIdFromRoot(root.entry_id),
|
|
18024
|
+
root_handoff: root,
|
|
18025
|
+
member_handoffs: sorted,
|
|
18026
|
+
state: determineWorkflowState(sorted, now),
|
|
18027
|
+
started_at: root.observed_at,
|
|
18028
|
+
last_activity_at: last.observed_at,
|
|
18029
|
+
involved_agents: [...involved].sort()
|
|
18030
|
+
};
|
|
18031
|
+
}
|
|
18032
|
+
var HEURISTIC_WINDOW_MS, STALL_THRESHOLD_MS, CYCLE_COMPLETION_MIN_HOPS;
|
|
18033
|
+
var init_workflow_grouper = __esm({
|
|
18034
|
+
"src/coordination/workflow-grouper.ts"() {
|
|
18035
|
+
init_handoff_log();
|
|
18036
|
+
HEURISTIC_WINDOW_MS = 5 * 60 * 1e3;
|
|
18037
|
+
STALL_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
|
|
18038
|
+
CYCLE_COMPLETION_MIN_HOPS = 2;
|
|
18039
|
+
}
|
|
18040
|
+
});
|
|
17901
18041
|
|
|
17902
18042
|
// src/coordination/handoff-routes.ts
|
|
17903
18043
|
function writeJSON6(res, status, payload) {
|
|
@@ -17921,6 +18061,101 @@ function matchEntryRoute2(path) {
|
|
|
17921
18061
|
if (rest.includes("/")) return null;
|
|
17922
18062
|
return { entryId: decodeURIComponent(rest) };
|
|
17923
18063
|
}
|
|
18064
|
+
function matchWorkflowRoute(path) {
|
|
18065
|
+
const prefix = `${COORDINATION_WORKFLOWS_PREFIX}/`;
|
|
18066
|
+
if (!path.startsWith(prefix)) return null;
|
|
18067
|
+
const rest = path.slice(prefix.length);
|
|
18068
|
+
if (rest.length === 0 || rest === "stream") return null;
|
|
18069
|
+
if (rest.includes("/")) return null;
|
|
18070
|
+
return { workflowId: decodeURIComponent(rest) };
|
|
18071
|
+
}
|
|
18072
|
+
async function computeWorkflowsAndTrackTransitions(deps) {
|
|
18073
|
+
const handoffs = await deps.handoffLog.query({ limit: 500 });
|
|
18074
|
+
const workflows = groupHandoffsIntoWorkflows(handoffs, {
|
|
18075
|
+
...deps.now !== void 0 ? { now: deps.now() } : {}
|
|
18076
|
+
});
|
|
18077
|
+
const transitions = deps.workflowStateTracker ? deps.workflowStateTracker.observe(workflows) : [];
|
|
18078
|
+
for (const change of transitions) {
|
|
18079
|
+
deps.auditLog.append(
|
|
18080
|
+
"l2",
|
|
18081
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_STATE_CHANGED,
|
|
18082
|
+
deps.operatorId,
|
|
18083
|
+
{
|
|
18084
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18085
|
+
workflow_id: change.workflow_id,
|
|
18086
|
+
previous_state: change.previous_state,
|
|
18087
|
+
new_state: change.new_state
|
|
18088
|
+
}
|
|
18089
|
+
);
|
|
18090
|
+
}
|
|
18091
|
+
return { workflows, transitions };
|
|
18092
|
+
}
|
|
18093
|
+
function filterWorkflowList(workflows, opts) {
|
|
18094
|
+
let filtered = workflows;
|
|
18095
|
+
if (opts.state) {
|
|
18096
|
+
filtered = filtered.filter((w) => w.state === opts.state);
|
|
18097
|
+
}
|
|
18098
|
+
if (opts.agentId) {
|
|
18099
|
+
filtered = filtered.filter((w) => w.involved_agents.includes(opts.agentId));
|
|
18100
|
+
}
|
|
18101
|
+
if (opts.since) {
|
|
18102
|
+
filtered = filtered.filter((w) => w.last_activity_at >= opts.since);
|
|
18103
|
+
}
|
|
18104
|
+
return filtered.slice(0, opts.limit);
|
|
18105
|
+
}
|
|
18106
|
+
function isWorkflowState(value) {
|
|
18107
|
+
return value === "in_progress" || value === "completed" || value === "stalled" || value === "unknown";
|
|
18108
|
+
}
|
|
18109
|
+
async function handleWorkflowStream(deps, res) {
|
|
18110
|
+
res.writeHead(200, {
|
|
18111
|
+
"Content-Type": "text/event-stream",
|
|
18112
|
+
"Cache-Control": "no-cache, no-transform",
|
|
18113
|
+
Connection: "keep-alive",
|
|
18114
|
+
"X-Accel-Buffering": "no"
|
|
18115
|
+
});
|
|
18116
|
+
const initial = await computeWorkflowsAndTrackTransitions(deps);
|
|
18117
|
+
res.write(
|
|
18118
|
+
`event: workflow_snapshot
|
|
18119
|
+
data: ${JSON.stringify({ workflows: initial.workflows })}
|
|
18120
|
+
|
|
18121
|
+
`
|
|
18122
|
+
);
|
|
18123
|
+
if (initial.transitions.length > 0) {
|
|
18124
|
+
res.write(
|
|
18125
|
+
`event: workflow_state_changed
|
|
18126
|
+
data: ${JSON.stringify({ transitions: initial.transitions })}
|
|
18127
|
+
|
|
18128
|
+
`
|
|
18129
|
+
);
|
|
18130
|
+
}
|
|
18131
|
+
const unsubscribe = deps.events.subscribe(() => {
|
|
18132
|
+
void (async () => {
|
|
18133
|
+
try {
|
|
18134
|
+
const tick = await computeWorkflowsAndTrackTransitions(deps);
|
|
18135
|
+
res.write(
|
|
18136
|
+
`event: workflow_snapshot
|
|
18137
|
+
data: ${JSON.stringify({ workflows: tick.workflows })}
|
|
18138
|
+
|
|
18139
|
+
`
|
|
18140
|
+
);
|
|
18141
|
+
if (tick.transitions.length > 0) {
|
|
18142
|
+
res.write(
|
|
18143
|
+
`event: workflow_state_changed
|
|
18144
|
+
data: ${JSON.stringify({ transitions: tick.transitions })}
|
|
18145
|
+
|
|
18146
|
+
`
|
|
18147
|
+
);
|
|
18148
|
+
}
|
|
18149
|
+
} catch {
|
|
18150
|
+
}
|
|
18151
|
+
})();
|
|
18152
|
+
});
|
|
18153
|
+
const cleanup = () => {
|
|
18154
|
+
unsubscribe();
|
|
18155
|
+
};
|
|
18156
|
+
res.on("close", cleanup);
|
|
18157
|
+
res.on("error", cleanup);
|
|
18158
|
+
}
|
|
17924
18159
|
async function handleStream3(deps, res) {
|
|
17925
18160
|
res.writeHead(200, {
|
|
17926
18161
|
"Content-Type": "text/event-stream",
|
|
@@ -18004,6 +18239,67 @@ async function handleCoordinationRoute(deps, req, res) {
|
|
|
18004
18239
|
writeJSON6(res, 200, { ok: true, data: { entries } });
|
|
18005
18240
|
return true;
|
|
18006
18241
|
}
|
|
18242
|
+
if (method === "GET" && path === `${COORDINATION_WORKFLOWS_PREFIX}/stream`) {
|
|
18243
|
+
await handleWorkflowStream(deps, res);
|
|
18244
|
+
return true;
|
|
18245
|
+
}
|
|
18246
|
+
if (method === "GET" && path === COORDINATION_WORKFLOWS_PREFIX) {
|
|
18247
|
+
const limit = parseLimit4(
|
|
18248
|
+
url.searchParams.get("limit"),
|
|
18249
|
+
COORDINATION_LIST_DEFAULT_LIMIT,
|
|
18250
|
+
COORDINATION_LIST_MAX_LIMIT
|
|
18251
|
+
);
|
|
18252
|
+
const rawState = url.searchParams.get("state");
|
|
18253
|
+
const state = rawState && isWorkflowState(rawState) ? rawState : void 0;
|
|
18254
|
+
const since = url.searchParams.get("since") ?? void 0;
|
|
18255
|
+
const agentId = url.searchParams.get("agent_id") ?? void 0;
|
|
18256
|
+
const computed = await computeWorkflowsAndTrackTransitions(deps);
|
|
18257
|
+
const filtered = filterWorkflowList(computed.workflows, {
|
|
18258
|
+
...state !== void 0 ? { state } : {},
|
|
18259
|
+
...agentId !== void 0 ? { agentId } : {},
|
|
18260
|
+
...since !== void 0 ? { since } : {},
|
|
18261
|
+
limit
|
|
18262
|
+
});
|
|
18263
|
+
deps.auditLog.append(
|
|
18264
|
+
"l2",
|
|
18265
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_VIEW_OPENED,
|
|
18266
|
+
deps.operatorId,
|
|
18267
|
+
{
|
|
18268
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18269
|
+
result_count: filtered.length,
|
|
18270
|
+
...state !== void 0 ? { state } : {},
|
|
18271
|
+
...agentId !== void 0 ? { agent_id: agentId } : {},
|
|
18272
|
+
...since !== void 0 ? { since } : {}
|
|
18273
|
+
}
|
|
18274
|
+
);
|
|
18275
|
+
writeJSON6(res, 200, { ok: true, data: { workflows: filtered } });
|
|
18276
|
+
return true;
|
|
18277
|
+
}
|
|
18278
|
+
const workflowMatch = matchWorkflowRoute(path);
|
|
18279
|
+
if (method === "GET" && workflowMatch) {
|
|
18280
|
+
const computed = await computeWorkflowsAndTrackTransitions(deps);
|
|
18281
|
+
const wf = computed.workflows.find(
|
|
18282
|
+
(w) => w.workflow_id === workflowMatch.workflowId
|
|
18283
|
+
);
|
|
18284
|
+
if (!wf) {
|
|
18285
|
+
writeJSON6(res, 404, { ok: false, error: "not_found" });
|
|
18286
|
+
return true;
|
|
18287
|
+
}
|
|
18288
|
+
deps.auditLog.append(
|
|
18289
|
+
"l2",
|
|
18290
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_DRILLED,
|
|
18291
|
+
deps.operatorId,
|
|
18292
|
+
{
|
|
18293
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18294
|
+
workflow_id: wf.workflow_id,
|
|
18295
|
+
state: wf.state,
|
|
18296
|
+
member_count: wf.member_handoffs.length,
|
|
18297
|
+
involved_agent_count: wf.involved_agents.length
|
|
18298
|
+
}
|
|
18299
|
+
);
|
|
18300
|
+
writeJSON6(res, 200, { ok: true, data: { workflow: wf } });
|
|
18301
|
+
return true;
|
|
18302
|
+
}
|
|
18007
18303
|
const entryMatch = matchEntryRoute2(path);
|
|
18008
18304
|
if (method === "GET" && entryMatch) {
|
|
18009
18305
|
const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
|
|
@@ -18056,14 +18352,16 @@ async function handleCoordinationRoute(deps, req, res) {
|
|
|
18056
18352
|
return true;
|
|
18057
18353
|
}
|
|
18058
18354
|
}
|
|
18059
|
-
var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
|
|
18355
|
+
var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_WORKFLOWS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
|
|
18060
18356
|
var init_handoff_routes = __esm({
|
|
18061
18357
|
"src/coordination/handoff-routes.ts"() {
|
|
18062
18358
|
init_auth_middleware();
|
|
18063
18359
|
init_handoff_log();
|
|
18064
18360
|
init_context_transfer_extractor();
|
|
18361
|
+
init_workflow_grouper();
|
|
18065
18362
|
COORDINATION_API_PREFIX = "/api/coordination";
|
|
18066
18363
|
COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
|
|
18364
|
+
COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
|
|
18067
18365
|
COORDINATION_LIST_DEFAULT_LIMIT = 50;
|
|
18068
18366
|
COORDINATION_LIST_MAX_LIMIT = 500;
|
|
18069
18367
|
HandoffEventBridge = class {
|
|
@@ -18182,6 +18480,8 @@ var init_dashboard = __esm({
|
|
|
18182
18480
|
*/
|
|
18183
18481
|
handoffLog = null;
|
|
18184
18482
|
handoffEventBridge = null;
|
|
18483
|
+
handoffContextTransfer = null;
|
|
18484
|
+
workflowStateTracker = null;
|
|
18185
18485
|
handoffAuditLog = null;
|
|
18186
18486
|
handoffOperatorId = null;
|
|
18187
18487
|
constructor(config) {
|
|
@@ -18262,6 +18562,8 @@ var init_dashboard = __esm({
|
|
|
18262
18562
|
this.handoffEventBridge = opts.eventBridge ?? null;
|
|
18263
18563
|
this.handoffAuditLog = opts.auditLog ?? null;
|
|
18264
18564
|
this.handoffOperatorId = opts.operatorId ?? null;
|
|
18565
|
+
this.handoffContextTransfer = opts.contextTransfer ?? null;
|
|
18566
|
+
this.workflowStateTracker = opts.workflowStateTracker ?? null;
|
|
18265
18567
|
}
|
|
18266
18568
|
/**
|
|
18267
18569
|
* v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
|
|
@@ -18319,7 +18621,9 @@ var init_dashboard = __esm({
|
|
|
18319
18621
|
handoffLog: this.handoffLog,
|
|
18320
18622
|
auditLog: this.handoffAuditLog,
|
|
18321
18623
|
operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
|
|
18322
|
-
events: this.handoffEventBridge
|
|
18624
|
+
events: this.handoffEventBridge,
|
|
18625
|
+
...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
|
|
18626
|
+
...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
|
|
18323
18627
|
},
|
|
18324
18628
|
req,
|
|
18325
18629
|
res
|
|
@@ -22602,11 +22906,117 @@ var init_sentinel_dispatcher = __esm({
|
|
|
22602
22906
|
};
|
|
22603
22907
|
}
|
|
22604
22908
|
});
|
|
22909
|
+
|
|
22910
|
+
// src/anomaly-detection/classifier-state-store.ts
|
|
22911
|
+
function stateKey(classifierId, agentId) {
|
|
22912
|
+
return `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.${agentId}`;
|
|
22913
|
+
}
|
|
22914
|
+
function aadFor(classifierId, agentId) {
|
|
22915
|
+
return `${classifierId}|${agentId}`;
|
|
22916
|
+
}
|
|
22917
|
+
var ANOMALY_CLASSIFIER_STATE_NAMESPACE, ANOMALY_CLASSIFIER_STATE_KEY_PREFIX, HKDF_INFO3, MAX_STATE_BYTES, ClassifierStateStore;
|
|
22605
22918
|
var init_classifier_state_store = __esm({
|
|
22606
22919
|
"src/anomaly-detection/classifier-state-store.ts"() {
|
|
22607
22920
|
init_encryption();
|
|
22608
22921
|
init_key_derivation();
|
|
22609
22922
|
init_encoding();
|
|
22923
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE = "_anomaly_classifier_state";
|
|
22924
|
+
ANOMALY_CLASSIFIER_STATE_KEY_PREFIX = "state.";
|
|
22925
|
+
HKDF_INFO3 = "l2-anomaly-classifier-state-v1";
|
|
22926
|
+
MAX_STATE_BYTES = 256 * 1024;
|
|
22927
|
+
ClassifierStateStore = class {
|
|
22928
|
+
storage;
|
|
22929
|
+
encryptionKey;
|
|
22930
|
+
fortressId;
|
|
22931
|
+
now;
|
|
22932
|
+
constructor(opts) {
|
|
22933
|
+
this.storage = opts.storage;
|
|
22934
|
+
this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
|
|
22935
|
+
this.fortressId = opts.fortressId;
|
|
22936
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
22937
|
+
}
|
|
22938
|
+
async saveState(classifierId, agentId, state) {
|
|
22939
|
+
const persisted = {
|
|
22940
|
+
version: 1,
|
|
22941
|
+
classifier_id: classifierId,
|
|
22942
|
+
agent_id: agentId,
|
|
22943
|
+
fortress_id: this.fortressId,
|
|
22944
|
+
saved_at: this.now().toISOString(),
|
|
22945
|
+
state
|
|
22946
|
+
};
|
|
22947
|
+
const aadString = aadFor(classifierId, agentId);
|
|
22948
|
+
const aad = stringToBytes(aadString);
|
|
22949
|
+
const plaintext = stringToBytes(JSON.stringify(persisted));
|
|
22950
|
+
const envelope = encrypt(plaintext, this.encryptionKey, aad);
|
|
22951
|
+
await this.storage.write(
|
|
22952
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22953
|
+
stateKey(classifierId, agentId),
|
|
22954
|
+
stringToBytes(JSON.stringify(envelope))
|
|
22955
|
+
);
|
|
22956
|
+
}
|
|
22957
|
+
async loadState(classifierId, agentId) {
|
|
22958
|
+
const key = stateKey(classifierId, agentId);
|
|
22959
|
+
let raw;
|
|
22960
|
+
try {
|
|
22961
|
+
raw = await this.storage.read(
|
|
22962
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22963
|
+
key
|
|
22964
|
+
);
|
|
22965
|
+
} catch {
|
|
22966
|
+
return null;
|
|
22967
|
+
}
|
|
22968
|
+
if (!raw) return null;
|
|
22969
|
+
if (raw.length > MAX_STATE_BYTES) return null;
|
|
22970
|
+
try {
|
|
22971
|
+
const aad = stringToBytes(aadFor(classifierId, agentId));
|
|
22972
|
+
const envelope = JSON.parse(bytesToString(raw));
|
|
22973
|
+
const plaintext = decrypt(envelope, this.encryptionKey, aad);
|
|
22974
|
+
const persisted = JSON.parse(
|
|
22975
|
+
bytesToString(plaintext)
|
|
22976
|
+
);
|
|
22977
|
+
if (persisted.version !== 1) return null;
|
|
22978
|
+
if (persisted.classifier_id !== classifierId) return null;
|
|
22979
|
+
if (persisted.agent_id !== agentId) return null;
|
|
22980
|
+
if (persisted.fortress_id !== this.fortressId) return null;
|
|
22981
|
+
return persisted.state;
|
|
22982
|
+
} catch {
|
|
22983
|
+
return null;
|
|
22984
|
+
}
|
|
22985
|
+
}
|
|
22986
|
+
/** Delete a single classifier-agent state record. */
|
|
22987
|
+
async deleteState(classifierId, agentId) {
|
|
22988
|
+
const key = stateKey(classifierId, agentId);
|
|
22989
|
+
const existed = await this.storage.exists(
|
|
22990
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22991
|
+
key
|
|
22992
|
+
);
|
|
22993
|
+
if (!existed) return false;
|
|
22994
|
+
try {
|
|
22995
|
+
await this.storage.delete(ANOMALY_CLASSIFIER_STATE_NAMESPACE, key);
|
|
22996
|
+
} catch {
|
|
22997
|
+
return false;
|
|
22998
|
+
}
|
|
22999
|
+
return true;
|
|
23000
|
+
}
|
|
23001
|
+
/**
|
|
23002
|
+
* List the (classifier_id, agent_id) tuples currently persisted.
|
|
23003
|
+
* Returns the agent ids for one classifier when classifierId is
|
|
23004
|
+
* given.
|
|
23005
|
+
*/
|
|
23006
|
+
async listAgents(classifierId) {
|
|
23007
|
+
const metas = await this.storage.list(
|
|
23008
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
23009
|
+
ANOMALY_CLASSIFIER_STATE_KEY_PREFIX
|
|
23010
|
+
);
|
|
23011
|
+
const prefix = `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.`;
|
|
23012
|
+
const out = [];
|
|
23013
|
+
for (const meta of metas) {
|
|
23014
|
+
if (!meta.key.startsWith(prefix)) continue;
|
|
23015
|
+
out.push(meta.key.slice(prefix.length));
|
|
23016
|
+
}
|
|
23017
|
+
return out;
|
|
23018
|
+
}
|
|
23019
|
+
};
|
|
22610
23020
|
}
|
|
22611
23021
|
});
|
|
22612
23022
|
|
|
@@ -22629,8 +23039,157 @@ var init_psi = __esm({
|
|
|
22629
23039
|
});
|
|
22630
23040
|
|
|
22631
23041
|
// src/anomaly-detection/types.ts
|
|
23042
|
+
function severityFromAnomalyScore(score) {
|
|
23043
|
+
if (!Number.isFinite(score)) return null;
|
|
23044
|
+
if (score < 1) return null;
|
|
23045
|
+
if (score < 3) return "info";
|
|
23046
|
+
if (score < 6) return "warn";
|
|
23047
|
+
return "alert";
|
|
23048
|
+
}
|
|
23049
|
+
function buildAnomalyFinding(detector, classifier, vector, prediction, severity) {
|
|
23050
|
+
const summary = formatAnomalySummary(
|
|
23051
|
+
detector,
|
|
23052
|
+
classifier,
|
|
23053
|
+
vector,
|
|
23054
|
+
prediction,
|
|
23055
|
+
severity
|
|
23056
|
+
);
|
|
23057
|
+
return {
|
|
23058
|
+
finding_id: "",
|
|
23059
|
+
sentinel_id: `${ANOMALY_SENTINEL_ID_PREFIX}${detector.detectorId}`,
|
|
23060
|
+
severity,
|
|
23061
|
+
summary,
|
|
23062
|
+
details: {
|
|
23063
|
+
detector_id: detector.detectorId,
|
|
23064
|
+
classifier_id: classifier.classifierId,
|
|
23065
|
+
anomaly_score: prediction.anomaly_score,
|
|
23066
|
+
window_label: vector.window_label,
|
|
23067
|
+
observed_features: vector.features,
|
|
23068
|
+
feature_contributions: prediction.feature_contributions,
|
|
23069
|
+
explanation: prediction.explanation
|
|
23070
|
+
},
|
|
23071
|
+
observed_at: vector.observed_at,
|
|
23072
|
+
agent_id: vector.agent_id,
|
|
23073
|
+
evidence_audit_ids: [],
|
|
23074
|
+
fortress_id: ""
|
|
23075
|
+
};
|
|
23076
|
+
}
|
|
23077
|
+
function formatAnomalySummary(detector, classifier, vector, prediction, severity) {
|
|
23078
|
+
const top = prediction.explanation.slice(0, 3).join("; ");
|
|
23079
|
+
return `${detector.detectorId}/${classifier.classifierId} ${severity}: agent ${vector.agent_id} drifted ${prediction.anomaly_score.toFixed(2)} sigma from baseline. Top contributors: ${top || "(none)"}.`;
|
|
23080
|
+
}
|
|
23081
|
+
var AnomalyDetector, ANOMALY_SENTINEL_ID_PREFIX;
|
|
22632
23082
|
var init_types4 = __esm({
|
|
22633
23083
|
"src/anomaly-detection/types.ts"() {
|
|
23084
|
+
AnomalyDetector = class {
|
|
23085
|
+
/**
|
|
23086
|
+
* Additional classifiers attached post-construction. Keyed by
|
|
23087
|
+
* classifierId so subscribe/unsubscribe is idempotent. Primary
|
|
23088
|
+
* `classifier` is NOT stored here.
|
|
23089
|
+
*/
|
|
23090
|
+
additionalClassifiers = /* @__PURE__ */ new Map();
|
|
23091
|
+
/**
|
|
23092
|
+
* Attach an additional classifier. Idempotent: a second call with
|
|
23093
|
+
* the same classifierId returns false. The primary classifier
|
|
23094
|
+
* cannot be re-attached as additional (returns false). The
|
|
23095
|
+
* dispatcher emits ANOMALY_CLASSIFIER_SUBSCRIBED on success.
|
|
23096
|
+
*/
|
|
23097
|
+
addClassifier(classifier) {
|
|
23098
|
+
if (classifier.classifierId === this.classifier.classifierId) return false;
|
|
23099
|
+
if (this.additionalClassifiers.has(classifier.classifierId)) return false;
|
|
23100
|
+
this.additionalClassifiers.set(classifier.classifierId, classifier);
|
|
23101
|
+
return true;
|
|
23102
|
+
}
|
|
23103
|
+
/**
|
|
23104
|
+
* Detach an additional classifier by id. Cannot remove the primary
|
|
23105
|
+
* (returns false). Returns true when an existing additional
|
|
23106
|
+
* classifier was removed. The dispatcher emits
|
|
23107
|
+
* ANOMALY_CLASSIFIER_UNSUBSCRIBED on success.
|
|
23108
|
+
*/
|
|
23109
|
+
removeClassifier(classifierId) {
|
|
23110
|
+
if (classifierId === this.classifier.classifierId) return false;
|
|
23111
|
+
return this.additionalClassifiers.delete(classifierId);
|
|
23112
|
+
}
|
|
23113
|
+
/** List every classifier id attached: primary first, then additionals. */
|
|
23114
|
+
listClassifierIds() {
|
|
23115
|
+
return [
|
|
23116
|
+
this.classifier.classifierId,
|
|
23117
|
+
...this.additionalClassifiers.keys()
|
|
23118
|
+
];
|
|
23119
|
+
}
|
|
23120
|
+
/**
|
|
23121
|
+
* Return every attached classifier: primary first, then additionals
|
|
23122
|
+
* in insertion order. Used by evaluate() and the dispatcher's train
|
|
23123
|
+
* + audit emission.
|
|
23124
|
+
*/
|
|
23125
|
+
getAllClassifiers() {
|
|
23126
|
+
return [this.classifier, ...this.additionalClassifiers.values()];
|
|
23127
|
+
}
|
|
23128
|
+
/**
|
|
23129
|
+
* Bind the detector to a fortress context. Default stores it on
|
|
23130
|
+
* `this`; subclasses with priming logic override.
|
|
23131
|
+
*/
|
|
23132
|
+
async subscribe(context) {
|
|
23133
|
+
this.context = context;
|
|
23134
|
+
}
|
|
23135
|
+
async unsubscribe() {
|
|
23136
|
+
this.context = void 0;
|
|
23137
|
+
this.additionalClassifiers.clear();
|
|
23138
|
+
}
|
|
23139
|
+
/**
|
|
23140
|
+
* One evaluation pass. Default impl: extract -> for each classifier
|
|
23141
|
+
* attached, predict (drift against that classifier's prior
|
|
23142
|
+
* baseline) -> observe (only when the prediction is in-baseline,
|
|
23143
|
+
* so outliers do not contaminate the rolling baseline and pull
|
|
23144
|
+
* future predictions toward themselves) -> emit findings above
|
|
23145
|
+
* threshold. Multi-classifier evaluation is per-classifier: each
|
|
23146
|
+
* decides independently whether to absorb or emit. Subclasses with
|
|
23147
|
+
* custom routing override.
|
|
23148
|
+
*
|
|
23149
|
+
* Predict-then-observe (with conditional observe) is the standard
|
|
23150
|
+
* online anomaly-detection pattern. Chi-1 spawn prompt called for
|
|
23151
|
+
* observe-then-predict; CTO call: changed to predict-then-observe
|
|
23152
|
+
* because observe-then-predict measures the sample against itself
|
|
23153
|
+
* after one-sample contamination, which is structurally incorrect
|
|
23154
|
+
* for drift detection. Chi-2 preserves that invariant on a per-
|
|
23155
|
+
* classifier basis (each classifier's observe is conditional on its
|
|
23156
|
+
* own predict result).
|
|
23157
|
+
*/
|
|
23158
|
+
async evaluate() {
|
|
23159
|
+
const ctx = this.requireContext();
|
|
23160
|
+
const vectors = await this.featureExtract(ctx);
|
|
23161
|
+
const findings = [];
|
|
23162
|
+
const classifiers = this.getAllClassifiers();
|
|
23163
|
+
for (const vector of vectors) {
|
|
23164
|
+
for (const classifier of classifiers) {
|
|
23165
|
+
const prediction = await classifier.predict(vector);
|
|
23166
|
+
if (!prediction.baseline_ready) {
|
|
23167
|
+
await classifier.observe(vector);
|
|
23168
|
+
continue;
|
|
23169
|
+
}
|
|
23170
|
+
const severity = severityFromAnomalyScore(prediction.anomaly_score);
|
|
23171
|
+
if (severity === null) {
|
|
23172
|
+
await classifier.observe(vector);
|
|
23173
|
+
continue;
|
|
23174
|
+
}
|
|
23175
|
+
findings.push(
|
|
23176
|
+
buildAnomalyFinding(this, classifier, vector, prediction, severity)
|
|
23177
|
+
);
|
|
23178
|
+
}
|
|
23179
|
+
}
|
|
23180
|
+
return findings;
|
|
23181
|
+
}
|
|
23182
|
+
context;
|
|
23183
|
+
requireContext() {
|
|
23184
|
+
if (!this.context) {
|
|
23185
|
+
throw new Error(
|
|
23186
|
+
`anomaly-detector ${this.detectorId}: evaluate() called before subscribe()`
|
|
23187
|
+
);
|
|
23188
|
+
}
|
|
23189
|
+
return this.context;
|
|
23190
|
+
}
|
|
23191
|
+
};
|
|
23192
|
+
ANOMALY_SENTINEL_ID_PREFIX = "anomaly:";
|
|
22634
23193
|
}
|
|
22635
23194
|
});
|
|
22636
23195
|
function classifierSpecificAuditOp(classifierId) {
|
|
@@ -22942,6 +23501,75 @@ var init_anomaly_pipeline = __esm({
|
|
|
22942
23501
|
}
|
|
22943
23502
|
});
|
|
22944
23503
|
|
|
23504
|
+
// src/coordination/workflow-state-tracker.ts
|
|
23505
|
+
var WorkflowStateTracker;
|
|
23506
|
+
var init_workflow_state_tracker = __esm({
|
|
23507
|
+
"src/coordination/workflow-state-tracker.ts"() {
|
|
23508
|
+
WorkflowStateTracker = class {
|
|
23509
|
+
states = /* @__PURE__ */ new Map();
|
|
23510
|
+
now;
|
|
23511
|
+
constructor(opts) {
|
|
23512
|
+
this.now = opts?.now ?? (() => /* @__PURE__ */ new Date());
|
|
23513
|
+
}
|
|
23514
|
+
/**
|
|
23515
|
+
* Diff the supplied workflow list against the last-observed states.
|
|
23516
|
+
* Returns the set of transitions detected this call; the tracker
|
|
23517
|
+
* mutates its internal map to reflect the new states.
|
|
23518
|
+
*
|
|
23519
|
+
* Transitions emitted:
|
|
23520
|
+
* - First observation of a workflow (`previous_state` is the
|
|
23521
|
+
* sentinel `unobserved`). Lets the route handler audit-emit
|
|
23522
|
+
* the initial state so the operator sees workflows as they
|
|
23523
|
+
* surface, not only when they change.
|
|
23524
|
+
* - Subsequent observation where `previous_state !== new_state`.
|
|
23525
|
+
*/
|
|
23526
|
+
observe(workflows) {
|
|
23527
|
+
const out = [];
|
|
23528
|
+
const observedAt = this.now().toISOString();
|
|
23529
|
+
for (const wf of workflows) {
|
|
23530
|
+
const prior = this.states.get(wf.workflow_id);
|
|
23531
|
+
if (prior === void 0) {
|
|
23532
|
+
out.push({
|
|
23533
|
+
workflow_id: wf.workflow_id,
|
|
23534
|
+
previous_state: "unobserved",
|
|
23535
|
+
new_state: wf.state,
|
|
23536
|
+
observed_at: observedAt
|
|
23537
|
+
});
|
|
23538
|
+
this.states.set(wf.workflow_id, wf.state);
|
|
23539
|
+
continue;
|
|
23540
|
+
}
|
|
23541
|
+
if (prior !== wf.state) {
|
|
23542
|
+
out.push({
|
|
23543
|
+
workflow_id: wf.workflow_id,
|
|
23544
|
+
previous_state: prior,
|
|
23545
|
+
new_state: wf.state,
|
|
23546
|
+
observed_at: observedAt
|
|
23547
|
+
});
|
|
23548
|
+
this.states.set(wf.workflow_id, wf.state);
|
|
23549
|
+
}
|
|
23550
|
+
}
|
|
23551
|
+
return out;
|
|
23552
|
+
}
|
|
23553
|
+
/**
|
|
23554
|
+
* Drop a workflow's recorded state. Surfaced for tests + future
|
|
23555
|
+
* "operator dismissed this workflow" affordance; not currently
|
|
23556
|
+
* called by the production wiring.
|
|
23557
|
+
*/
|
|
23558
|
+
forget(workflowId) {
|
|
23559
|
+
this.states.delete(workflowId);
|
|
23560
|
+
}
|
|
23561
|
+
/** Reset the tracker. Tests use this between runs. */
|
|
23562
|
+
reset() {
|
|
23563
|
+
this.states.clear();
|
|
23564
|
+
}
|
|
23565
|
+
/** Read-only view of the current snapshot. Useful for diagnostics. */
|
|
23566
|
+
snapshot() {
|
|
23567
|
+
return new Map(this.states);
|
|
23568
|
+
}
|
|
23569
|
+
};
|
|
23570
|
+
}
|
|
23571
|
+
});
|
|
23572
|
+
|
|
22945
23573
|
// src/sentinel/sentinel.ts
|
|
22946
23574
|
var Sentinel;
|
|
22947
23575
|
var init_sentinel = __esm({
|
|
@@ -38653,7 +39281,7 @@ ${runningLines.join("\n")}`;
|
|
|
38653
39281
|
function chatStorageKey(surface, threadKey) {
|
|
38654
39282
|
return `${surface}.${threadKey}`;
|
|
38655
39283
|
}
|
|
38656
|
-
var OPERATOR_CHAT_NAMESPACE,
|
|
39284
|
+
var OPERATOR_CHAT_NAMESPACE, HKDF_INFO4, OperatorChatStore;
|
|
38657
39285
|
var init_operator_chat_store = __esm({
|
|
38658
39286
|
"src/chat/operator-chat-store.ts"() {
|
|
38659
39287
|
init_encryption();
|
|
@@ -38661,13 +39289,13 @@ var init_operator_chat_store = __esm({
|
|
|
38661
39289
|
init_encoding();
|
|
38662
39290
|
init_operator_chat_types();
|
|
38663
39291
|
OPERATOR_CHAT_NAMESPACE = "_chat";
|
|
38664
|
-
|
|
39292
|
+
HKDF_INFO4 = "operator-chat-store-v1";
|
|
38665
39293
|
OperatorChatStore = class {
|
|
38666
39294
|
storage;
|
|
38667
39295
|
encryptionKey;
|
|
38668
39296
|
constructor(storage, masterKey) {
|
|
38669
39297
|
this.storage = storage;
|
|
38670
|
-
this.encryptionKey = derivePurposeKey(masterKey,
|
|
39298
|
+
this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
|
|
38671
39299
|
}
|
|
38672
39300
|
/**
|
|
38673
39301
|
* Load a thread. Returns null if no record exists or if the on-disk
|
|
@@ -38763,7 +39391,7 @@ function lastTurnId(bundle) {
|
|
|
38763
39391
|
}
|
|
38764
39392
|
return max;
|
|
38765
39393
|
}
|
|
38766
|
-
var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX,
|
|
39394
|
+
var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO5, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
|
|
38767
39395
|
var init_concierge_memory_store = __esm({
|
|
38768
39396
|
"src/chat/concierge-memory-store.ts"() {
|
|
38769
39397
|
init_encryption();
|
|
@@ -38771,7 +39399,7 @@ var init_concierge_memory_store = __esm({
|
|
|
38771
39399
|
init_encoding();
|
|
38772
39400
|
CONCIERGE_MEMORY_NAMESPACE = "_chat";
|
|
38773
39401
|
CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
|
|
38774
|
-
|
|
39402
|
+
HKDF_INFO5 = "concierge-memory-store-v1";
|
|
38775
39403
|
DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
|
|
38776
39404
|
MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
|
|
38777
39405
|
ConciergeMemoryStore = class {
|
|
@@ -38782,7 +39410,7 @@ var init_concierge_memory_store = __esm({
|
|
|
38782
39410
|
locks;
|
|
38783
39411
|
constructor(opts) {
|
|
38784
39412
|
this.storage = opts.storage;
|
|
38785
|
-
this.encryptionKey = derivePurposeKey(opts.masterKey,
|
|
39413
|
+
this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO5);
|
|
38786
39414
|
this.fortressId = opts.fortressId;
|
|
38787
39415
|
this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
|
|
38788
39416
|
this.locks = /* @__PURE__ */ new Map();
|
|
@@ -39431,7 +40059,7 @@ var init_defaults = __esm({
|
|
|
39431
40059
|
});
|
|
39432
40060
|
|
|
39433
40061
|
// src/intelligence/policy-store.ts
|
|
39434
|
-
var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY,
|
|
40062
|
+
var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO6, IntelligenceConfigStore;
|
|
39435
40063
|
var init_policy_store = __esm({
|
|
39436
40064
|
"src/intelligence/policy-store.ts"() {
|
|
39437
40065
|
init_encryption();
|
|
@@ -39440,13 +40068,13 @@ var init_policy_store = __esm({
|
|
|
39440
40068
|
init_defaults();
|
|
39441
40069
|
INTELLIGENCE_NAMESPACE = "_intelligence";
|
|
39442
40070
|
SUBSTRATE_CONFIG_KEY = "substrate-config";
|
|
39443
|
-
|
|
40071
|
+
HKDF_INFO6 = "intelligence-substrate-config";
|
|
39444
40072
|
IntelligenceConfigStore = class {
|
|
39445
40073
|
storage;
|
|
39446
40074
|
encryptionKey;
|
|
39447
40075
|
constructor(storage, masterKey) {
|
|
39448
40076
|
this.storage = storage;
|
|
39449
|
-
this.encryptionKey = derivePurposeKey(masterKey,
|
|
40077
|
+
this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO6);
|
|
39450
40078
|
}
|
|
39451
40079
|
/**
|
|
39452
40080
|
* Load the operator's substrate config from disk. Returns the config
|
|
@@ -41261,6 +41889,247 @@ var init_constants5 = __esm({
|
|
|
41261
41889
|
];
|
|
41262
41890
|
}
|
|
41263
41891
|
});
|
|
41892
|
+
async function issueDidWeb(opts) {
|
|
41893
|
+
if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
|
|
41894
|
+
throw new Error(
|
|
41895
|
+
`did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
|
|
41896
|
+
);
|
|
41897
|
+
}
|
|
41898
|
+
if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
|
|
41899
|
+
throw new Error(
|
|
41900
|
+
`did-web: fortress_id '${opts.fortress_id}' is not a valid label`
|
|
41901
|
+
);
|
|
41902
|
+
}
|
|
41903
|
+
if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
|
|
41904
|
+
throw new Error(
|
|
41905
|
+
`did-web: agent_label '${opts.agent_label}' is not a valid label`
|
|
41906
|
+
);
|
|
41907
|
+
}
|
|
41908
|
+
if (opts.public_key.length !== 32) {
|
|
41909
|
+
throw new Error(
|
|
41910
|
+
`did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
|
|
41911
|
+
);
|
|
41912
|
+
}
|
|
41913
|
+
const did = buildDid(opts);
|
|
41914
|
+
const verificationMethodId = `${did}#key-1`;
|
|
41915
|
+
const verificationMethod = {
|
|
41916
|
+
id: verificationMethodId,
|
|
41917
|
+
type: "JsonWebKey2020",
|
|
41918
|
+
controller: did,
|
|
41919
|
+
publicKeyJwk: {
|
|
41920
|
+
kty: "OKP",
|
|
41921
|
+
crv: "Ed25519",
|
|
41922
|
+
x: toBase64url(opts.public_key)
|
|
41923
|
+
}
|
|
41924
|
+
};
|
|
41925
|
+
const didDocument = {
|
|
41926
|
+
"@context": [...DID_CONTEXT],
|
|
41927
|
+
id: did,
|
|
41928
|
+
verificationMethod: [verificationMethod],
|
|
41929
|
+
authentication: [verificationMethodId],
|
|
41930
|
+
assertionMethod: [verificationMethodId]
|
|
41931
|
+
};
|
|
41932
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
41933
|
+
return {
|
|
41934
|
+
did,
|
|
41935
|
+
did_document: didDocument,
|
|
41936
|
+
public_key: opts.public_key,
|
|
41937
|
+
created_at: now.toISOString(),
|
|
41938
|
+
authority_host: opts.authority_host,
|
|
41939
|
+
fortress_id: opts.fortress_id,
|
|
41940
|
+
...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
|
|
41941
|
+
};
|
|
41942
|
+
}
|
|
41943
|
+
function publishDidWebDocument(identifier, opts = {}) {
|
|
41944
|
+
const path = opts.publish_path ?? canonicalPublishPath(identifier);
|
|
41945
|
+
const artifact = canonicalSerializeDidDocument(identifier.did_document);
|
|
41946
|
+
const digest = sha256(stringToBytes(artifact));
|
|
41947
|
+
const url = `https://${identifier.authority_host}${path}`;
|
|
41948
|
+
return {
|
|
41949
|
+
url,
|
|
41950
|
+
publish_path: path,
|
|
41951
|
+
artifact,
|
|
41952
|
+
sha256: hashToString(digest)
|
|
41953
|
+
};
|
|
41954
|
+
}
|
|
41955
|
+
async function resolveDidWeb(did, opts) {
|
|
41956
|
+
const parsed = parseDidWeb(did);
|
|
41957
|
+
const url = didToUrl(parsed);
|
|
41958
|
+
if (!opts.allowed_hosts.includes(parsed.authority_host)) {
|
|
41959
|
+
return {
|
|
41960
|
+
ok: false,
|
|
41961
|
+
failure: "host_not_allowed",
|
|
41962
|
+
message: `did-web: authority_host '${parsed.authority_host}' is not in the operator's allowed_hosts allowlist; resolution refused (no-outbound-by-default)`,
|
|
41963
|
+
url
|
|
41964
|
+
};
|
|
41965
|
+
}
|
|
41966
|
+
const timeoutMs = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
|
|
41967
|
+
const fetcher = opts.fetcher ?? defaultFetcher;
|
|
41968
|
+
const controller = new AbortController();
|
|
41969
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41970
|
+
let response;
|
|
41971
|
+
try {
|
|
41972
|
+
response = await fetcher(url, { signal: controller.signal });
|
|
41973
|
+
} catch (err) {
|
|
41974
|
+
clearTimeout(timer);
|
|
41975
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
41976
|
+
if (controller.signal.aborted) {
|
|
41977
|
+
return {
|
|
41978
|
+
ok: false,
|
|
41979
|
+
failure: "timeout",
|
|
41980
|
+
message: `did-web: resolution exceeded ${timeoutMs}ms`,
|
|
41981
|
+
url
|
|
41982
|
+
};
|
|
41983
|
+
}
|
|
41984
|
+
return {
|
|
41985
|
+
ok: false,
|
|
41986
|
+
failure: "fetch_failed",
|
|
41987
|
+
message: `did-web: fetch error: ${message}`,
|
|
41988
|
+
url
|
|
41989
|
+
};
|
|
41990
|
+
}
|
|
41991
|
+
clearTimeout(timer);
|
|
41992
|
+
if (response.status === 404) {
|
|
41993
|
+
return {
|
|
41994
|
+
ok: false,
|
|
41995
|
+
failure: "not_found",
|
|
41996
|
+
message: `did-web: 404 from authority host`,
|
|
41997
|
+
url
|
|
41998
|
+
};
|
|
41999
|
+
}
|
|
42000
|
+
if (!response.ok) {
|
|
42001
|
+
return {
|
|
42002
|
+
ok: false,
|
|
42003
|
+
failure: "fetch_failed",
|
|
42004
|
+
message: `did-web: authority host returned ${response.status}`,
|
|
42005
|
+
url
|
|
42006
|
+
};
|
|
42007
|
+
}
|
|
42008
|
+
let body;
|
|
42009
|
+
try {
|
|
42010
|
+
body = await response.json();
|
|
42011
|
+
} catch (err) {
|
|
42012
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42013
|
+
return {
|
|
42014
|
+
ok: false,
|
|
42015
|
+
failure: "invalid_json",
|
|
42016
|
+
message: `did-web: invalid JSON: ${message}`,
|
|
42017
|
+
url
|
|
42018
|
+
};
|
|
42019
|
+
}
|
|
42020
|
+
if (!isDidDocument(body, did)) {
|
|
42021
|
+
return {
|
|
42022
|
+
ok: false,
|
|
42023
|
+
failure: "invalid_json",
|
|
42024
|
+
message: `did-web: response body is not a valid DID Document for ${did}`,
|
|
42025
|
+
url
|
|
42026
|
+
};
|
|
42027
|
+
}
|
|
42028
|
+
if (opts.expected_public_key !== void 0) {
|
|
42029
|
+
const expectedX = toBase64url(opts.expected_public_key);
|
|
42030
|
+
const actualX = body.verificationMethod[0]?.publicKeyJwk.x;
|
|
42031
|
+
if (actualX !== expectedX) {
|
|
42032
|
+
return {
|
|
42033
|
+
ok: false,
|
|
42034
|
+
failure: "signature_mismatch",
|
|
42035
|
+
message: `did-web: verificationMethod public key does not match expected key`,
|
|
42036
|
+
url
|
|
42037
|
+
};
|
|
42038
|
+
}
|
|
42039
|
+
}
|
|
42040
|
+
return { ok: true, did_document: body, url };
|
|
42041
|
+
}
|
|
42042
|
+
function parseDidWeb(did) {
|
|
42043
|
+
if (!did.startsWith("did:web:")) {
|
|
42044
|
+
throw new Error(`did-web: '${did}' is not a did:web identifier`);
|
|
42045
|
+
}
|
|
42046
|
+
const rest = did.slice("did:web:".length);
|
|
42047
|
+
const segments = rest.split(":");
|
|
42048
|
+
const authorityHost = segments[0];
|
|
42049
|
+
if (!HOST_RE.test(authorityHost)) {
|
|
42050
|
+
throw new Error(`did-web: '${authorityHost}' is not a valid DNS host`);
|
|
42051
|
+
}
|
|
42052
|
+
const parsed = { authority_host: authorityHost };
|
|
42053
|
+
if (segments.length === 1) return parsed;
|
|
42054
|
+
if (segments.length === 5 && segments[1] === "fortress" && segments[3] === "agent") {
|
|
42055
|
+
parsed.fortress_id = segments[2];
|
|
42056
|
+
parsed.agent_label = segments[4];
|
|
42057
|
+
return parsed;
|
|
42058
|
+
}
|
|
42059
|
+
throw new Error(
|
|
42060
|
+
`did-web: '${did}' does not match the supported shapes (bare did:web:<host> or did:web:<host>:fortress:<fid>:agent:<alabel>)`
|
|
42061
|
+
);
|
|
42062
|
+
}
|
|
42063
|
+
function didToUrl(parsed) {
|
|
42064
|
+
if (parsed.fortress_id === void 0 || parsed.agent_label === void 0) {
|
|
42065
|
+
return `https://${parsed.authority_host}/.well-known/did.json`;
|
|
42066
|
+
}
|
|
42067
|
+
return `https://${parsed.authority_host}/fortress/${parsed.fortress_id}/agent/${parsed.agent_label}/did.json`;
|
|
42068
|
+
}
|
|
42069
|
+
function buildDid(opts) {
|
|
42070
|
+
if (opts.agent_label === void 0) {
|
|
42071
|
+
return `did:web:${opts.authority_host}`;
|
|
42072
|
+
}
|
|
42073
|
+
return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
|
|
42074
|
+
}
|
|
42075
|
+
function canonicalPublishPath(identifier) {
|
|
42076
|
+
if (identifier.agent_label === void 0) {
|
|
42077
|
+
return "/.well-known/did.json";
|
|
42078
|
+
}
|
|
42079
|
+
return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
|
|
42080
|
+
}
|
|
42081
|
+
function canonicalSerializeDidDocument(doc) {
|
|
42082
|
+
return JSON.stringify(
|
|
42083
|
+
{
|
|
42084
|
+
"@context": doc["@context"],
|
|
42085
|
+
id: doc.id,
|
|
42086
|
+
verificationMethod: doc.verificationMethod,
|
|
42087
|
+
authentication: doc.authentication,
|
|
42088
|
+
assertionMethod: doc.assertionMethod
|
|
42089
|
+
},
|
|
42090
|
+
null,
|
|
42091
|
+
2
|
|
42092
|
+
);
|
|
42093
|
+
}
|
|
42094
|
+
function isDidDocument(value, expectedDid) {
|
|
42095
|
+
if (!value || typeof value !== "object") return false;
|
|
42096
|
+
const v = value;
|
|
42097
|
+
if (v["id"] !== expectedDid) return false;
|
|
42098
|
+
if (!Array.isArray(v["@context"])) return false;
|
|
42099
|
+
const vm = v["verificationMethod"];
|
|
42100
|
+
if (!Array.isArray(vm) || vm.length === 0) return false;
|
|
42101
|
+
const first = vm[0];
|
|
42102
|
+
if (!first || typeof first["id"] !== "string") return false;
|
|
42103
|
+
const jwk = first["publicKeyJwk"];
|
|
42104
|
+
if (!jwk || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519") return false;
|
|
42105
|
+
if (typeof jwk["x"] !== "string") return false;
|
|
42106
|
+
if (!Array.isArray(v["authentication"])) return false;
|
|
42107
|
+
if (!Array.isArray(v["assertionMethod"])) return false;
|
|
42108
|
+
return true;
|
|
42109
|
+
}
|
|
42110
|
+
async function defaultFetcher(url, init) {
|
|
42111
|
+
const response = await fetch(url, init);
|
|
42112
|
+
return {
|
|
42113
|
+
ok: response.ok,
|
|
42114
|
+
status: response.status,
|
|
42115
|
+
json: () => response.json()
|
|
42116
|
+
};
|
|
42117
|
+
}
|
|
42118
|
+
var DID_CONTEXT, DEFAULT_TIMEOUT_MS4, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
|
|
42119
|
+
var init_did_web = __esm({
|
|
42120
|
+
"src/recognition/did-web.ts"() {
|
|
42121
|
+
init_encoding();
|
|
42122
|
+
init_hashing();
|
|
42123
|
+
DID_CONTEXT = [
|
|
42124
|
+
"https://www.w3.org/ns/did/v1",
|
|
42125
|
+
"https://w3id.org/security/suites/jws-2020/v1"
|
|
42126
|
+
];
|
|
42127
|
+
DEFAULT_TIMEOUT_MS4 = 5e3;
|
|
42128
|
+
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;
|
|
42129
|
+
FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
42130
|
+
AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
42131
|
+
}
|
|
42132
|
+
});
|
|
41264
42133
|
|
|
41265
42134
|
// src/contracts/v1.1/exit-bundle-manifest.ts
|
|
41266
42135
|
var EXIT_BUNDLE_PATH_PATTERN, EXIT_BUNDLE_PATH_MAX_BYTES;
|
|
@@ -41964,6 +42833,7 @@ async function exportExitBundle(opts) {
|
|
|
41964
42833
|
"placeholder_vault_metadata"
|
|
41965
42834
|
)
|
|
41966
42835
|
);
|
|
42836
|
+
const didWebBinding = validateExportDidWeb(opts.didWeb);
|
|
41967
42837
|
const body = {
|
|
41968
42838
|
manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
|
|
41969
42839
|
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -41971,7 +42841,8 @@ async function exportExitBundle(opts) {
|
|
|
41971
42841
|
identity_id: identity.identity_id,
|
|
41972
42842
|
fortress_id: identity.did,
|
|
41973
42843
|
fortress_master_pubkey: identity.public_key,
|
|
41974
|
-
did: identity.did
|
|
42844
|
+
did: identity.did,
|
|
42845
|
+
...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
|
|
41975
42846
|
},
|
|
41976
42847
|
source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
|
|
41977
42848
|
artifacts,
|
|
@@ -41993,6 +42864,18 @@ async function exportExitBundle(opts) {
|
|
|
41993
42864
|
};
|
|
41994
42865
|
const manifestBytes = jsonBytes(manifest);
|
|
41995
42866
|
await writeFile(join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
|
|
42867
|
+
if (didWebBinding !== void 0) {
|
|
42868
|
+
opts.auditLog.append(
|
|
42869
|
+
"l1",
|
|
42870
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.EXPORT_INCLUDED,
|
|
42871
|
+
identity.identity_id,
|
|
42872
|
+
{
|
|
42873
|
+
approval_id: exportApprovalAuditId,
|
|
42874
|
+
identifier: didWebBinding.identifier,
|
|
42875
|
+
authority_host: didWebBinding.authority_host
|
|
42876
|
+
}
|
|
42877
|
+
);
|
|
42878
|
+
}
|
|
41996
42879
|
await opts.auditLog.flush();
|
|
41997
42880
|
return {
|
|
41998
42881
|
bundle_dir: bundleDir,
|
|
@@ -42004,6 +42887,30 @@ async function exportExitBundle(opts) {
|
|
|
42004
42887
|
]
|
|
42005
42888
|
};
|
|
42006
42889
|
}
|
|
42890
|
+
function validateExportDidWeb(binding) {
|
|
42891
|
+
if (binding === void 0) return void 0;
|
|
42892
|
+
if (!binding.identifier || typeof binding.identifier !== "string") {
|
|
42893
|
+
throw new Error(
|
|
42894
|
+
"exit-bundle: did_web.identifier must be a non-empty did:web URI"
|
|
42895
|
+
);
|
|
42896
|
+
}
|
|
42897
|
+
if (!binding.authority_host || typeof binding.authority_host !== "string") {
|
|
42898
|
+
throw new Error(
|
|
42899
|
+
"exit-bundle: did_web.authority_host must be a non-empty DNS host"
|
|
42900
|
+
);
|
|
42901
|
+
}
|
|
42902
|
+
const parsed = parseDidWeb(binding.identifier);
|
|
42903
|
+
if (parsed.authority_host.toLowerCase() !== binding.authority_host.toLowerCase()) {
|
|
42904
|
+
throw new Error(
|
|
42905
|
+
`exit-bundle: did_web.identifier authority host '${parsed.authority_host}' does not match did_web.authority_host '${binding.authority_host}'`
|
|
42906
|
+
);
|
|
42907
|
+
}
|
|
42908
|
+
return {
|
|
42909
|
+
identifier: binding.identifier,
|
|
42910
|
+
authority_host: binding.authority_host,
|
|
42911
|
+
...binding.published_at !== void 0 ? { published_at: binding.published_at } : {}
|
|
42912
|
+
};
|
|
42913
|
+
}
|
|
42007
42914
|
function publicKeysFromIdentityArtifact(identityArtifact) {
|
|
42008
42915
|
const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
|
|
42009
42916
|
return {
|
|
@@ -42218,6 +43125,87 @@ async function importExitBundle(opts) {
|
|
|
42218
43125
|
};
|
|
42219
43126
|
}
|
|
42220
43127
|
const manifest = await readManifest(opts.bundleDir);
|
|
43128
|
+
const importWarnings = [];
|
|
43129
|
+
const manifestDidWeb = manifest.body.identity_binding.did_web;
|
|
43130
|
+
if (manifestDidWeb !== void 0 && !opts.skipDidWebVerify) {
|
|
43131
|
+
const expectedPublicKey = fromBase64url(
|
|
43132
|
+
manifest.body.identity_binding.fortress_master_pubkey
|
|
43133
|
+
);
|
|
43134
|
+
const resolveOpts = {
|
|
43135
|
+
allowed_hosts: opts.didWebAllowedHosts ?? [],
|
|
43136
|
+
expected_public_key: expectedPublicKey,
|
|
43137
|
+
...opts.didWebFetcher !== void 0 ? { fetcher: opts.didWebFetcher } : {},
|
|
43138
|
+
...opts.didWebTimeoutMs !== void 0 ? { timeout_ms: opts.didWebTimeoutMs } : {}
|
|
43139
|
+
};
|
|
43140
|
+
const resolution = await resolveDidWeb(
|
|
43141
|
+
manifestDidWeb.identifier,
|
|
43142
|
+
resolveOpts
|
|
43143
|
+
);
|
|
43144
|
+
const authorityHost = manifestDidWeb.authority_host;
|
|
43145
|
+
opts.auditLog.append(
|
|
43146
|
+
"l1",
|
|
43147
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.AUTHORITY_HOST,
|
|
43148
|
+
manifest.body.identity_binding.identity_id,
|
|
43149
|
+
{ authority_host: authorityHost, identifier: manifestDidWeb.identifier }
|
|
43150
|
+
);
|
|
43151
|
+
if (resolution.ok) {
|
|
43152
|
+
opts.auditLog.append(
|
|
43153
|
+
"l1",
|
|
43154
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43155
|
+
manifest.body.identity_binding.identity_id,
|
|
43156
|
+
{
|
|
43157
|
+
outcome: "success",
|
|
43158
|
+
identifier: manifestDidWeb.identifier,
|
|
43159
|
+
authority_host: authorityHost,
|
|
43160
|
+
resolved_url: resolution.url
|
|
43161
|
+
}
|
|
43162
|
+
);
|
|
43163
|
+
} else if (resolution.failure === "signature_mismatch") {
|
|
43164
|
+
opts.auditLog.append(
|
|
43165
|
+
"l1",
|
|
43166
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43167
|
+
manifest.body.identity_binding.identity_id,
|
|
43168
|
+
{
|
|
43169
|
+
outcome: "mismatch",
|
|
43170
|
+
identifier: manifestDidWeb.identifier,
|
|
43171
|
+
authority_host: authorityHost,
|
|
43172
|
+
resolved_url: resolution.url
|
|
43173
|
+
}
|
|
43174
|
+
);
|
|
43175
|
+
await opts.auditLog.flush();
|
|
43176
|
+
throw new ExitBundleImportError(
|
|
43177
|
+
"did_web_mismatch",
|
|
43178
|
+
`did:web cross-check failed: the DID Document at ${resolution.url} resolved successfully, but the verificationMethod public key did not match the manifest's claimed fortress_master_pubkey. The bundle's claimed origin (${manifestDidWeb.identifier}) is inconsistent with the published DID Document. To proceed anyway with the manifest signature alone, re-run import with --skip-did-web-verify.`
|
|
43179
|
+
);
|
|
43180
|
+
} else {
|
|
43181
|
+
opts.auditLog.append(
|
|
43182
|
+
"l1",
|
|
43183
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43184
|
+
manifest.body.identity_binding.identity_id,
|
|
43185
|
+
{
|
|
43186
|
+
outcome: "resolution_failure",
|
|
43187
|
+
failure: resolution.failure,
|
|
43188
|
+
identifier: manifestDidWeb.identifier,
|
|
43189
|
+
authority_host: authorityHost,
|
|
43190
|
+
resolved_url: resolution.url
|
|
43191
|
+
}
|
|
43192
|
+
);
|
|
43193
|
+
importWarnings.push(
|
|
43194
|
+
`did:web resolution failed (${resolution.failure}): ${resolution.message}. Import proceeded with manifest-signature verification alone; recognition-layer cross-check was skipped. Re-run with --did-web-allowed-host=<host> to enable resolution, or --skip-did-web-verify to skip deliberately.`
|
|
43195
|
+
);
|
|
43196
|
+
}
|
|
43197
|
+
} else if (manifestDidWeb !== void 0 && opts.skipDidWebVerify) {
|
|
43198
|
+
opts.auditLog.append(
|
|
43199
|
+
"l1",
|
|
43200
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43201
|
+
manifest.body.identity_binding.identity_id,
|
|
43202
|
+
{
|
|
43203
|
+
outcome: "skipped",
|
|
43204
|
+
identifier: manifestDidWeb.identifier,
|
|
43205
|
+
authority_host: manifestDidWeb.authority_host
|
|
43206
|
+
}
|
|
43207
|
+
);
|
|
43208
|
+
}
|
|
42221
43209
|
const identityArtifact = await loadExitArtifact(
|
|
42222
43210
|
opts.bundleDir,
|
|
42223
43211
|
manifest,
|
|
@@ -42282,7 +43270,7 @@ async function importExitBundle(opts) {
|
|
|
42282
43270
|
unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
|
|
42283
43271
|
},
|
|
42284
43272
|
staged_artifacts: [],
|
|
42285
|
-
warnings: verification.warnings,
|
|
43273
|
+
warnings: [...verification.warnings, ...importWarnings],
|
|
42286
43274
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
42287
43275
|
};
|
|
42288
43276
|
}
|
|
@@ -42449,7 +43437,7 @@ async function importExitBundle(opts) {
|
|
|
42449
43437
|
state: stateResult,
|
|
42450
43438
|
reputation: reputationResult,
|
|
42451
43439
|
staged_artifacts: stagedArtifacts,
|
|
42452
|
-
warnings: verification.warnings,
|
|
43440
|
+
warnings: [...verification.warnings, ...importWarnings],
|
|
42453
43441
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
42454
43442
|
};
|
|
42455
43443
|
}
|
|
@@ -42471,12 +43459,13 @@ function exitBundleManifestShape() {
|
|
|
42471
43459
|
]
|
|
42472
43460
|
};
|
|
42473
43461
|
}
|
|
42474
|
-
var ARTIFACT_DIR, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE, ExitBundleImportError;
|
|
43462
|
+
var ARTIFACT_DIR, EXIT_BUNDLE_DID_WEB_AUDIT_OPS, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE, ExitBundleImportError;
|
|
42475
43463
|
var init_bundle = __esm({
|
|
42476
43464
|
"src/exit/bundle.ts"() {
|
|
42477
43465
|
init_state_store();
|
|
42478
43466
|
init_config();
|
|
42479
43467
|
init_constants5();
|
|
43468
|
+
init_did_web();
|
|
42480
43469
|
init_canonical_json();
|
|
42481
43470
|
init_hashing();
|
|
42482
43471
|
init_encoding();
|
|
@@ -42486,6 +43475,11 @@ var init_bundle = __esm({
|
|
|
42486
43475
|
init_reputation_store();
|
|
42487
43476
|
init_verifier2();
|
|
42488
43477
|
ARTIFACT_DIR = "artifacts";
|
|
43478
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS = {
|
|
43479
|
+
EXPORT_INCLUDED: "exit_bundle_did_web_export_included",
|
|
43480
|
+
IMPORT_VERIFIED: "exit_bundle_did_web_import_verified",
|
|
43481
|
+
AUTHORITY_HOST: "exit_bundle_did_web_authority_host"
|
|
43482
|
+
};
|
|
42489
43483
|
EXIT_IMPORT_NAMESPACE = "_exit_imports";
|
|
42490
43484
|
EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
|
|
42491
43485
|
EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
|
|
@@ -42704,6 +43698,26 @@ ${policyErr.message}
|
|
|
42704
43698
|
}
|
|
42705
43699
|
throw policyErr;
|
|
42706
43700
|
}
|
|
43701
|
+
const includeDidWebFlag = flagValue(argv, "--include-did-web");
|
|
43702
|
+
const includeDidWebDisabled = includeDidWebFlag === "false";
|
|
43703
|
+
const didWebIdentifier = flagValue(argv, "--did-web");
|
|
43704
|
+
const didWebAuthorityHost = flagValue(argv, "--did-web-authority-host");
|
|
43705
|
+
const didWebPublishedAt = flagValue(argv, "--did-web-published-at");
|
|
43706
|
+
let exportDidWeb;
|
|
43707
|
+
if (!includeDidWebDisabled && didWebIdentifier !== void 0) {
|
|
43708
|
+
if (didWebAuthorityHost === void 0) {
|
|
43709
|
+
write(
|
|
43710
|
+
err,
|
|
43711
|
+
"Error: --did-web requires --did-web-authority-host=<host>\n"
|
|
43712
|
+
);
|
|
43713
|
+
return 2;
|
|
43714
|
+
}
|
|
43715
|
+
exportDidWeb = {
|
|
43716
|
+
identifier: didWebIdentifier,
|
|
43717
|
+
authority_host: didWebAuthorityHost,
|
|
43718
|
+
...didWebPublishedAt !== void 0 ? { published_at: didWebPublishedAt } : {}
|
|
43719
|
+
};
|
|
43720
|
+
}
|
|
42707
43721
|
const result = await exportExitBundle({
|
|
42708
43722
|
bundleDir: outDir,
|
|
42709
43723
|
storage: ctx.storage,
|
|
@@ -42715,7 +43729,8 @@ ${policyErr.message}
|
|
|
42715
43729
|
config,
|
|
42716
43730
|
stateStoragePath: ctx.stateStoragePath,
|
|
42717
43731
|
stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
|
|
42718
|
-
keySource: ctx.keySource
|
|
43732
|
+
keySource: ctx.keySource,
|
|
43733
|
+
...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
|
|
42719
43734
|
});
|
|
42720
43735
|
if (json) write(out, JSON.stringify(result, null, 2) + "\n");
|
|
42721
43736
|
else {
|
|
@@ -42793,6 +43808,11 @@ ${policyErr.message}
|
|
|
42793
43808
|
write(err, "--conflict must be skip, overwrite, or version\n");
|
|
42794
43809
|
return 2;
|
|
42795
43810
|
}
|
|
43811
|
+
const didWebAllowedHosts = repeatedFlagValues(
|
|
43812
|
+
argv,
|
|
43813
|
+
"--did-web-allowed-host"
|
|
43814
|
+
);
|
|
43815
|
+
const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
|
|
42796
43816
|
let result;
|
|
42797
43817
|
try {
|
|
42798
43818
|
result = await importExitBundle({
|
|
@@ -42808,7 +43828,9 @@ ${policyErr.message}
|
|
|
42808
43828
|
conflictResolution: conflict,
|
|
42809
43829
|
sourcePassphrase: flagValue(argv, "--source-passphrase"),
|
|
42810
43830
|
sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
|
|
42811
|
-
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
|
|
43831
|
+
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
|
|
43832
|
+
...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
|
|
43833
|
+
skipDidWebVerify
|
|
42812
43834
|
});
|
|
42813
43835
|
} catch (e) {
|
|
42814
43836
|
if (e instanceof InvalidExitBundleError) {
|
|
@@ -43593,12 +44615,14 @@ ${err.message}
|
|
|
43593
44615
|
fortressId: fortressIdForAggregator
|
|
43594
44616
|
});
|
|
43595
44617
|
const handoffEventBridge = new HandoffEventBridge();
|
|
44618
|
+
const workflowStateTracker = new WorkflowStateTracker();
|
|
43596
44619
|
if (dashboard) {
|
|
43597
44620
|
dashboard.setHandoffLog({
|
|
43598
44621
|
handoffLog,
|
|
43599
44622
|
eventBridge: handoffEventBridge,
|
|
43600
44623
|
auditLog,
|
|
43601
|
-
operatorId: aggregatorIdentityId
|
|
44624
|
+
operatorId: aggregatorIdentityId,
|
|
44625
|
+
workflowStateTracker
|
|
43602
44626
|
});
|
|
43603
44627
|
}
|
|
43604
44628
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
@@ -43800,6 +44824,7 @@ var init_src = __esm({
|
|
|
43800
44824
|
init_anomaly_pipeline();
|
|
43801
44825
|
init_handoff_log();
|
|
43802
44826
|
init_handoff_routes();
|
|
44827
|
+
init_workflow_state_tracker();
|
|
43803
44828
|
init_sentinels();
|
|
43804
44829
|
init_subscription_store();
|
|
43805
44830
|
init_tools4();
|
|
@@ -47906,7 +48931,7 @@ async function probeTenantDashboard(tenant, options = {}) {
|
|
|
47906
48931
|
if (!rt) {
|
|
47907
48932
|
return { running: false, status: null, reason: "no runtime.json" };
|
|
47908
48933
|
}
|
|
47909
|
-
const timeoutMs = options.timeoutMs ??
|
|
48934
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
|
|
47910
48935
|
return await new Promise((resolve8) => {
|
|
47911
48936
|
const req = get$1(
|
|
47912
48937
|
{
|
|
@@ -47942,10 +48967,10 @@ async function probeTenantDashboard(tenant, options = {}) {
|
|
|
47942
48967
|
});
|
|
47943
48968
|
});
|
|
47944
48969
|
}
|
|
47945
|
-
var
|
|
48970
|
+
var DEFAULT_TIMEOUT_MS5;
|
|
47946
48971
|
var init_health = __esm({
|
|
47947
48972
|
"src/cli/agents/health.ts"() {
|
|
47948
|
-
|
|
48973
|
+
DEFAULT_TIMEOUT_MS5 = 500;
|
|
47949
48974
|
}
|
|
47950
48975
|
});
|
|
47951
48976
|
function resolveCtx(args) {
|
|
@@ -49196,108 +50221,6 @@ var init_sentinel2 = __esm({
|
|
|
49196
50221
|
init_sentinels();
|
|
49197
50222
|
}
|
|
49198
50223
|
});
|
|
49199
|
-
async function issueDidWeb(opts) {
|
|
49200
|
-
if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
|
|
49201
|
-
throw new Error(
|
|
49202
|
-
`did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
|
|
49203
|
-
);
|
|
49204
|
-
}
|
|
49205
|
-
if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
|
|
49206
|
-
throw new Error(
|
|
49207
|
-
`did-web: fortress_id '${opts.fortress_id}' is not a valid label`
|
|
49208
|
-
);
|
|
49209
|
-
}
|
|
49210
|
-
if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
|
|
49211
|
-
throw new Error(
|
|
49212
|
-
`did-web: agent_label '${opts.agent_label}' is not a valid label`
|
|
49213
|
-
);
|
|
49214
|
-
}
|
|
49215
|
-
if (opts.public_key.length !== 32) {
|
|
49216
|
-
throw new Error(
|
|
49217
|
-
`did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
|
|
49218
|
-
);
|
|
49219
|
-
}
|
|
49220
|
-
const did = buildDid(opts);
|
|
49221
|
-
const verificationMethodId = `${did}#key-1`;
|
|
49222
|
-
const verificationMethod = {
|
|
49223
|
-
id: verificationMethodId,
|
|
49224
|
-
type: "JsonWebKey2020",
|
|
49225
|
-
controller: did,
|
|
49226
|
-
publicKeyJwk: {
|
|
49227
|
-
kty: "OKP",
|
|
49228
|
-
crv: "Ed25519",
|
|
49229
|
-
x: toBase64url(opts.public_key)
|
|
49230
|
-
}
|
|
49231
|
-
};
|
|
49232
|
-
const didDocument = {
|
|
49233
|
-
"@context": [...DID_CONTEXT],
|
|
49234
|
-
id: did,
|
|
49235
|
-
verificationMethod: [verificationMethod],
|
|
49236
|
-
authentication: [verificationMethodId],
|
|
49237
|
-
assertionMethod: [verificationMethodId]
|
|
49238
|
-
};
|
|
49239
|
-
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
49240
|
-
return {
|
|
49241
|
-
did,
|
|
49242
|
-
did_document: didDocument,
|
|
49243
|
-
public_key: opts.public_key,
|
|
49244
|
-
created_at: now.toISOString(),
|
|
49245
|
-
authority_host: opts.authority_host,
|
|
49246
|
-
fortress_id: opts.fortress_id,
|
|
49247
|
-
...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
|
|
49248
|
-
};
|
|
49249
|
-
}
|
|
49250
|
-
function publishDidWebDocument(identifier, opts = {}) {
|
|
49251
|
-
const path = opts.publish_path ?? canonicalPublishPath(identifier);
|
|
49252
|
-
const artifact = canonicalSerializeDidDocument(identifier.did_document);
|
|
49253
|
-
const digest = sha256(stringToBytes(artifact));
|
|
49254
|
-
const url = `https://${identifier.authority_host}${path}`;
|
|
49255
|
-
return {
|
|
49256
|
-
url,
|
|
49257
|
-
publish_path: path,
|
|
49258
|
-
artifact,
|
|
49259
|
-
sha256: hashToString(digest)
|
|
49260
|
-
};
|
|
49261
|
-
}
|
|
49262
|
-
function buildDid(opts) {
|
|
49263
|
-
if (opts.agent_label === void 0) {
|
|
49264
|
-
return `did:web:${opts.authority_host}`;
|
|
49265
|
-
}
|
|
49266
|
-
return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
|
|
49267
|
-
}
|
|
49268
|
-
function canonicalPublishPath(identifier) {
|
|
49269
|
-
if (identifier.agent_label === void 0) {
|
|
49270
|
-
return "/.well-known/did.json";
|
|
49271
|
-
}
|
|
49272
|
-
return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
|
|
49273
|
-
}
|
|
49274
|
-
function canonicalSerializeDidDocument(doc) {
|
|
49275
|
-
return JSON.stringify(
|
|
49276
|
-
{
|
|
49277
|
-
"@context": doc["@context"],
|
|
49278
|
-
id: doc.id,
|
|
49279
|
-
verificationMethod: doc.verificationMethod,
|
|
49280
|
-
authentication: doc.authentication,
|
|
49281
|
-
assertionMethod: doc.assertionMethod
|
|
49282
|
-
},
|
|
49283
|
-
null,
|
|
49284
|
-
2
|
|
49285
|
-
);
|
|
49286
|
-
}
|
|
49287
|
-
var DID_CONTEXT, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
|
|
49288
|
-
var init_did_web = __esm({
|
|
49289
|
-
"src/recognition/did-web.ts"() {
|
|
49290
|
-
init_encoding();
|
|
49291
|
-
init_hashing();
|
|
49292
|
-
DID_CONTEXT = [
|
|
49293
|
-
"https://www.w3.org/ns/did/v1",
|
|
49294
|
-
"https://w3id.org/security/suites/jws-2020/v1"
|
|
49295
|
-
];
|
|
49296
|
-
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;
|
|
49297
|
-
FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
49298
|
-
AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
49299
|
-
}
|
|
49300
|
-
});
|
|
49301
50224
|
|
|
49302
50225
|
// src/cli/did-web.ts
|
|
49303
50226
|
var did_web_exports = {};
|
|
@@ -49556,6 +50479,689 @@ var init_did_web2 = __esm({
|
|
|
49556
50479
|
}
|
|
49557
50480
|
});
|
|
49558
50481
|
|
|
50482
|
+
// src/anomaly-detection/classifiers/rolling-baseline.ts
|
|
50483
|
+
var ROLLING_BASELINE_CLASSIFIER_ID, DEFAULT_MIN_SAMPLES_FOR_PREDICTION, STDDEV_FLOOR, RollingBaselineClassifier;
|
|
50484
|
+
var init_rolling_baseline = __esm({
|
|
50485
|
+
"src/anomaly-detection/classifiers/rolling-baseline.ts"() {
|
|
50486
|
+
ROLLING_BASELINE_CLASSIFIER_ID = "rolling-baseline";
|
|
50487
|
+
DEFAULT_MIN_SAMPLES_FOR_PREDICTION = 7;
|
|
50488
|
+
STDDEV_FLOOR = 0.5;
|
|
50489
|
+
RollingBaselineClassifier = class {
|
|
50490
|
+
classifierId = ROLLING_BASELINE_CLASSIFIER_ID;
|
|
50491
|
+
stateStore;
|
|
50492
|
+
minSamplesForPrediction;
|
|
50493
|
+
/** In-memory cache of per-agent state; loaded lazily on first touch. */
|
|
50494
|
+
cache = /* @__PURE__ */ new Map();
|
|
50495
|
+
/** Agents whose in-memory state has been mutated since last train(). */
|
|
50496
|
+
dirty = /* @__PURE__ */ new Set();
|
|
50497
|
+
constructor(opts) {
|
|
50498
|
+
this.stateStore = opts.stateStore;
|
|
50499
|
+
this.minSamplesForPrediction = opts.minSamplesForPrediction ?? DEFAULT_MIN_SAMPLES_FOR_PREDICTION;
|
|
50500
|
+
}
|
|
50501
|
+
async observe(vector) {
|
|
50502
|
+
const state = await this.loadOrInit(vector.agent_id);
|
|
50503
|
+
for (const [featureName, observed] of Object.entries(vector.features)) {
|
|
50504
|
+
if (!Number.isFinite(observed)) continue;
|
|
50505
|
+
const welford = state.features[featureName] ?? {
|
|
50506
|
+
n: 0,
|
|
50507
|
+
mean: 0,
|
|
50508
|
+
m2: 0
|
|
50509
|
+
};
|
|
50510
|
+
const nextN = welford.n + 1;
|
|
50511
|
+
const delta = observed - welford.mean;
|
|
50512
|
+
const nextMean = welford.mean + delta / nextN;
|
|
50513
|
+
const delta2 = observed - nextMean;
|
|
50514
|
+
const nextM2 = welford.m2 + delta * delta2;
|
|
50515
|
+
state.features[featureName] = { n: nextN, mean: nextMean, m2: nextM2 };
|
|
50516
|
+
}
|
|
50517
|
+
state.observation_count += 1;
|
|
50518
|
+
state.last_observed_at = vector.observed_at;
|
|
50519
|
+
this.dirty.add(vector.agent_id);
|
|
50520
|
+
}
|
|
50521
|
+
async predict(vector) {
|
|
50522
|
+
const state = await this.loadOrInit(vector.agent_id);
|
|
50523
|
+
if (state.observation_count < this.minSamplesForPrediction) {
|
|
50524
|
+
return {
|
|
50525
|
+
anomaly_score: 0,
|
|
50526
|
+
explanation: [],
|
|
50527
|
+
feature_contributions: [],
|
|
50528
|
+
baseline_ready: false
|
|
50529
|
+
};
|
|
50530
|
+
}
|
|
50531
|
+
const contributions = [];
|
|
50532
|
+
let sumSquaredZ = 0;
|
|
50533
|
+
for (const [featureName, observed] of Object.entries(vector.features)) {
|
|
50534
|
+
if (!Number.isFinite(observed)) continue;
|
|
50535
|
+
const welford = state.features[featureName];
|
|
50536
|
+
if (!welford || welford.n < 2) continue;
|
|
50537
|
+
const variance = welford.m2 / (welford.n - 1);
|
|
50538
|
+
const stddev = Math.max(Math.sqrt(variance), STDDEV_FLOOR);
|
|
50539
|
+
const z = (observed - welford.mean) / stddev;
|
|
50540
|
+
sumSquaredZ += z * z;
|
|
50541
|
+
contributions.push({
|
|
50542
|
+
feature_name: featureName,
|
|
50543
|
+
observed,
|
|
50544
|
+
baseline_mean: welford.mean,
|
|
50545
|
+
baseline_stddev: stddev,
|
|
50546
|
+
z_score: z
|
|
50547
|
+
});
|
|
50548
|
+
}
|
|
50549
|
+
contributions.sort(
|
|
50550
|
+
(a, b) => Math.abs(b.z_score) - Math.abs(a.z_score)
|
|
50551
|
+
);
|
|
50552
|
+
const anomalyScore = Math.sqrt(sumSquaredZ);
|
|
50553
|
+
const explanation = contributions.map(
|
|
50554
|
+
(c) => `${c.feature_name} ${c.observed.toFixed(2)} vs baseline ${c.baseline_mean.toFixed(2)}+/-${c.baseline_stddev.toFixed(2)} (z=${c.z_score.toFixed(2)})`
|
|
50555
|
+
);
|
|
50556
|
+
return {
|
|
50557
|
+
anomaly_score: anomalyScore,
|
|
50558
|
+
explanation,
|
|
50559
|
+
feature_contributions: contributions,
|
|
50560
|
+
baseline_ready: true
|
|
50561
|
+
};
|
|
50562
|
+
}
|
|
50563
|
+
async train() {
|
|
50564
|
+
const dirtyAgents = [...this.dirty];
|
|
50565
|
+
for (const agentId of dirtyAgents) {
|
|
50566
|
+
const state = this.cache.get(agentId);
|
|
50567
|
+
if (!state) continue;
|
|
50568
|
+
await this.stateStore.saveState(this.classifierId, agentId, state);
|
|
50569
|
+
this.dirty.delete(agentId);
|
|
50570
|
+
}
|
|
50571
|
+
let sampleCount = 0;
|
|
50572
|
+
for (const state of this.cache.values()) {
|
|
50573
|
+
sampleCount += state.observation_count;
|
|
50574
|
+
}
|
|
50575
|
+
return {
|
|
50576
|
+
trained_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
50577
|
+
sample_count: sampleCount,
|
|
50578
|
+
agent_count: this.cache.size
|
|
50579
|
+
};
|
|
50580
|
+
}
|
|
50581
|
+
/** Test helper: read the in-memory state for an agent. */
|
|
50582
|
+
getAgentState(agentId) {
|
|
50583
|
+
return this.cache.get(agentId);
|
|
50584
|
+
}
|
|
50585
|
+
async loadOrInit(agentId) {
|
|
50586
|
+
const cached = this.cache.get(agentId);
|
|
50587
|
+
if (cached) return cached;
|
|
50588
|
+
const persisted = await this.stateStore.loadState(
|
|
50589
|
+
this.classifierId,
|
|
50590
|
+
agentId
|
|
50591
|
+
);
|
|
50592
|
+
const state = persisted ?? {
|
|
50593
|
+
observation_count: 0,
|
|
50594
|
+
last_observed_at: null,
|
|
50595
|
+
features: {}
|
|
50596
|
+
};
|
|
50597
|
+
this.cache.set(agentId, state);
|
|
50598
|
+
return state;
|
|
50599
|
+
}
|
|
50600
|
+
};
|
|
50601
|
+
}
|
|
50602
|
+
});
|
|
50603
|
+
|
|
50604
|
+
// src/anomaly-detection/feature-extractors/per-agent-activity.ts
|
|
50605
|
+
function emptyBucket() {
|
|
50606
|
+
return {
|
|
50607
|
+
tool_call_count: 0,
|
|
50608
|
+
egress_call_count: 0,
|
|
50609
|
+
credential_use_count: 0,
|
|
50610
|
+
audit_event_count: 0,
|
|
50611
|
+
recent_receipt_count: 0
|
|
50612
|
+
};
|
|
50613
|
+
}
|
|
50614
|
+
function bucketToFeatures(bucket) {
|
|
50615
|
+
return {
|
|
50616
|
+
tool_call_count: bucket.tool_call_count,
|
|
50617
|
+
egress_call_count: bucket.egress_call_count,
|
|
50618
|
+
credential_use_count: bucket.credential_use_count,
|
|
50619
|
+
audit_event_count: bucket.audit_event_count,
|
|
50620
|
+
recent_receipt_count: bucket.recent_receipt_count
|
|
50621
|
+
};
|
|
50622
|
+
}
|
|
50623
|
+
function classifyEntry(entry, bucket) {
|
|
50624
|
+
bucket.audit_event_count += 1;
|
|
50625
|
+
bucket.tool_call_count += 1;
|
|
50626
|
+
if (entry.operation.startsWith("proxy_call:")) {
|
|
50627
|
+
bucket.egress_call_count += 1;
|
|
50628
|
+
}
|
|
50629
|
+
if (entry.operation.startsWith("broker_secret_") || entry.operation.startsWith("broker_token_")) {
|
|
50630
|
+
bucket.credential_use_count += 1;
|
|
50631
|
+
}
|
|
50632
|
+
if (entry.operation.startsWith("composition_receipt_") || entry.operation === "reputation_record" || entry.operation === "reputation_query" || entry.operation === "reputation_publish") {
|
|
50633
|
+
bucket.recent_receipt_count += 1;
|
|
50634
|
+
}
|
|
50635
|
+
}
|
|
50636
|
+
async function extractPerAgentActivity(context) {
|
|
50637
|
+
const now = context.now();
|
|
50638
|
+
const sinceIso = new Date(now.getTime() - WINDOW_MS2).toISOString();
|
|
50639
|
+
const result = await context.auditLog.query({
|
|
50640
|
+
since: sinceIso,
|
|
50641
|
+
limit: QUERY_LIMIT6
|
|
50642
|
+
});
|
|
50643
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
50644
|
+
for (const entry of result.entries) {
|
|
50645
|
+
const agentId = entry.identity_id && entry.identity_id.length > 0 ? entry.identity_id : SYSTEM_AGENT_BUCKET;
|
|
50646
|
+
let bucket = buckets.get(agentId);
|
|
50647
|
+
if (!bucket) {
|
|
50648
|
+
bucket = emptyBucket();
|
|
50649
|
+
buckets.set(agentId, bucket);
|
|
50650
|
+
}
|
|
50651
|
+
classifyEntry(entry, bucket);
|
|
50652
|
+
}
|
|
50653
|
+
const observedAt = now.toISOString();
|
|
50654
|
+
const vectors = [];
|
|
50655
|
+
for (const [agentId, bucket] of buckets.entries()) {
|
|
50656
|
+
vectors.push({
|
|
50657
|
+
agent_id: agentId,
|
|
50658
|
+
observed_at: observedAt,
|
|
50659
|
+
features: bucketToFeatures(bucket),
|
|
50660
|
+
window_label: PER_AGENT_ACTIVITY_WINDOW_LABEL
|
|
50661
|
+
});
|
|
50662
|
+
}
|
|
50663
|
+
vectors.sort((a, b) => a.agent_id < b.agent_id ? -1 : 1);
|
|
50664
|
+
return vectors;
|
|
50665
|
+
}
|
|
50666
|
+
var PER_AGENT_ACTIVITY_EXTRACTOR_ID, WINDOW_MS2, QUERY_LIMIT6, SYSTEM_AGENT_BUCKET, PER_AGENT_ACTIVITY_WINDOW_LABEL;
|
|
50667
|
+
var init_per_agent_activity = __esm({
|
|
50668
|
+
"src/anomaly-detection/feature-extractors/per-agent-activity.ts"() {
|
|
50669
|
+
PER_AGENT_ACTIVITY_EXTRACTOR_ID = "per-agent-activity";
|
|
50670
|
+
WINDOW_MS2 = 24 * 60 * 60 * 1e3;
|
|
50671
|
+
QUERY_LIMIT6 = 1e4;
|
|
50672
|
+
SYSTEM_AGENT_BUCKET = "system";
|
|
50673
|
+
PER_AGENT_ACTIVITY_WINDOW_LABEL = "24h_rolling";
|
|
50674
|
+
}
|
|
50675
|
+
});
|
|
50676
|
+
|
|
50677
|
+
// src/anomaly-detection/detectors/per-agent-activity-detector.ts
|
|
50678
|
+
var PER_AGENT_ACTIVITY_DETECTOR_ID, PerAgentActivityDetector, PendingClassifier;
|
|
50679
|
+
var init_per_agent_activity_detector = __esm({
|
|
50680
|
+
"src/anomaly-detection/detectors/per-agent-activity-detector.ts"() {
|
|
50681
|
+
init_types4();
|
|
50682
|
+
init_rolling_baseline();
|
|
50683
|
+
init_classifier_state_store();
|
|
50684
|
+
init_per_agent_activity();
|
|
50685
|
+
PER_AGENT_ACTIVITY_DETECTOR_ID = PER_AGENT_ACTIVITY_EXTRACTOR_ID;
|
|
50686
|
+
PerAgentActivityDetector = class extends AnomalyDetector {
|
|
50687
|
+
detectorId = PER_AGENT_ACTIVITY_DETECTOR_ID;
|
|
50688
|
+
description = "Per-agent statistical drift detector: tool-call count, egress volume, credential-use rate, audit-event count, recent-receipt count over a 24h rolling window. Compared against a per-agent rolling baseline (Welford running mean + variance).";
|
|
50689
|
+
classifier;
|
|
50690
|
+
explicitClassifier;
|
|
50691
|
+
minSamplesForPrediction;
|
|
50692
|
+
constructor(opts) {
|
|
50693
|
+
super();
|
|
50694
|
+
this.explicitClassifier = opts?.classifier !== void 0;
|
|
50695
|
+
if (opts?.classifier) {
|
|
50696
|
+
this.classifier = opts.classifier;
|
|
50697
|
+
} else {
|
|
50698
|
+
this.classifier = new PendingClassifier();
|
|
50699
|
+
}
|
|
50700
|
+
this.minSamplesForPrediction = opts?.minSamplesForPrediction;
|
|
50701
|
+
}
|
|
50702
|
+
async subscribe(context) {
|
|
50703
|
+
await super.subscribe(context);
|
|
50704
|
+
if (this.explicitClassifier) return;
|
|
50705
|
+
const stateStore = new ClassifierStateStore({
|
|
50706
|
+
storage: context.storage,
|
|
50707
|
+
masterKey: context.masterKey,
|
|
50708
|
+
fortressId: context.fortressId,
|
|
50709
|
+
now: context.now
|
|
50710
|
+
});
|
|
50711
|
+
const realClassifier = new RollingBaselineClassifier({
|
|
50712
|
+
stateStore,
|
|
50713
|
+
...this.minSamplesForPrediction !== void 0 ? { minSamplesForPrediction: this.minSamplesForPrediction } : {}
|
|
50714
|
+
});
|
|
50715
|
+
this.classifier = realClassifier;
|
|
50716
|
+
}
|
|
50717
|
+
async featureExtract(context) {
|
|
50718
|
+
return extractPerAgentActivity(context);
|
|
50719
|
+
}
|
|
50720
|
+
};
|
|
50721
|
+
PendingClassifier = class {
|
|
50722
|
+
classifierId = "pending";
|
|
50723
|
+
async observe() {
|
|
50724
|
+
throw new Error(
|
|
50725
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50726
|
+
);
|
|
50727
|
+
}
|
|
50728
|
+
async predict() {
|
|
50729
|
+
throw new Error(
|
|
50730
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50731
|
+
);
|
|
50732
|
+
}
|
|
50733
|
+
async train() {
|
|
50734
|
+
throw new Error(
|
|
50735
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50736
|
+
);
|
|
50737
|
+
}
|
|
50738
|
+
};
|
|
50739
|
+
}
|
|
50740
|
+
});
|
|
50741
|
+
|
|
50742
|
+
// src/anomaly-detection/anomaly-catalog.ts
|
|
50743
|
+
function findCatalogEntry(detectorId, classifierId) {
|
|
50744
|
+
return ANOMALY_CATALOG.find(
|
|
50745
|
+
(e) => e.detectorId === detectorId && e.classifierId === classifierId
|
|
50746
|
+
);
|
|
50747
|
+
}
|
|
50748
|
+
var ANOMALY_CATALOG;
|
|
50749
|
+
var init_anomaly_catalog = __esm({
|
|
50750
|
+
"src/anomaly-detection/anomaly-catalog.ts"() {
|
|
50751
|
+
init_per_agent_activity_detector();
|
|
50752
|
+
init_rolling_baseline();
|
|
50753
|
+
ANOMALY_CATALOG = [
|
|
50754
|
+
{
|
|
50755
|
+
detectorId: PER_AGENT_ACTIVITY_DETECTOR_ID,
|
|
50756
|
+
classifierId: ROLLING_BASELINE_CLASSIFIER_ID,
|
|
50757
|
+
description: "Per-agent statistical drift detector: tool-call count, egress volume, credential-use rate, audit-event count, recent-receipt count over a 24h rolling window. Welford running mean + variance baseline per agent.",
|
|
50758
|
+
factory: () => new PerAgentActivityDetector()
|
|
50759
|
+
}
|
|
50760
|
+
];
|
|
50761
|
+
}
|
|
50762
|
+
});
|
|
50763
|
+
function anomalySubscriptionsPath(storagePath) {
|
|
50764
|
+
return join(storagePath, "anomaly-subscriptions.json");
|
|
50765
|
+
}
|
|
50766
|
+
async function loadAnomalySubscriptions(storagePath) {
|
|
50767
|
+
const filePath = anomalySubscriptionsPath(storagePath);
|
|
50768
|
+
try {
|
|
50769
|
+
const raw = await readFile(filePath, "utf8");
|
|
50770
|
+
const parsed = JSON.parse(raw);
|
|
50771
|
+
if (parsed.version !== FILE_VERSION2) return [];
|
|
50772
|
+
if (!Array.isArray(parsed.subscribed)) return [];
|
|
50773
|
+
return parsed.subscribed.filter(
|
|
50774
|
+
(t) => t !== null && typeof t === "object" && typeof t.detector_id === "string" && typeof t.classifier_id === "string"
|
|
50775
|
+
);
|
|
50776
|
+
} catch {
|
|
50777
|
+
return [];
|
|
50778
|
+
}
|
|
50779
|
+
}
|
|
50780
|
+
async function saveAnomalySubscriptions(storagePath, subscriptions) {
|
|
50781
|
+
const filePath = anomalySubscriptionsPath(storagePath);
|
|
50782
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
50783
|
+
const payload = {
|
|
50784
|
+
version: FILE_VERSION2,
|
|
50785
|
+
// Deduplicate.
|
|
50786
|
+
subscribed: dedupe(subscriptions)
|
|
50787
|
+
};
|
|
50788
|
+
await writeFile(filePath, JSON.stringify(payload, null, 2), {
|
|
50789
|
+
mode: 384
|
|
50790
|
+
});
|
|
50791
|
+
}
|
|
50792
|
+
function dedupe(items) {
|
|
50793
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50794
|
+
const out = [];
|
|
50795
|
+
for (const item of items) {
|
|
50796
|
+
const key = `${item.detector_id}|${item.classifier_id}`;
|
|
50797
|
+
if (seen.has(key)) continue;
|
|
50798
|
+
seen.add(key);
|
|
50799
|
+
out.push(item);
|
|
50800
|
+
}
|
|
50801
|
+
return out;
|
|
50802
|
+
}
|
|
50803
|
+
var FILE_VERSION2;
|
|
50804
|
+
var init_anomaly_subscription_store = __esm({
|
|
50805
|
+
"src/anomaly-detection/anomaly-subscription-store.ts"() {
|
|
50806
|
+
FILE_VERSION2 = 1;
|
|
50807
|
+
}
|
|
50808
|
+
});
|
|
50809
|
+
|
|
50810
|
+
// src/cli/anomaly.ts
|
|
50811
|
+
var anomaly_exports = {};
|
|
50812
|
+
__export(anomaly_exports, {
|
|
50813
|
+
runAnomalyCommand: () => runAnomalyCommand
|
|
50814
|
+
});
|
|
50815
|
+
async function runAnomalyCommand(args) {
|
|
50816
|
+
const out = args.out ?? process.stdout;
|
|
50817
|
+
const err = args.err ?? process.stderr;
|
|
50818
|
+
const [sub, ...rest] = args.argv;
|
|
50819
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
50820
|
+
printUsage8(out);
|
|
50821
|
+
return 0;
|
|
50822
|
+
}
|
|
50823
|
+
try {
|
|
50824
|
+
switch (sub) {
|
|
50825
|
+
case "detectors":
|
|
50826
|
+
return cmdDetectors(rest, { out });
|
|
50827
|
+
case "list-subscribed":
|
|
50828
|
+
return await cmdListSubscribed2({ out, args });
|
|
50829
|
+
case "subscribe":
|
|
50830
|
+
return await cmdSubscribe2(rest, { out, err, args });
|
|
50831
|
+
case "unsubscribe":
|
|
50832
|
+
return await cmdUnsubscribe2(rest, { out, err, args });
|
|
50833
|
+
case "findings":
|
|
50834
|
+
return await cmdFindings2(rest, { out, err, args });
|
|
50835
|
+
case "classifier-state":
|
|
50836
|
+
return await cmdClassifierState(rest, { out, err, args });
|
|
50837
|
+
default:
|
|
50838
|
+
err.write(`Unknown subcommand: ${sub}
|
|
50839
|
+
`);
|
|
50840
|
+
printUsage8(err);
|
|
50841
|
+
return 2;
|
|
50842
|
+
}
|
|
50843
|
+
} catch (e) {
|
|
50844
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
50845
|
+
err.write(`sanctuary anomaly: ${msg}
|
|
50846
|
+
`);
|
|
50847
|
+
return 1;
|
|
50848
|
+
}
|
|
50849
|
+
}
|
|
50850
|
+
function printUsage8(s) {
|
|
50851
|
+
s.write(`Usage: sanctuary anomaly <command> [args]
|
|
50852
|
+
|
|
50853
|
+
detectors list Catalog of available
|
|
50854
|
+
detector + classifier tuples.
|
|
50855
|
+
list-subscribed Subscriptions on this fortress.
|
|
50856
|
+
subscribe <detector-id> --classifier <id>
|
|
50857
|
+
Opt in. Writes the
|
|
50858
|
+
subscription file; the server
|
|
50859
|
+
picks it up on next boot.
|
|
50860
|
+
unsubscribe <detector-id> --classifier <id>
|
|
50861
|
+
Opt out.
|
|
50862
|
+
findings [opts] Read anomaly findings.
|
|
50863
|
+
--since <iso> observed_at >= iso.
|
|
50864
|
+
--severity <info|warn|alert> Filter by severity.
|
|
50865
|
+
--detector-id <id> Filter by emitting detector.
|
|
50866
|
+
--agent-id <id> Filter by agent attribution.
|
|
50867
|
+
--limit <n> Cap result count (default 100).
|
|
50868
|
+
findings show <finding-id> Full drift-inspector detail.
|
|
50869
|
+
classifier-state <detector-id> --classifier <id>
|
|
50870
|
+
Per-agent training state.
|
|
50871
|
+
`);
|
|
50872
|
+
}
|
|
50873
|
+
function flagValue4(argv, name) {
|
|
50874
|
+
const i = argv.indexOf(name);
|
|
50875
|
+
if (i === -1) return void 0;
|
|
50876
|
+
return argv[i + 1];
|
|
50877
|
+
}
|
|
50878
|
+
function cmdDetectors(argv, ctx) {
|
|
50879
|
+
const sub = argv[0];
|
|
50880
|
+
if (sub !== void 0 && sub !== "list") {
|
|
50881
|
+
ctx.out.write(`Unknown detectors subcommand: ${sub}
|
|
50882
|
+
`);
|
|
50883
|
+
return 2;
|
|
50884
|
+
}
|
|
50885
|
+
if (ANOMALY_CATALOG.length === 0) {
|
|
50886
|
+
ctx.out.write("(no detectors registered)\n");
|
|
50887
|
+
return 0;
|
|
50888
|
+
}
|
|
50889
|
+
for (const entry of ANOMALY_CATALOG) {
|
|
50890
|
+
ctx.out.write(
|
|
50891
|
+
`${entry.detectorId} [classifier: ${entry.classifierId}]
|
|
50892
|
+
${entry.description}
|
|
50893
|
+
`
|
|
50894
|
+
);
|
|
50895
|
+
}
|
|
50896
|
+
return 0;
|
|
50897
|
+
}
|
|
50898
|
+
async function cmdListSubscribed2(ctx) {
|
|
50899
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50900
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50901
|
+
if (subscribed.length === 0) {
|
|
50902
|
+
ctx.out.write("(no subscriptions)\n");
|
|
50903
|
+
return 0;
|
|
50904
|
+
}
|
|
50905
|
+
for (const t of subscribed) {
|
|
50906
|
+
ctx.out.write(`${t.detector_id} [classifier: ${t.classifier_id}]
|
|
50907
|
+
`);
|
|
50908
|
+
}
|
|
50909
|
+
return 0;
|
|
50910
|
+
}
|
|
50911
|
+
async function cmdSubscribe2(argv, ctx) {
|
|
50912
|
+
const detectorId = argv[0];
|
|
50913
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
50914
|
+
if (!detectorId) {
|
|
50915
|
+
ctx.err.write("subscribe requires a detector-id\n");
|
|
50916
|
+
return 2;
|
|
50917
|
+
}
|
|
50918
|
+
if (!classifierId) {
|
|
50919
|
+
ctx.err.write("subscribe requires --classifier <id>\n");
|
|
50920
|
+
return 2;
|
|
50921
|
+
}
|
|
50922
|
+
const entry = findCatalogEntry(detectorId, classifierId);
|
|
50923
|
+
if (!entry) {
|
|
50924
|
+
ctx.err.write(
|
|
50925
|
+
`Unknown detector/classifier pair: ${detectorId} / ${classifierId}
|
|
50926
|
+
`
|
|
50927
|
+
);
|
|
50928
|
+
return 2;
|
|
50929
|
+
}
|
|
50930
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50931
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50932
|
+
const exists = subscribed.some(
|
|
50933
|
+
(t) => t.detector_id === detectorId && t.classifier_id === classifierId
|
|
50934
|
+
);
|
|
50935
|
+
if (exists) {
|
|
50936
|
+
ctx.out.write(
|
|
50937
|
+
`Already subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50938
|
+
`
|
|
50939
|
+
);
|
|
50940
|
+
return 0;
|
|
50941
|
+
}
|
|
50942
|
+
subscribed.push({ detector_id: detectorId, classifier_id: classifierId });
|
|
50943
|
+
await saveAnomalySubscriptions(storagePath, subscribed);
|
|
50944
|
+
ctx.out.write(
|
|
50945
|
+
`Subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50946
|
+
Restart Sanctuary or wait for the next dispatcher tick.
|
|
50947
|
+
`
|
|
50948
|
+
);
|
|
50949
|
+
return 0;
|
|
50950
|
+
}
|
|
50951
|
+
async function cmdUnsubscribe2(argv, ctx) {
|
|
50952
|
+
const detectorId = argv[0];
|
|
50953
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
50954
|
+
if (!detectorId) {
|
|
50955
|
+
ctx.err.write("unsubscribe requires a detector-id\n");
|
|
50956
|
+
return 2;
|
|
50957
|
+
}
|
|
50958
|
+
if (!classifierId) {
|
|
50959
|
+
ctx.err.write("unsubscribe requires --classifier <id>\n");
|
|
50960
|
+
return 2;
|
|
50961
|
+
}
|
|
50962
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50963
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50964
|
+
const filtered = subscribed.filter(
|
|
50965
|
+
(t) => !(t.detector_id === detectorId && t.classifier_id === classifierId)
|
|
50966
|
+
);
|
|
50967
|
+
if (filtered.length === subscribed.length) {
|
|
50968
|
+
ctx.out.write(
|
|
50969
|
+
`Not subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50970
|
+
`
|
|
50971
|
+
);
|
|
50972
|
+
return 0;
|
|
50973
|
+
}
|
|
50974
|
+
await saveAnomalySubscriptions(storagePath, filtered);
|
|
50975
|
+
ctx.out.write(
|
|
50976
|
+
`Unsubscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50977
|
+
`
|
|
50978
|
+
);
|
|
50979
|
+
return 0;
|
|
50980
|
+
}
|
|
50981
|
+
async function cmdFindings2(argv, ctx) {
|
|
50982
|
+
if (argv[0] === "show") {
|
|
50983
|
+
return await cmdFindingsShow(argv.slice(1), ctx);
|
|
50984
|
+
}
|
|
50985
|
+
const filters = parseFindingFilters2(argv);
|
|
50986
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
50987
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50988
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
50989
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
50990
|
+
const store = new SentinelFindingStore({
|
|
50991
|
+
storage,
|
|
50992
|
+
masterKey,
|
|
50993
|
+
fortressId
|
|
50994
|
+
});
|
|
50995
|
+
const filterSentinelId = filters.detectorId !== void 0 ? `${ANOMALY_SENTINEL_ID_PREFIX}${filters.detectorId}` : void 0;
|
|
50996
|
+
const allFindings = await store.listFindings({
|
|
50997
|
+
limit: filters.limit ?? 100,
|
|
50998
|
+
...filters.since !== void 0 ? { since: filters.since } : {},
|
|
50999
|
+
...filters.severity !== void 0 ? { severity: filters.severity } : {},
|
|
51000
|
+
...filterSentinelId !== void 0 ? { sentinelId: filterSentinelId } : {},
|
|
51001
|
+
...filters.agentId !== void 0 ? { agentId: filters.agentId } : {}
|
|
51002
|
+
});
|
|
51003
|
+
const anomalyFindings = filterSentinelId !== void 0 ? allFindings : allFindings.filter(
|
|
51004
|
+
(f) => f.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)
|
|
51005
|
+
);
|
|
51006
|
+
if (anomalyFindings.length === 0) {
|
|
51007
|
+
ctx.out.write("(no findings)\n");
|
|
51008
|
+
return 0;
|
|
51009
|
+
}
|
|
51010
|
+
for (const finding of anomalyFindings) {
|
|
51011
|
+
const detectorId = finding.details["detector_id"] ?? "";
|
|
51012
|
+
const score = finding.details["anomaly_score"];
|
|
51013
|
+
const scoreStr = typeof score === "number" ? ` score=${score.toFixed(2)}` : "";
|
|
51014
|
+
ctx.out.write(
|
|
51015
|
+
`[${finding.observed_at}] ${finding.severity.toUpperCase()} ${detectorId}${finding.agent_id ? ` (agent ${finding.agent_id})` : ""}${scoreStr}: ${finding.summary}
|
|
51016
|
+
`
|
|
51017
|
+
);
|
|
51018
|
+
}
|
|
51019
|
+
return 0;
|
|
51020
|
+
}
|
|
51021
|
+
async function cmdFindingsShow(argv, ctx) {
|
|
51022
|
+
const findingId = argv[0];
|
|
51023
|
+
if (!findingId) {
|
|
51024
|
+
ctx.err.write("findings show requires a finding-id\n");
|
|
51025
|
+
return 2;
|
|
51026
|
+
}
|
|
51027
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
51028
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51029
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51030
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
51031
|
+
const store = new SentinelFindingStore({ storage, masterKey, fortressId });
|
|
51032
|
+
const finding = await store.loadFinding(findingId);
|
|
51033
|
+
if (!finding) {
|
|
51034
|
+
ctx.err.write(`Finding not found: ${findingId}
|
|
51035
|
+
`);
|
|
51036
|
+
return 1;
|
|
51037
|
+
}
|
|
51038
|
+
if (!finding.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)) {
|
|
51039
|
+
ctx.err.write(
|
|
51040
|
+
`Finding ${findingId} is not an anomaly finding; try sanctuary sentinel findings.
|
|
51041
|
+
`
|
|
51042
|
+
);
|
|
51043
|
+
return 1;
|
|
51044
|
+
}
|
|
51045
|
+
ctx.out.write(JSON.stringify(finding, null, 2) + "\n");
|
|
51046
|
+
return 0;
|
|
51047
|
+
}
|
|
51048
|
+
async function cmdClassifierState(argv, ctx) {
|
|
51049
|
+
const detectorId = argv[0];
|
|
51050
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
51051
|
+
if (!detectorId) {
|
|
51052
|
+
ctx.err.write("classifier-state requires a detector-id\n");
|
|
51053
|
+
return 2;
|
|
51054
|
+
}
|
|
51055
|
+
if (!classifierId) {
|
|
51056
|
+
ctx.err.write("classifier-state requires --classifier <id>\n");
|
|
51057
|
+
return 2;
|
|
51058
|
+
}
|
|
51059
|
+
const entry = findCatalogEntry(detectorId, classifierId);
|
|
51060
|
+
if (!entry) {
|
|
51061
|
+
ctx.err.write(
|
|
51062
|
+
`Unknown detector/classifier pair: ${detectorId} / ${classifierId}
|
|
51063
|
+
`
|
|
51064
|
+
);
|
|
51065
|
+
return 2;
|
|
51066
|
+
}
|
|
51067
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
51068
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51069
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51070
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
51071
|
+
const stateStore = new ClassifierStateStore({
|
|
51072
|
+
storage,
|
|
51073
|
+
masterKey,
|
|
51074
|
+
fortressId
|
|
51075
|
+
});
|
|
51076
|
+
const agentIds = await stateStore.listAgents(classifierId);
|
|
51077
|
+
if (agentIds.length === 0) {
|
|
51078
|
+
ctx.out.write("(no classifier state yet)\n");
|
|
51079
|
+
return 0;
|
|
51080
|
+
}
|
|
51081
|
+
for (const agentId of agentIds) {
|
|
51082
|
+
try {
|
|
51083
|
+
const raw = await stateStore.loadState(classifierId, agentId);
|
|
51084
|
+
if (raw === null) continue;
|
|
51085
|
+
const sampleCount = typeof raw.sample_count === "number" ? raw.sample_count : "?";
|
|
51086
|
+
ctx.out.write(`${agentId}: sample_count=${sampleCount}
|
|
51087
|
+
`);
|
|
51088
|
+
} catch {
|
|
51089
|
+
ctx.out.write(`${agentId}: (load failed)
|
|
51090
|
+
`);
|
|
51091
|
+
}
|
|
51092
|
+
}
|
|
51093
|
+
return 0;
|
|
51094
|
+
}
|
|
51095
|
+
function parseFindingFilters2(argv) {
|
|
51096
|
+
const filters = {};
|
|
51097
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
51098
|
+
const arg = argv[i];
|
|
51099
|
+
if (arg === "--since" && argv[i + 1]) {
|
|
51100
|
+
filters.since = argv[++i];
|
|
51101
|
+
} else if (arg === "--severity" && argv[i + 1]) {
|
|
51102
|
+
const next = argv[++i];
|
|
51103
|
+
if (next === "info" || next === "warn" || next === "alert") {
|
|
51104
|
+
filters.severity = next;
|
|
51105
|
+
}
|
|
51106
|
+
} else if (arg === "--detector-id" && argv[i + 1]) {
|
|
51107
|
+
filters.detectorId = argv[++i];
|
|
51108
|
+
} else if (arg === "--agent-id" && argv[i + 1]) {
|
|
51109
|
+
filters.agentId = argv[++i];
|
|
51110
|
+
} else if (arg === "--limit" && argv[i + 1]) {
|
|
51111
|
+
const n = Number.parseInt(argv[++i], 10);
|
|
51112
|
+
if (!Number.isNaN(n) && n > 0) filters.limit = n;
|
|
51113
|
+
}
|
|
51114
|
+
}
|
|
51115
|
+
return filters;
|
|
51116
|
+
}
|
|
51117
|
+
async function resolveStoragePath3(args) {
|
|
51118
|
+
if (args.storagePath) return args.storagePath;
|
|
51119
|
+
const config = await loadConfig();
|
|
51120
|
+
return config.storage_path;
|
|
51121
|
+
}
|
|
51122
|
+
async function deriveFortressMasterKey(ctx) {
|
|
51123
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51124
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51125
|
+
let passphrase = ctx.args.passphrase ?? process.env["SANCTUARY_PASSPHRASE"];
|
|
51126
|
+
if (!passphrase) {
|
|
51127
|
+
const resolved = await getOrCreatePassphrase();
|
|
51128
|
+
passphrase = resolved.value;
|
|
51129
|
+
}
|
|
51130
|
+
let existingParams;
|
|
51131
|
+
try {
|
|
51132
|
+
const raw = await storage.read("_meta", "key-params");
|
|
51133
|
+
if (raw) existingParams = JSON.parse(bytesToString(raw));
|
|
51134
|
+
} catch {
|
|
51135
|
+
}
|
|
51136
|
+
const { key: masterKey, params } = await deriveMasterKey(
|
|
51137
|
+
passphrase,
|
|
51138
|
+
existingParams
|
|
51139
|
+
);
|
|
51140
|
+
if (!existingParams) {
|
|
51141
|
+
await storage.write(
|
|
51142
|
+
"_meta",
|
|
51143
|
+
"key-params",
|
|
51144
|
+
stringToBytes(JSON.stringify(params))
|
|
51145
|
+
);
|
|
51146
|
+
}
|
|
51147
|
+
return masterKey;
|
|
51148
|
+
}
|
|
51149
|
+
var init_anomaly = __esm({
|
|
51150
|
+
"src/cli/anomaly.ts"() {
|
|
51151
|
+
init_config();
|
|
51152
|
+
init_filesystem();
|
|
51153
|
+
init_key_derivation();
|
|
51154
|
+
init_encoding();
|
|
51155
|
+
init_passphrase();
|
|
51156
|
+
init_wiring();
|
|
51157
|
+
init_sentinel_finding_store();
|
|
51158
|
+
init_anomaly_catalog();
|
|
51159
|
+
init_anomaly_subscription_store();
|
|
51160
|
+
init_classifier_state_store();
|
|
51161
|
+
init_types4();
|
|
51162
|
+
}
|
|
51163
|
+
});
|
|
51164
|
+
|
|
49559
51165
|
// src/mcp/broker-server.ts
|
|
49560
51166
|
var broker_server_exports = {};
|
|
49561
51167
|
__export(broker_server_exports, {
|
|
@@ -50425,6 +52031,11 @@ async function main() {
|
|
|
50425
52031
|
const code = await runDidWebCommand2({ argv: args.slice(1) });
|
|
50426
52032
|
process.exit(code);
|
|
50427
52033
|
}
|
|
52034
|
+
if (args[0] === "anomaly") {
|
|
52035
|
+
const { runAnomalyCommand: runAnomalyCommand2 } = await Promise.resolve().then(() => (init_anomaly(), anomaly_exports));
|
|
52036
|
+
const code = await runAnomalyCommand2({ argv: args.slice(1) });
|
|
52037
|
+
process.exit(code);
|
|
52038
|
+
}
|
|
50428
52039
|
if (args[0] === "broker-server") {
|
|
50429
52040
|
const { openBroker: openBroker2 } = await Promise.resolve().then(() => (init_open(), open_exports));
|
|
50430
52041
|
const { createBrokerMcpServer: createBrokerMcpServer2 } = await Promise.resolve().then(() => (init_broker_server(), broker_server_exports));
|