@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 CHANGED
@@ -17625,8 +17625,30 @@ var init_handoff_log = __esm({
17625
17625
  }
17626
17626
  };
17627
17627
  COORDINATION_VIEW_AUDIT_OPS = {
17628
+ /** v1.3 Omega-1: operator opened the chronological handoff list. */
17628
17629
  VIEW_OPENED: "operator_coordination_view_opened",
17629
- ENTRY_DRILLED: "operator_handoff_entry_drilled"
17630
+ /** v1.3 Omega-1: operator drilled into a single handoff for detail. */
17631
+ ENTRY_DRILLED: "operator_handoff_entry_drilled",
17632
+ /**
17633
+ * v1.3 Omega-3: operator opened the Workflows sibling-view (list of
17634
+ * multi-handoff workflows grouped by `workflow-grouper`). Mirrors
17635
+ * VIEW_OPENED's shape so the dashboard activity feed can group both
17636
+ * as "operator coordination surfaces."
17637
+ */
17638
+ WORKFLOW_VIEW_OPENED: "operator_workflow_view_opened",
17639
+ /**
17640
+ * v1.3 Omega-3: operator drilled into a single workflow for its
17641
+ * timeline + member-handoffs detail. Mirrors ENTRY_DRILLED's shape.
17642
+ */
17643
+ WORKFLOW_DRILLED: "operator_workflow_drilled",
17644
+ /**
17645
+ * v1.3 Omega-3: server-side state transition observed on a
17646
+ * workflow (e.g., in_progress -> stalled). Emitted by the route
17647
+ * layer after the state tracker diffs against its prior snapshot.
17648
+ * Distinct from the operator-action events above: this records what
17649
+ * the workflow itself is doing, not what the operator clicked.
17650
+ */
17651
+ WORKFLOW_STATE_CHANGED: "coordination_workflow_state_changed"
17630
17652
  };
17631
17653
  }
17632
17654
  });
@@ -17905,6 +17927,124 @@ var init_context_transfer_extractor = __esm({
17905
17927
  };
17906
17928
  }
17907
17929
  });
17930
+ function groupHandoffsIntoWorkflows(handoffs, opts) {
17931
+ if (handoffs.length === 0) return [];
17932
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
17933
+ const linkedGroups = /* @__PURE__ */ new Map();
17934
+ const unlinked = [];
17935
+ for (const h of handoffs) {
17936
+ if (h.workflow_link !== null && h.workflow_link.length > 0) {
17937
+ let bucket = linkedGroups.get(h.workflow_link);
17938
+ if (!bucket) {
17939
+ bucket = [];
17940
+ linkedGroups.set(h.workflow_link, bucket);
17941
+ }
17942
+ bucket.push(h);
17943
+ } else {
17944
+ unlinked.push(h);
17945
+ }
17946
+ }
17947
+ const sortedUnlinked = [...unlinked].sort(
17948
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17949
+ );
17950
+ const heuristicChains = [];
17951
+ for (const h of sortedUnlinked) {
17952
+ const joinedIdx = findExtendableChain(heuristicChains, h);
17953
+ if (joinedIdx !== null) {
17954
+ heuristicChains[joinedIdx].push(h);
17955
+ } else {
17956
+ heuristicChains.push([h]);
17957
+ }
17958
+ }
17959
+ const workflows = [];
17960
+ for (const members of linkedGroups.values()) {
17961
+ workflows.push(materialize(members, now));
17962
+ }
17963
+ for (const members of heuristicChains) {
17964
+ workflows.push(materialize(members, now));
17965
+ }
17966
+ workflows.sort(
17967
+ (a, b) => a.last_activity_at < b.last_activity_at ? 1 : -1
17968
+ );
17969
+ return workflows;
17970
+ }
17971
+ function determineWorkflowState(members, now) {
17972
+ if (members.length === 0) return "unknown";
17973
+ const sorted = [...members].sort(
17974
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17975
+ );
17976
+ const last = sorted[sorted.length - 1];
17977
+ const root = sorted[0];
17978
+ const lastMs = Date.parse(last.observed_at);
17979
+ if (!Number.isFinite(lastMs)) return "unknown";
17980
+ if (last.target_agent_id === OPERATOR_PSEUDO_AGENT) {
17981
+ return "completed";
17982
+ }
17983
+ if (sorted.length > CYCLE_COMPLETION_MIN_HOPS && last.target_agent_id === root.source_agent_id) {
17984
+ return "completed";
17985
+ }
17986
+ const ageMs = now.getTime() - lastMs;
17987
+ if (ageMs > STALL_THRESHOLD_MS) {
17988
+ return "stalled";
17989
+ }
17990
+ return "in_progress";
17991
+ }
17992
+ function workflowIdFromRoot(rootEntryId) {
17993
+ return crypto.createHash("sha256").update(`workflow:${rootEntryId}`).digest("hex").slice(0, 32);
17994
+ }
17995
+ function findExtendableChain(chains, h) {
17996
+ const hMs = Date.parse(h.observed_at);
17997
+ if (!Number.isFinite(hMs)) return null;
17998
+ let bestIdx = null;
17999
+ let bestGapMs = Number.POSITIVE_INFINITY;
18000
+ for (let i = 0; i < chains.length; i += 1) {
18001
+ const chain = chains[i];
18002
+ const last = chain[chain.length - 1];
18003
+ const lastMs = Date.parse(last.observed_at);
18004
+ if (!Number.isFinite(lastMs)) continue;
18005
+ const gapMs = Math.abs(hMs - lastMs);
18006
+ if (gapMs > HEURISTIC_WINDOW_MS) continue;
18007
+ if (!sharesAgent(last, h)) continue;
18008
+ if (gapMs < bestGapMs) {
18009
+ bestGapMs = gapMs;
18010
+ bestIdx = i;
18011
+ }
18012
+ }
18013
+ return bestIdx;
18014
+ }
18015
+ function sharesAgent(a, b) {
18016
+ 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;
18017
+ }
18018
+ function materialize(members, now) {
18019
+ const sorted = [...members].sort(
18020
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
18021
+ );
18022
+ const root = sorted[0];
18023
+ const last = sorted[sorted.length - 1];
18024
+ const involved = /* @__PURE__ */ new Set();
18025
+ for (const h of sorted) {
18026
+ if (h.source_agent_id) involved.add(h.source_agent_id);
18027
+ if (h.target_agent_id) involved.add(h.target_agent_id);
18028
+ }
18029
+ return {
18030
+ workflow_id: workflowIdFromRoot(root.entry_id),
18031
+ root_handoff: root,
18032
+ member_handoffs: sorted,
18033
+ state: determineWorkflowState(sorted, now),
18034
+ started_at: root.observed_at,
18035
+ last_activity_at: last.observed_at,
18036
+ involved_agents: [...involved].sort()
18037
+ };
18038
+ }
18039
+ var HEURISTIC_WINDOW_MS, STALL_THRESHOLD_MS, CYCLE_COMPLETION_MIN_HOPS;
18040
+ var init_workflow_grouper = __esm({
18041
+ "src/coordination/workflow-grouper.ts"() {
18042
+ init_handoff_log();
18043
+ HEURISTIC_WINDOW_MS = 5 * 60 * 1e3;
18044
+ STALL_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
18045
+ CYCLE_COMPLETION_MIN_HOPS = 2;
18046
+ }
18047
+ });
17908
18048
 
17909
18049
  // src/coordination/handoff-routes.ts
17910
18050
  function writeJSON6(res, status, payload) {
@@ -17928,6 +18068,101 @@ function matchEntryRoute2(path) {
17928
18068
  if (rest.includes("/")) return null;
17929
18069
  return { entryId: decodeURIComponent(rest) };
17930
18070
  }
18071
+ function matchWorkflowRoute(path) {
18072
+ const prefix = `${COORDINATION_WORKFLOWS_PREFIX}/`;
18073
+ if (!path.startsWith(prefix)) return null;
18074
+ const rest = path.slice(prefix.length);
18075
+ if (rest.length === 0 || rest === "stream") return null;
18076
+ if (rest.includes("/")) return null;
18077
+ return { workflowId: decodeURIComponent(rest) };
18078
+ }
18079
+ async function computeWorkflowsAndTrackTransitions(deps) {
18080
+ const handoffs = await deps.handoffLog.query({ limit: 500 });
18081
+ const workflows = groupHandoffsIntoWorkflows(handoffs, {
18082
+ ...deps.now !== void 0 ? { now: deps.now() } : {}
18083
+ });
18084
+ const transitions = deps.workflowStateTracker ? deps.workflowStateTracker.observe(workflows) : [];
18085
+ for (const change of transitions) {
18086
+ deps.auditLog.append(
18087
+ "l2",
18088
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_STATE_CHANGED,
18089
+ deps.operatorId,
18090
+ {
18091
+ fortress_id: deps.handoffLog.getFortressId(),
18092
+ workflow_id: change.workflow_id,
18093
+ previous_state: change.previous_state,
18094
+ new_state: change.new_state
18095
+ }
18096
+ );
18097
+ }
18098
+ return { workflows, transitions };
18099
+ }
18100
+ function filterWorkflowList(workflows, opts) {
18101
+ let filtered = workflows;
18102
+ if (opts.state) {
18103
+ filtered = filtered.filter((w) => w.state === opts.state);
18104
+ }
18105
+ if (opts.agentId) {
18106
+ filtered = filtered.filter((w) => w.involved_agents.includes(opts.agentId));
18107
+ }
18108
+ if (opts.since) {
18109
+ filtered = filtered.filter((w) => w.last_activity_at >= opts.since);
18110
+ }
18111
+ return filtered.slice(0, opts.limit);
18112
+ }
18113
+ function isWorkflowState(value) {
18114
+ return value === "in_progress" || value === "completed" || value === "stalled" || value === "unknown";
18115
+ }
18116
+ async function handleWorkflowStream(deps, res) {
18117
+ res.writeHead(200, {
18118
+ "Content-Type": "text/event-stream",
18119
+ "Cache-Control": "no-cache, no-transform",
18120
+ Connection: "keep-alive",
18121
+ "X-Accel-Buffering": "no"
18122
+ });
18123
+ const initial = await computeWorkflowsAndTrackTransitions(deps);
18124
+ res.write(
18125
+ `event: workflow_snapshot
18126
+ data: ${JSON.stringify({ workflows: initial.workflows })}
18127
+
18128
+ `
18129
+ );
18130
+ if (initial.transitions.length > 0) {
18131
+ res.write(
18132
+ `event: workflow_state_changed
18133
+ data: ${JSON.stringify({ transitions: initial.transitions })}
18134
+
18135
+ `
18136
+ );
18137
+ }
18138
+ const unsubscribe = deps.events.subscribe(() => {
18139
+ void (async () => {
18140
+ try {
18141
+ const tick = await computeWorkflowsAndTrackTransitions(deps);
18142
+ res.write(
18143
+ `event: workflow_snapshot
18144
+ data: ${JSON.stringify({ workflows: tick.workflows })}
18145
+
18146
+ `
18147
+ );
18148
+ if (tick.transitions.length > 0) {
18149
+ res.write(
18150
+ `event: workflow_state_changed
18151
+ data: ${JSON.stringify({ transitions: tick.transitions })}
18152
+
18153
+ `
18154
+ );
18155
+ }
18156
+ } catch {
18157
+ }
18158
+ })();
18159
+ });
18160
+ const cleanup = () => {
18161
+ unsubscribe();
18162
+ };
18163
+ res.on("close", cleanup);
18164
+ res.on("error", cleanup);
18165
+ }
17931
18166
  async function handleStream3(deps, res) {
17932
18167
  res.writeHead(200, {
17933
18168
  "Content-Type": "text/event-stream",
@@ -18011,6 +18246,67 @@ async function handleCoordinationRoute(deps, req, res) {
18011
18246
  writeJSON6(res, 200, { ok: true, data: { entries } });
18012
18247
  return true;
18013
18248
  }
18249
+ if (method === "GET" && path === `${COORDINATION_WORKFLOWS_PREFIX}/stream`) {
18250
+ await handleWorkflowStream(deps, res);
18251
+ return true;
18252
+ }
18253
+ if (method === "GET" && path === COORDINATION_WORKFLOWS_PREFIX) {
18254
+ const limit = parseLimit4(
18255
+ url.searchParams.get("limit"),
18256
+ COORDINATION_LIST_DEFAULT_LIMIT,
18257
+ COORDINATION_LIST_MAX_LIMIT
18258
+ );
18259
+ const rawState = url.searchParams.get("state");
18260
+ const state = rawState && isWorkflowState(rawState) ? rawState : void 0;
18261
+ const since = url.searchParams.get("since") ?? void 0;
18262
+ const agentId = url.searchParams.get("agent_id") ?? void 0;
18263
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
18264
+ const filtered = filterWorkflowList(computed.workflows, {
18265
+ ...state !== void 0 ? { state } : {},
18266
+ ...agentId !== void 0 ? { agentId } : {},
18267
+ ...since !== void 0 ? { since } : {},
18268
+ limit
18269
+ });
18270
+ deps.auditLog.append(
18271
+ "l2",
18272
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_VIEW_OPENED,
18273
+ deps.operatorId,
18274
+ {
18275
+ fortress_id: deps.handoffLog.getFortressId(),
18276
+ result_count: filtered.length,
18277
+ ...state !== void 0 ? { state } : {},
18278
+ ...agentId !== void 0 ? { agent_id: agentId } : {},
18279
+ ...since !== void 0 ? { since } : {}
18280
+ }
18281
+ );
18282
+ writeJSON6(res, 200, { ok: true, data: { workflows: filtered } });
18283
+ return true;
18284
+ }
18285
+ const workflowMatch = matchWorkflowRoute(path);
18286
+ if (method === "GET" && workflowMatch) {
18287
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
18288
+ const wf = computed.workflows.find(
18289
+ (w) => w.workflow_id === workflowMatch.workflowId
18290
+ );
18291
+ if (!wf) {
18292
+ writeJSON6(res, 404, { ok: false, error: "not_found" });
18293
+ return true;
18294
+ }
18295
+ deps.auditLog.append(
18296
+ "l2",
18297
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_DRILLED,
18298
+ deps.operatorId,
18299
+ {
18300
+ fortress_id: deps.handoffLog.getFortressId(),
18301
+ workflow_id: wf.workflow_id,
18302
+ state: wf.state,
18303
+ member_count: wf.member_handoffs.length,
18304
+ involved_agent_count: wf.involved_agents.length
18305
+ }
18306
+ );
18307
+ writeJSON6(res, 200, { ok: true, data: { workflow: wf } });
18308
+ return true;
18309
+ }
18014
18310
  const entryMatch = matchEntryRoute2(path);
18015
18311
  if (method === "GET" && entryMatch) {
18016
18312
  const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
@@ -18063,14 +18359,16 @@ async function handleCoordinationRoute(deps, req, res) {
18063
18359
  return true;
18064
18360
  }
18065
18361
  }
18066
- var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
18362
+ var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_WORKFLOWS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
18067
18363
  var init_handoff_routes = __esm({
18068
18364
  "src/coordination/handoff-routes.ts"() {
18069
18365
  init_auth_middleware();
18070
18366
  init_handoff_log();
18071
18367
  init_context_transfer_extractor();
18368
+ init_workflow_grouper();
18072
18369
  COORDINATION_API_PREFIX = "/api/coordination";
18073
18370
  COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
18371
+ COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
18074
18372
  COORDINATION_LIST_DEFAULT_LIMIT = 50;
18075
18373
  COORDINATION_LIST_MAX_LIMIT = 500;
18076
18374
  HandoffEventBridge = class {
@@ -18189,6 +18487,8 @@ var init_dashboard = __esm({
18189
18487
  */
18190
18488
  handoffLog = null;
18191
18489
  handoffEventBridge = null;
18490
+ handoffContextTransfer = null;
18491
+ workflowStateTracker = null;
18192
18492
  handoffAuditLog = null;
18193
18493
  handoffOperatorId = null;
18194
18494
  constructor(config) {
@@ -18269,6 +18569,8 @@ var init_dashboard = __esm({
18269
18569
  this.handoffEventBridge = opts.eventBridge ?? null;
18270
18570
  this.handoffAuditLog = opts.auditLog ?? null;
18271
18571
  this.handoffOperatorId = opts.operatorId ?? null;
18572
+ this.handoffContextTransfer = opts.contextTransfer ?? null;
18573
+ this.workflowStateTracker = opts.workflowStateTracker ?? null;
18272
18574
  }
18273
18575
  /**
18274
18576
  * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
@@ -18326,7 +18628,9 @@ var init_dashboard = __esm({
18326
18628
  handoffLog: this.handoffLog,
18327
18629
  auditLog: this.handoffAuditLog,
18328
18630
  operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
18329
- events: this.handoffEventBridge
18631
+ events: this.handoffEventBridge,
18632
+ ...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
18633
+ ...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
18330
18634
  },
18331
18635
  req,
18332
18636
  res
@@ -22609,11 +22913,117 @@ var init_sentinel_dispatcher = __esm({
22609
22913
  };
22610
22914
  }
22611
22915
  });
22916
+
22917
+ // src/anomaly-detection/classifier-state-store.ts
22918
+ function stateKey(classifierId, agentId) {
22919
+ return `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.${agentId}`;
22920
+ }
22921
+ function aadFor(classifierId, agentId) {
22922
+ return `${classifierId}|${agentId}`;
22923
+ }
22924
+ var ANOMALY_CLASSIFIER_STATE_NAMESPACE, ANOMALY_CLASSIFIER_STATE_KEY_PREFIX, HKDF_INFO3, MAX_STATE_BYTES, ClassifierStateStore;
22612
22925
  var init_classifier_state_store = __esm({
22613
22926
  "src/anomaly-detection/classifier-state-store.ts"() {
22614
22927
  init_encryption();
22615
22928
  init_key_derivation();
22616
22929
  init_encoding();
22930
+ ANOMALY_CLASSIFIER_STATE_NAMESPACE = "_anomaly_classifier_state";
22931
+ ANOMALY_CLASSIFIER_STATE_KEY_PREFIX = "state.";
22932
+ HKDF_INFO3 = "l2-anomaly-classifier-state-v1";
22933
+ MAX_STATE_BYTES = 256 * 1024;
22934
+ ClassifierStateStore = class {
22935
+ storage;
22936
+ encryptionKey;
22937
+ fortressId;
22938
+ now;
22939
+ constructor(opts) {
22940
+ this.storage = opts.storage;
22941
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
22942
+ this.fortressId = opts.fortressId;
22943
+ this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
22944
+ }
22945
+ async saveState(classifierId, agentId, state) {
22946
+ const persisted = {
22947
+ version: 1,
22948
+ classifier_id: classifierId,
22949
+ agent_id: agentId,
22950
+ fortress_id: this.fortressId,
22951
+ saved_at: this.now().toISOString(),
22952
+ state
22953
+ };
22954
+ const aadString = aadFor(classifierId, agentId);
22955
+ const aad = stringToBytes(aadString);
22956
+ const plaintext = stringToBytes(JSON.stringify(persisted));
22957
+ const envelope = encrypt(plaintext, this.encryptionKey, aad);
22958
+ await this.storage.write(
22959
+ ANOMALY_CLASSIFIER_STATE_NAMESPACE,
22960
+ stateKey(classifierId, agentId),
22961
+ stringToBytes(JSON.stringify(envelope))
22962
+ );
22963
+ }
22964
+ async loadState(classifierId, agentId) {
22965
+ const key = stateKey(classifierId, agentId);
22966
+ let raw;
22967
+ try {
22968
+ raw = await this.storage.read(
22969
+ ANOMALY_CLASSIFIER_STATE_NAMESPACE,
22970
+ key
22971
+ );
22972
+ } catch {
22973
+ return null;
22974
+ }
22975
+ if (!raw) return null;
22976
+ if (raw.length > MAX_STATE_BYTES) return null;
22977
+ try {
22978
+ const aad = stringToBytes(aadFor(classifierId, agentId));
22979
+ const envelope = JSON.parse(bytesToString(raw));
22980
+ const plaintext = decrypt(envelope, this.encryptionKey, aad);
22981
+ const persisted = JSON.parse(
22982
+ bytesToString(plaintext)
22983
+ );
22984
+ if (persisted.version !== 1) return null;
22985
+ if (persisted.classifier_id !== classifierId) return null;
22986
+ if (persisted.agent_id !== agentId) return null;
22987
+ if (persisted.fortress_id !== this.fortressId) return null;
22988
+ return persisted.state;
22989
+ } catch {
22990
+ return null;
22991
+ }
22992
+ }
22993
+ /** Delete a single classifier-agent state record. */
22994
+ async deleteState(classifierId, agentId) {
22995
+ const key = stateKey(classifierId, agentId);
22996
+ const existed = await this.storage.exists(
22997
+ ANOMALY_CLASSIFIER_STATE_NAMESPACE,
22998
+ key
22999
+ );
23000
+ if (!existed) return false;
23001
+ try {
23002
+ await this.storage.delete(ANOMALY_CLASSIFIER_STATE_NAMESPACE, key);
23003
+ } catch {
23004
+ return false;
23005
+ }
23006
+ return true;
23007
+ }
23008
+ /**
23009
+ * List the (classifier_id, agent_id) tuples currently persisted.
23010
+ * Returns the agent ids for one classifier when classifierId is
23011
+ * given.
23012
+ */
23013
+ async listAgents(classifierId) {
23014
+ const metas = await this.storage.list(
23015
+ ANOMALY_CLASSIFIER_STATE_NAMESPACE,
23016
+ ANOMALY_CLASSIFIER_STATE_KEY_PREFIX
23017
+ );
23018
+ const prefix = `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.`;
23019
+ const out = [];
23020
+ for (const meta of metas) {
23021
+ if (!meta.key.startsWith(prefix)) continue;
23022
+ out.push(meta.key.slice(prefix.length));
23023
+ }
23024
+ return out;
23025
+ }
23026
+ };
22617
23027
  }
22618
23028
  });
22619
23029
 
@@ -22636,8 +23046,157 @@ var init_psi = __esm({
22636
23046
  });
22637
23047
 
22638
23048
  // src/anomaly-detection/types.ts
23049
+ function severityFromAnomalyScore(score) {
23050
+ if (!Number.isFinite(score)) return null;
23051
+ if (score < 1) return null;
23052
+ if (score < 3) return "info";
23053
+ if (score < 6) return "warn";
23054
+ return "alert";
23055
+ }
23056
+ function buildAnomalyFinding(detector, classifier, vector, prediction, severity) {
23057
+ const summary = formatAnomalySummary(
23058
+ detector,
23059
+ classifier,
23060
+ vector,
23061
+ prediction,
23062
+ severity
23063
+ );
23064
+ return {
23065
+ finding_id: "",
23066
+ sentinel_id: `${ANOMALY_SENTINEL_ID_PREFIX}${detector.detectorId}`,
23067
+ severity,
23068
+ summary,
23069
+ details: {
23070
+ detector_id: detector.detectorId,
23071
+ classifier_id: classifier.classifierId,
23072
+ anomaly_score: prediction.anomaly_score,
23073
+ window_label: vector.window_label,
23074
+ observed_features: vector.features,
23075
+ feature_contributions: prediction.feature_contributions,
23076
+ explanation: prediction.explanation
23077
+ },
23078
+ observed_at: vector.observed_at,
23079
+ agent_id: vector.agent_id,
23080
+ evidence_audit_ids: [],
23081
+ fortress_id: ""
23082
+ };
23083
+ }
23084
+ function formatAnomalySummary(detector, classifier, vector, prediction, severity) {
23085
+ const top = prediction.explanation.slice(0, 3).join("; ");
23086
+ return `${detector.detectorId}/${classifier.classifierId} ${severity}: agent ${vector.agent_id} drifted ${prediction.anomaly_score.toFixed(2)} sigma from baseline. Top contributors: ${top || "(none)"}.`;
23087
+ }
23088
+ var AnomalyDetector, ANOMALY_SENTINEL_ID_PREFIX;
22639
23089
  var init_types4 = __esm({
22640
23090
  "src/anomaly-detection/types.ts"() {
23091
+ AnomalyDetector = class {
23092
+ /**
23093
+ * Additional classifiers attached post-construction. Keyed by
23094
+ * classifierId so subscribe/unsubscribe is idempotent. Primary
23095
+ * `classifier` is NOT stored here.
23096
+ */
23097
+ additionalClassifiers = /* @__PURE__ */ new Map();
23098
+ /**
23099
+ * Attach an additional classifier. Idempotent: a second call with
23100
+ * the same classifierId returns false. The primary classifier
23101
+ * cannot be re-attached as additional (returns false). The
23102
+ * dispatcher emits ANOMALY_CLASSIFIER_SUBSCRIBED on success.
23103
+ */
23104
+ addClassifier(classifier) {
23105
+ if (classifier.classifierId === this.classifier.classifierId) return false;
23106
+ if (this.additionalClassifiers.has(classifier.classifierId)) return false;
23107
+ this.additionalClassifiers.set(classifier.classifierId, classifier);
23108
+ return true;
23109
+ }
23110
+ /**
23111
+ * Detach an additional classifier by id. Cannot remove the primary
23112
+ * (returns false). Returns true when an existing additional
23113
+ * classifier was removed. The dispatcher emits
23114
+ * ANOMALY_CLASSIFIER_UNSUBSCRIBED on success.
23115
+ */
23116
+ removeClassifier(classifierId) {
23117
+ if (classifierId === this.classifier.classifierId) return false;
23118
+ return this.additionalClassifiers.delete(classifierId);
23119
+ }
23120
+ /** List every classifier id attached: primary first, then additionals. */
23121
+ listClassifierIds() {
23122
+ return [
23123
+ this.classifier.classifierId,
23124
+ ...this.additionalClassifiers.keys()
23125
+ ];
23126
+ }
23127
+ /**
23128
+ * Return every attached classifier: primary first, then additionals
23129
+ * in insertion order. Used by evaluate() and the dispatcher's train
23130
+ * + audit emission.
23131
+ */
23132
+ getAllClassifiers() {
23133
+ return [this.classifier, ...this.additionalClassifiers.values()];
23134
+ }
23135
+ /**
23136
+ * Bind the detector to a fortress context. Default stores it on
23137
+ * `this`; subclasses with priming logic override.
23138
+ */
23139
+ async subscribe(context) {
23140
+ this.context = context;
23141
+ }
23142
+ async unsubscribe() {
23143
+ this.context = void 0;
23144
+ this.additionalClassifiers.clear();
23145
+ }
23146
+ /**
23147
+ * One evaluation pass. Default impl: extract -> for each classifier
23148
+ * attached, predict (drift against that classifier's prior
23149
+ * baseline) -> observe (only when the prediction is in-baseline,
23150
+ * so outliers do not contaminate the rolling baseline and pull
23151
+ * future predictions toward themselves) -> emit findings above
23152
+ * threshold. Multi-classifier evaluation is per-classifier: each
23153
+ * decides independently whether to absorb or emit. Subclasses with
23154
+ * custom routing override.
23155
+ *
23156
+ * Predict-then-observe (with conditional observe) is the standard
23157
+ * online anomaly-detection pattern. Chi-1 spawn prompt called for
23158
+ * observe-then-predict; CTO call: changed to predict-then-observe
23159
+ * because observe-then-predict measures the sample against itself
23160
+ * after one-sample contamination, which is structurally incorrect
23161
+ * for drift detection. Chi-2 preserves that invariant on a per-
23162
+ * classifier basis (each classifier's observe is conditional on its
23163
+ * own predict result).
23164
+ */
23165
+ async evaluate() {
23166
+ const ctx = this.requireContext();
23167
+ const vectors = await this.featureExtract(ctx);
23168
+ const findings = [];
23169
+ const classifiers = this.getAllClassifiers();
23170
+ for (const vector of vectors) {
23171
+ for (const classifier of classifiers) {
23172
+ const prediction = await classifier.predict(vector);
23173
+ if (!prediction.baseline_ready) {
23174
+ await classifier.observe(vector);
23175
+ continue;
23176
+ }
23177
+ const severity = severityFromAnomalyScore(prediction.anomaly_score);
23178
+ if (severity === null) {
23179
+ await classifier.observe(vector);
23180
+ continue;
23181
+ }
23182
+ findings.push(
23183
+ buildAnomalyFinding(this, classifier, vector, prediction, severity)
23184
+ );
23185
+ }
23186
+ }
23187
+ return findings;
23188
+ }
23189
+ context;
23190
+ requireContext() {
23191
+ if (!this.context) {
23192
+ throw new Error(
23193
+ `anomaly-detector ${this.detectorId}: evaluate() called before subscribe()`
23194
+ );
23195
+ }
23196
+ return this.context;
23197
+ }
23198
+ };
23199
+ ANOMALY_SENTINEL_ID_PREFIX = "anomaly:";
22641
23200
  }
22642
23201
  });
22643
23202
  function classifierSpecificAuditOp(classifierId) {
@@ -22949,6 +23508,75 @@ var init_anomaly_pipeline = __esm({
22949
23508
  }
22950
23509
  });
22951
23510
 
23511
+ // src/coordination/workflow-state-tracker.ts
23512
+ var WorkflowStateTracker;
23513
+ var init_workflow_state_tracker = __esm({
23514
+ "src/coordination/workflow-state-tracker.ts"() {
23515
+ WorkflowStateTracker = class {
23516
+ states = /* @__PURE__ */ new Map();
23517
+ now;
23518
+ constructor(opts) {
23519
+ this.now = opts?.now ?? (() => /* @__PURE__ */ new Date());
23520
+ }
23521
+ /**
23522
+ * Diff the supplied workflow list against the last-observed states.
23523
+ * Returns the set of transitions detected this call; the tracker
23524
+ * mutates its internal map to reflect the new states.
23525
+ *
23526
+ * Transitions emitted:
23527
+ * - First observation of a workflow (`previous_state` is the
23528
+ * sentinel `unobserved`). Lets the route handler audit-emit
23529
+ * the initial state so the operator sees workflows as they
23530
+ * surface, not only when they change.
23531
+ * - Subsequent observation where `previous_state !== new_state`.
23532
+ */
23533
+ observe(workflows) {
23534
+ const out = [];
23535
+ const observedAt = this.now().toISOString();
23536
+ for (const wf of workflows) {
23537
+ const prior = this.states.get(wf.workflow_id);
23538
+ if (prior === void 0) {
23539
+ out.push({
23540
+ workflow_id: wf.workflow_id,
23541
+ previous_state: "unobserved",
23542
+ new_state: wf.state,
23543
+ observed_at: observedAt
23544
+ });
23545
+ this.states.set(wf.workflow_id, wf.state);
23546
+ continue;
23547
+ }
23548
+ if (prior !== wf.state) {
23549
+ out.push({
23550
+ workflow_id: wf.workflow_id,
23551
+ previous_state: prior,
23552
+ new_state: wf.state,
23553
+ observed_at: observedAt
23554
+ });
23555
+ this.states.set(wf.workflow_id, wf.state);
23556
+ }
23557
+ }
23558
+ return out;
23559
+ }
23560
+ /**
23561
+ * Drop a workflow's recorded state. Surfaced for tests + future
23562
+ * "operator dismissed this workflow" affordance; not currently
23563
+ * called by the production wiring.
23564
+ */
23565
+ forget(workflowId) {
23566
+ this.states.delete(workflowId);
23567
+ }
23568
+ /** Reset the tracker. Tests use this between runs. */
23569
+ reset() {
23570
+ this.states.clear();
23571
+ }
23572
+ /** Read-only view of the current snapshot. Useful for diagnostics. */
23573
+ snapshot() {
23574
+ return new Map(this.states);
23575
+ }
23576
+ };
23577
+ }
23578
+ });
23579
+
22952
23580
  // src/sentinel/sentinel.ts
22953
23581
  var Sentinel;
22954
23582
  var init_sentinel = __esm({
@@ -38660,7 +39288,7 @@ ${runningLines.join("\n")}`;
38660
39288
  function chatStorageKey(surface, threadKey) {
38661
39289
  return `${surface}.${threadKey}`;
38662
39290
  }
38663
- var OPERATOR_CHAT_NAMESPACE, HKDF_INFO3, OperatorChatStore;
39291
+ var OPERATOR_CHAT_NAMESPACE, HKDF_INFO4, OperatorChatStore;
38664
39292
  var init_operator_chat_store = __esm({
38665
39293
  "src/chat/operator-chat-store.ts"() {
38666
39294
  init_encryption();
@@ -38668,13 +39296,13 @@ var init_operator_chat_store = __esm({
38668
39296
  init_encoding();
38669
39297
  init_operator_chat_types();
38670
39298
  OPERATOR_CHAT_NAMESPACE = "_chat";
38671
- HKDF_INFO3 = "operator-chat-store-v1";
39299
+ HKDF_INFO4 = "operator-chat-store-v1";
38672
39300
  OperatorChatStore = class {
38673
39301
  storage;
38674
39302
  encryptionKey;
38675
39303
  constructor(storage, masterKey) {
38676
39304
  this.storage = storage;
38677
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO3);
39305
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
38678
39306
  }
38679
39307
  /**
38680
39308
  * Load a thread. Returns null if no record exists or if the on-disk
@@ -38770,7 +39398,7 @@ function lastTurnId(bundle) {
38770
39398
  }
38771
39399
  return max;
38772
39400
  }
38773
- var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO4, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
39401
+ var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO5, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
38774
39402
  var init_concierge_memory_store = __esm({
38775
39403
  "src/chat/concierge-memory-store.ts"() {
38776
39404
  init_encryption();
@@ -38778,7 +39406,7 @@ var init_concierge_memory_store = __esm({
38778
39406
  init_encoding();
38779
39407
  CONCIERGE_MEMORY_NAMESPACE = "_chat";
38780
39408
  CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
38781
- HKDF_INFO4 = "concierge-memory-store-v1";
39409
+ HKDF_INFO5 = "concierge-memory-store-v1";
38782
39410
  DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
38783
39411
  MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
38784
39412
  ConciergeMemoryStore = class {
@@ -38789,7 +39417,7 @@ var init_concierge_memory_store = __esm({
38789
39417
  locks;
38790
39418
  constructor(opts) {
38791
39419
  this.storage = opts.storage;
38792
- this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO4);
39420
+ this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO5);
38793
39421
  this.fortressId = opts.fortressId;
38794
39422
  this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
38795
39423
  this.locks = /* @__PURE__ */ new Map();
@@ -39438,7 +40066,7 @@ var init_defaults = __esm({
39438
40066
  });
39439
40067
 
39440
40068
  // src/intelligence/policy-store.ts
39441
- var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO5, IntelligenceConfigStore;
40069
+ var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO6, IntelligenceConfigStore;
39442
40070
  var init_policy_store = __esm({
39443
40071
  "src/intelligence/policy-store.ts"() {
39444
40072
  init_encryption();
@@ -39447,13 +40075,13 @@ var init_policy_store = __esm({
39447
40075
  init_defaults();
39448
40076
  INTELLIGENCE_NAMESPACE = "_intelligence";
39449
40077
  SUBSTRATE_CONFIG_KEY = "substrate-config";
39450
- HKDF_INFO5 = "intelligence-substrate-config";
40078
+ HKDF_INFO6 = "intelligence-substrate-config";
39451
40079
  IntelligenceConfigStore = class {
39452
40080
  storage;
39453
40081
  encryptionKey;
39454
40082
  constructor(storage, masterKey) {
39455
40083
  this.storage = storage;
39456
- this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO5);
40084
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO6);
39457
40085
  }
39458
40086
  /**
39459
40087
  * Load the operator's substrate config from disk. Returns the config
@@ -41268,6 +41896,247 @@ var init_constants5 = __esm({
41268
41896
  ];
41269
41897
  }
41270
41898
  });
41899
+ async function issueDidWeb(opts) {
41900
+ if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
41901
+ throw new Error(
41902
+ `did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
41903
+ );
41904
+ }
41905
+ if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
41906
+ throw new Error(
41907
+ `did-web: fortress_id '${opts.fortress_id}' is not a valid label`
41908
+ );
41909
+ }
41910
+ if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
41911
+ throw new Error(
41912
+ `did-web: agent_label '${opts.agent_label}' is not a valid label`
41913
+ );
41914
+ }
41915
+ if (opts.public_key.length !== 32) {
41916
+ throw new Error(
41917
+ `did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
41918
+ );
41919
+ }
41920
+ const did = buildDid(opts);
41921
+ const verificationMethodId = `${did}#key-1`;
41922
+ const verificationMethod = {
41923
+ id: verificationMethodId,
41924
+ type: "JsonWebKey2020",
41925
+ controller: did,
41926
+ publicKeyJwk: {
41927
+ kty: "OKP",
41928
+ crv: "Ed25519",
41929
+ x: toBase64url(opts.public_key)
41930
+ }
41931
+ };
41932
+ const didDocument = {
41933
+ "@context": [...DID_CONTEXT],
41934
+ id: did,
41935
+ verificationMethod: [verificationMethod],
41936
+ authentication: [verificationMethodId],
41937
+ assertionMethod: [verificationMethodId]
41938
+ };
41939
+ const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
41940
+ return {
41941
+ did,
41942
+ did_document: didDocument,
41943
+ public_key: opts.public_key,
41944
+ created_at: now.toISOString(),
41945
+ authority_host: opts.authority_host,
41946
+ fortress_id: opts.fortress_id,
41947
+ ...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
41948
+ };
41949
+ }
41950
+ function publishDidWebDocument(identifier, opts = {}) {
41951
+ const path = opts.publish_path ?? canonicalPublishPath(identifier);
41952
+ const artifact = canonicalSerializeDidDocument(identifier.did_document);
41953
+ const digest = sha256.sha256(stringToBytes(artifact));
41954
+ const url = `https://${identifier.authority_host}${path}`;
41955
+ return {
41956
+ url,
41957
+ publish_path: path,
41958
+ artifact,
41959
+ sha256: hashToString(digest)
41960
+ };
41961
+ }
41962
+ async function resolveDidWeb(did, opts) {
41963
+ const parsed = parseDidWeb(did);
41964
+ const url = didToUrl(parsed);
41965
+ if (!opts.allowed_hosts.includes(parsed.authority_host)) {
41966
+ return {
41967
+ ok: false,
41968
+ failure: "host_not_allowed",
41969
+ message: `did-web: authority_host '${parsed.authority_host}' is not in the operator's allowed_hosts allowlist; resolution refused (no-outbound-by-default)`,
41970
+ url
41971
+ };
41972
+ }
41973
+ const timeoutMs = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
41974
+ const fetcher = opts.fetcher ?? defaultFetcher;
41975
+ const controller = new AbortController();
41976
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
41977
+ let response;
41978
+ try {
41979
+ response = await fetcher(url, { signal: controller.signal });
41980
+ } catch (err) {
41981
+ clearTimeout(timer);
41982
+ const message = err instanceof Error ? err.message : String(err);
41983
+ if (controller.signal.aborted) {
41984
+ return {
41985
+ ok: false,
41986
+ failure: "timeout",
41987
+ message: `did-web: resolution exceeded ${timeoutMs}ms`,
41988
+ url
41989
+ };
41990
+ }
41991
+ return {
41992
+ ok: false,
41993
+ failure: "fetch_failed",
41994
+ message: `did-web: fetch error: ${message}`,
41995
+ url
41996
+ };
41997
+ }
41998
+ clearTimeout(timer);
41999
+ if (response.status === 404) {
42000
+ return {
42001
+ ok: false,
42002
+ failure: "not_found",
42003
+ message: `did-web: 404 from authority host`,
42004
+ url
42005
+ };
42006
+ }
42007
+ if (!response.ok) {
42008
+ return {
42009
+ ok: false,
42010
+ failure: "fetch_failed",
42011
+ message: `did-web: authority host returned ${response.status}`,
42012
+ url
42013
+ };
42014
+ }
42015
+ let body;
42016
+ try {
42017
+ body = await response.json();
42018
+ } catch (err) {
42019
+ const message = err instanceof Error ? err.message : String(err);
42020
+ return {
42021
+ ok: false,
42022
+ failure: "invalid_json",
42023
+ message: `did-web: invalid JSON: ${message}`,
42024
+ url
42025
+ };
42026
+ }
42027
+ if (!isDidDocument(body, did)) {
42028
+ return {
42029
+ ok: false,
42030
+ failure: "invalid_json",
42031
+ message: `did-web: response body is not a valid DID Document for ${did}`,
42032
+ url
42033
+ };
42034
+ }
42035
+ if (opts.expected_public_key !== void 0) {
42036
+ const expectedX = toBase64url(opts.expected_public_key);
42037
+ const actualX = body.verificationMethod[0]?.publicKeyJwk.x;
42038
+ if (actualX !== expectedX) {
42039
+ return {
42040
+ ok: false,
42041
+ failure: "signature_mismatch",
42042
+ message: `did-web: verificationMethod public key does not match expected key`,
42043
+ url
42044
+ };
42045
+ }
42046
+ }
42047
+ return { ok: true, did_document: body, url };
42048
+ }
42049
+ function parseDidWeb(did) {
42050
+ if (!did.startsWith("did:web:")) {
42051
+ throw new Error(`did-web: '${did}' is not a did:web identifier`);
42052
+ }
42053
+ const rest = did.slice("did:web:".length);
42054
+ const segments = rest.split(":");
42055
+ const authorityHost = segments[0];
42056
+ if (!HOST_RE.test(authorityHost)) {
42057
+ throw new Error(`did-web: '${authorityHost}' is not a valid DNS host`);
42058
+ }
42059
+ const parsed = { authority_host: authorityHost };
42060
+ if (segments.length === 1) return parsed;
42061
+ if (segments.length === 5 && segments[1] === "fortress" && segments[3] === "agent") {
42062
+ parsed.fortress_id = segments[2];
42063
+ parsed.agent_label = segments[4];
42064
+ return parsed;
42065
+ }
42066
+ throw new Error(
42067
+ `did-web: '${did}' does not match the supported shapes (bare did:web:<host> or did:web:<host>:fortress:<fid>:agent:<alabel>)`
42068
+ );
42069
+ }
42070
+ function didToUrl(parsed) {
42071
+ if (parsed.fortress_id === void 0 || parsed.agent_label === void 0) {
42072
+ return `https://${parsed.authority_host}/.well-known/did.json`;
42073
+ }
42074
+ return `https://${parsed.authority_host}/fortress/${parsed.fortress_id}/agent/${parsed.agent_label}/did.json`;
42075
+ }
42076
+ function buildDid(opts) {
42077
+ if (opts.agent_label === void 0) {
42078
+ return `did:web:${opts.authority_host}`;
42079
+ }
42080
+ return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
42081
+ }
42082
+ function canonicalPublishPath(identifier) {
42083
+ if (identifier.agent_label === void 0) {
42084
+ return "/.well-known/did.json";
42085
+ }
42086
+ return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
42087
+ }
42088
+ function canonicalSerializeDidDocument(doc) {
42089
+ return JSON.stringify(
42090
+ {
42091
+ "@context": doc["@context"],
42092
+ id: doc.id,
42093
+ verificationMethod: doc.verificationMethod,
42094
+ authentication: doc.authentication,
42095
+ assertionMethod: doc.assertionMethod
42096
+ },
42097
+ null,
42098
+ 2
42099
+ );
42100
+ }
42101
+ function isDidDocument(value, expectedDid) {
42102
+ if (!value || typeof value !== "object") return false;
42103
+ const v = value;
42104
+ if (v["id"] !== expectedDid) return false;
42105
+ if (!Array.isArray(v["@context"])) return false;
42106
+ const vm = v["verificationMethod"];
42107
+ if (!Array.isArray(vm) || vm.length === 0) return false;
42108
+ const first = vm[0];
42109
+ if (!first || typeof first["id"] !== "string") return false;
42110
+ const jwk = first["publicKeyJwk"];
42111
+ if (!jwk || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519") return false;
42112
+ if (typeof jwk["x"] !== "string") return false;
42113
+ if (!Array.isArray(v["authentication"])) return false;
42114
+ if (!Array.isArray(v["assertionMethod"])) return false;
42115
+ return true;
42116
+ }
42117
+ async function defaultFetcher(url, init) {
42118
+ const response = await fetch(url, init);
42119
+ return {
42120
+ ok: response.ok,
42121
+ status: response.status,
42122
+ json: () => response.json()
42123
+ };
42124
+ }
42125
+ var DID_CONTEXT, DEFAULT_TIMEOUT_MS4, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
42126
+ var init_did_web = __esm({
42127
+ "src/recognition/did-web.ts"() {
42128
+ init_encoding();
42129
+ init_hashing();
42130
+ DID_CONTEXT = [
42131
+ "https://www.w3.org/ns/did/v1",
42132
+ "https://w3id.org/security/suites/jws-2020/v1"
42133
+ ];
42134
+ DEFAULT_TIMEOUT_MS4 = 5e3;
42135
+ 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;
42136
+ FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
42137
+ AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
42138
+ }
42139
+ });
41271
42140
 
41272
42141
  // src/contracts/v1.1/exit-bundle-manifest.ts
41273
42142
  var EXIT_BUNDLE_PATH_PATTERN, EXIT_BUNDLE_PATH_MAX_BYTES;
@@ -41971,6 +42840,7 @@ async function exportExitBundle(opts) {
41971
42840
  "placeholder_vault_metadata"
41972
42841
  )
41973
42842
  );
42843
+ const didWebBinding = validateExportDidWeb(opts.didWeb);
41974
42844
  const body = {
41975
42845
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
41976
42846
  exported_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -41978,7 +42848,8 @@ async function exportExitBundle(opts) {
41978
42848
  identity_id: identity.identity_id,
41979
42849
  fortress_id: identity.did,
41980
42850
  fortress_master_pubkey: identity.public_key,
41981
- did: identity.did
42851
+ did: identity.did,
42852
+ ...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
41982
42853
  },
41983
42854
  source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
41984
42855
  artifacts,
@@ -42000,6 +42871,18 @@ async function exportExitBundle(opts) {
42000
42871
  };
42001
42872
  const manifestBytes = jsonBytes(manifest);
42002
42873
  await promises.writeFile(path.join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
42874
+ if (didWebBinding !== void 0) {
42875
+ opts.auditLog.append(
42876
+ "l1",
42877
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.EXPORT_INCLUDED,
42878
+ identity.identity_id,
42879
+ {
42880
+ approval_id: exportApprovalAuditId,
42881
+ identifier: didWebBinding.identifier,
42882
+ authority_host: didWebBinding.authority_host
42883
+ }
42884
+ );
42885
+ }
42003
42886
  await opts.auditLog.flush();
42004
42887
  return {
42005
42888
  bundle_dir: bundleDir,
@@ -42011,6 +42894,30 @@ async function exportExitBundle(opts) {
42011
42894
  ]
42012
42895
  };
42013
42896
  }
42897
+ function validateExportDidWeb(binding) {
42898
+ if (binding === void 0) return void 0;
42899
+ if (!binding.identifier || typeof binding.identifier !== "string") {
42900
+ throw new Error(
42901
+ "exit-bundle: did_web.identifier must be a non-empty did:web URI"
42902
+ );
42903
+ }
42904
+ if (!binding.authority_host || typeof binding.authority_host !== "string") {
42905
+ throw new Error(
42906
+ "exit-bundle: did_web.authority_host must be a non-empty DNS host"
42907
+ );
42908
+ }
42909
+ const parsed = parseDidWeb(binding.identifier);
42910
+ if (parsed.authority_host.toLowerCase() !== binding.authority_host.toLowerCase()) {
42911
+ throw new Error(
42912
+ `exit-bundle: did_web.identifier authority host '${parsed.authority_host}' does not match did_web.authority_host '${binding.authority_host}'`
42913
+ );
42914
+ }
42915
+ return {
42916
+ identifier: binding.identifier,
42917
+ authority_host: binding.authority_host,
42918
+ ...binding.published_at !== void 0 ? { published_at: binding.published_at } : {}
42919
+ };
42920
+ }
42014
42921
  function publicKeysFromIdentityArtifact(identityArtifact) {
42015
42922
  const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
42016
42923
  return {
@@ -42225,6 +43132,87 @@ async function importExitBundle(opts) {
42225
43132
  };
42226
43133
  }
42227
43134
  const manifest = await readManifest(opts.bundleDir);
43135
+ const importWarnings = [];
43136
+ const manifestDidWeb = manifest.body.identity_binding.did_web;
43137
+ if (manifestDidWeb !== void 0 && !opts.skipDidWebVerify) {
43138
+ const expectedPublicKey = fromBase64url(
43139
+ manifest.body.identity_binding.fortress_master_pubkey
43140
+ );
43141
+ const resolveOpts = {
43142
+ allowed_hosts: opts.didWebAllowedHosts ?? [],
43143
+ expected_public_key: expectedPublicKey,
43144
+ ...opts.didWebFetcher !== void 0 ? { fetcher: opts.didWebFetcher } : {},
43145
+ ...opts.didWebTimeoutMs !== void 0 ? { timeout_ms: opts.didWebTimeoutMs } : {}
43146
+ };
43147
+ const resolution = await resolveDidWeb(
43148
+ manifestDidWeb.identifier,
43149
+ resolveOpts
43150
+ );
43151
+ const authorityHost = manifestDidWeb.authority_host;
43152
+ opts.auditLog.append(
43153
+ "l1",
43154
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.AUTHORITY_HOST,
43155
+ manifest.body.identity_binding.identity_id,
43156
+ { authority_host: authorityHost, identifier: manifestDidWeb.identifier }
43157
+ );
43158
+ if (resolution.ok) {
43159
+ opts.auditLog.append(
43160
+ "l1",
43161
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
43162
+ manifest.body.identity_binding.identity_id,
43163
+ {
43164
+ outcome: "success",
43165
+ identifier: manifestDidWeb.identifier,
43166
+ authority_host: authorityHost,
43167
+ resolved_url: resolution.url
43168
+ }
43169
+ );
43170
+ } else if (resolution.failure === "signature_mismatch") {
43171
+ opts.auditLog.append(
43172
+ "l1",
43173
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
43174
+ manifest.body.identity_binding.identity_id,
43175
+ {
43176
+ outcome: "mismatch",
43177
+ identifier: manifestDidWeb.identifier,
43178
+ authority_host: authorityHost,
43179
+ resolved_url: resolution.url
43180
+ }
43181
+ );
43182
+ await opts.auditLog.flush();
43183
+ throw new ExitBundleImportError(
43184
+ "did_web_mismatch",
43185
+ `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.`
43186
+ );
43187
+ } else {
43188
+ opts.auditLog.append(
43189
+ "l1",
43190
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
43191
+ manifest.body.identity_binding.identity_id,
43192
+ {
43193
+ outcome: "resolution_failure",
43194
+ failure: resolution.failure,
43195
+ identifier: manifestDidWeb.identifier,
43196
+ authority_host: authorityHost,
43197
+ resolved_url: resolution.url
43198
+ }
43199
+ );
43200
+ importWarnings.push(
43201
+ `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.`
43202
+ );
43203
+ }
43204
+ } else if (manifestDidWeb !== void 0 && opts.skipDidWebVerify) {
43205
+ opts.auditLog.append(
43206
+ "l1",
43207
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
43208
+ manifest.body.identity_binding.identity_id,
43209
+ {
43210
+ outcome: "skipped",
43211
+ identifier: manifestDidWeb.identifier,
43212
+ authority_host: manifestDidWeb.authority_host
43213
+ }
43214
+ );
43215
+ }
42228
43216
  const identityArtifact = await loadExitArtifact(
42229
43217
  opts.bundleDir,
42230
43218
  manifest,
@@ -42289,7 +43277,7 @@ async function importExitBundle(opts) {
42289
43277
  unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
42290
43278
  },
42291
43279
  staged_artifacts: [],
42292
- warnings: verification.warnings,
43280
+ warnings: [...verification.warnings, ...importWarnings],
42293
43281
  unsupported_artifacts: verification.unsupported_artifacts
42294
43282
  };
42295
43283
  }
@@ -42456,7 +43444,7 @@ async function importExitBundle(opts) {
42456
43444
  state: stateResult,
42457
43445
  reputation: reputationResult,
42458
43446
  staged_artifacts: stagedArtifacts,
42459
- warnings: verification.warnings,
43447
+ warnings: [...verification.warnings, ...importWarnings],
42460
43448
  unsupported_artifacts: verification.unsupported_artifacts
42461
43449
  };
42462
43450
  }
@@ -42478,12 +43466,13 @@ function exitBundleManifestShape() {
42478
43466
  ]
42479
43467
  };
42480
43468
  }
42481
- 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;
43469
+ 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;
42482
43470
  var init_bundle = __esm({
42483
43471
  "src/exit/bundle.ts"() {
42484
43472
  init_state_store();
42485
43473
  init_config();
42486
43474
  init_constants5();
43475
+ init_did_web();
42487
43476
  init_canonical_json();
42488
43477
  init_hashing();
42489
43478
  init_encoding();
@@ -42493,6 +43482,11 @@ var init_bundle = __esm({
42493
43482
  init_reputation_store();
42494
43483
  init_verifier2();
42495
43484
  ARTIFACT_DIR = "artifacts";
43485
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS = {
43486
+ EXPORT_INCLUDED: "exit_bundle_did_web_export_included",
43487
+ IMPORT_VERIFIED: "exit_bundle_did_web_import_verified",
43488
+ AUTHORITY_HOST: "exit_bundle_did_web_authority_host"
43489
+ };
42496
43490
  EXIT_IMPORT_NAMESPACE = "_exit_imports";
42497
43491
  EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
42498
43492
  EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
@@ -42711,6 +43705,26 @@ ${policyErr.message}
42711
43705
  }
42712
43706
  throw policyErr;
42713
43707
  }
43708
+ const includeDidWebFlag = flagValue(argv, "--include-did-web");
43709
+ const includeDidWebDisabled = includeDidWebFlag === "false";
43710
+ const didWebIdentifier = flagValue(argv, "--did-web");
43711
+ const didWebAuthorityHost = flagValue(argv, "--did-web-authority-host");
43712
+ const didWebPublishedAt = flagValue(argv, "--did-web-published-at");
43713
+ let exportDidWeb;
43714
+ if (!includeDidWebDisabled && didWebIdentifier !== void 0) {
43715
+ if (didWebAuthorityHost === void 0) {
43716
+ write(
43717
+ err,
43718
+ "Error: --did-web requires --did-web-authority-host=<host>\n"
43719
+ );
43720
+ return 2;
43721
+ }
43722
+ exportDidWeb = {
43723
+ identifier: didWebIdentifier,
43724
+ authority_host: didWebAuthorityHost,
43725
+ ...didWebPublishedAt !== void 0 ? { published_at: didWebPublishedAt } : {}
43726
+ };
43727
+ }
42714
43728
  const result = await exportExitBundle({
42715
43729
  bundleDir: outDir,
42716
43730
  storage: ctx.storage,
@@ -42722,7 +43736,8 @@ ${policyErr.message}
42722
43736
  config,
42723
43737
  stateStoragePath: ctx.stateStoragePath,
42724
43738
  stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
42725
- keySource: ctx.keySource
43739
+ keySource: ctx.keySource,
43740
+ ...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
42726
43741
  });
42727
43742
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
42728
43743
  else {
@@ -42800,6 +43815,11 @@ ${policyErr.message}
42800
43815
  write(err, "--conflict must be skip, overwrite, or version\n");
42801
43816
  return 2;
42802
43817
  }
43818
+ const didWebAllowedHosts = repeatedFlagValues(
43819
+ argv,
43820
+ "--did-web-allowed-host"
43821
+ );
43822
+ const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
42803
43823
  let result;
42804
43824
  try {
42805
43825
  result = await importExitBundle({
@@ -42815,7 +43835,9 @@ ${policyErr.message}
42815
43835
  conflictResolution: conflict,
42816
43836
  sourcePassphrase: flagValue(argv, "--source-passphrase"),
42817
43837
  sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
42818
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
43838
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
43839
+ ...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
43840
+ skipDidWebVerify
42819
43841
  });
42820
43842
  } catch (e) {
42821
43843
  if (e instanceof InvalidExitBundleError) {
@@ -43600,12 +44622,14 @@ ${err.message}
43600
44622
  fortressId: fortressIdForAggregator
43601
44623
  });
43602
44624
  const handoffEventBridge = new HandoffEventBridge();
44625
+ const workflowStateTracker = new WorkflowStateTracker();
43603
44626
  if (dashboard) {
43604
44627
  dashboard.setHandoffLog({
43605
44628
  handoffLog,
43606
44629
  eventBridge: handoffEventBridge,
43607
44630
  auditLog,
43608
- operatorId: aggregatorIdentityId
44631
+ operatorId: aggregatorIdentityId,
44632
+ workflowStateTracker
43609
44633
  });
43610
44634
  }
43611
44635
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
@@ -43807,6 +44831,7 @@ var init_src = __esm({
43807
44831
  init_anomaly_pipeline();
43808
44832
  init_handoff_log();
43809
44833
  init_handoff_routes();
44834
+ init_workflow_state_tracker();
43810
44835
  init_sentinels();
43811
44836
  init_subscription_store();
43812
44837
  init_tools4();
@@ -47913,7 +48938,7 @@ async function probeTenantDashboard(tenant, options = {}) {
47913
48938
  if (!rt) {
47914
48939
  return { running: false, status: null, reason: "no runtime.json" };
47915
48940
  }
47916
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
48941
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
47917
48942
  return await new Promise((resolve8) => {
47918
48943
  const req = http.get(
47919
48944
  {
@@ -47949,10 +48974,10 @@ async function probeTenantDashboard(tenant, options = {}) {
47949
48974
  });
47950
48975
  });
47951
48976
  }
47952
- var DEFAULT_TIMEOUT_MS4;
48977
+ var DEFAULT_TIMEOUT_MS5;
47953
48978
  var init_health = __esm({
47954
48979
  "src/cli/agents/health.ts"() {
47955
- DEFAULT_TIMEOUT_MS4 = 500;
48980
+ DEFAULT_TIMEOUT_MS5 = 500;
47956
48981
  }
47957
48982
  });
47958
48983
  function resolveCtx(args) {
@@ -49203,108 +50228,6 @@ var init_sentinel2 = __esm({
49203
50228
  init_sentinels();
49204
50229
  }
49205
50230
  });
49206
- async function issueDidWeb(opts) {
49207
- if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
49208
- throw new Error(
49209
- `did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
49210
- );
49211
- }
49212
- if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
49213
- throw new Error(
49214
- `did-web: fortress_id '${opts.fortress_id}' is not a valid label`
49215
- );
49216
- }
49217
- if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
49218
- throw new Error(
49219
- `did-web: agent_label '${opts.agent_label}' is not a valid label`
49220
- );
49221
- }
49222
- if (opts.public_key.length !== 32) {
49223
- throw new Error(
49224
- `did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
49225
- );
49226
- }
49227
- const did = buildDid(opts);
49228
- const verificationMethodId = `${did}#key-1`;
49229
- const verificationMethod = {
49230
- id: verificationMethodId,
49231
- type: "JsonWebKey2020",
49232
- controller: did,
49233
- publicKeyJwk: {
49234
- kty: "OKP",
49235
- crv: "Ed25519",
49236
- x: toBase64url(opts.public_key)
49237
- }
49238
- };
49239
- const didDocument = {
49240
- "@context": [...DID_CONTEXT],
49241
- id: did,
49242
- verificationMethod: [verificationMethod],
49243
- authentication: [verificationMethodId],
49244
- assertionMethod: [verificationMethodId]
49245
- };
49246
- const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
49247
- return {
49248
- did,
49249
- did_document: didDocument,
49250
- public_key: opts.public_key,
49251
- created_at: now.toISOString(),
49252
- authority_host: opts.authority_host,
49253
- fortress_id: opts.fortress_id,
49254
- ...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
49255
- };
49256
- }
49257
- function publishDidWebDocument(identifier, opts = {}) {
49258
- const path = opts.publish_path ?? canonicalPublishPath(identifier);
49259
- const artifact = canonicalSerializeDidDocument(identifier.did_document);
49260
- const digest = sha256.sha256(stringToBytes(artifact));
49261
- const url = `https://${identifier.authority_host}${path}`;
49262
- return {
49263
- url,
49264
- publish_path: path,
49265
- artifact,
49266
- sha256: hashToString(digest)
49267
- };
49268
- }
49269
- function buildDid(opts) {
49270
- if (opts.agent_label === void 0) {
49271
- return `did:web:${opts.authority_host}`;
49272
- }
49273
- return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
49274
- }
49275
- function canonicalPublishPath(identifier) {
49276
- if (identifier.agent_label === void 0) {
49277
- return "/.well-known/did.json";
49278
- }
49279
- return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
49280
- }
49281
- function canonicalSerializeDidDocument(doc) {
49282
- return JSON.stringify(
49283
- {
49284
- "@context": doc["@context"],
49285
- id: doc.id,
49286
- verificationMethod: doc.verificationMethod,
49287
- authentication: doc.authentication,
49288
- assertionMethod: doc.assertionMethod
49289
- },
49290
- null,
49291
- 2
49292
- );
49293
- }
49294
- var DID_CONTEXT, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
49295
- var init_did_web = __esm({
49296
- "src/recognition/did-web.ts"() {
49297
- init_encoding();
49298
- init_hashing();
49299
- DID_CONTEXT = [
49300
- "https://www.w3.org/ns/did/v1",
49301
- "https://w3id.org/security/suites/jws-2020/v1"
49302
- ];
49303
- 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;
49304
- FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
49305
- AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
49306
- }
49307
- });
49308
50231
 
49309
50232
  // src/cli/did-web.ts
49310
50233
  var did_web_exports = {};
@@ -49563,6 +50486,689 @@ var init_did_web2 = __esm({
49563
50486
  }
49564
50487
  });
49565
50488
 
50489
+ // src/anomaly-detection/classifiers/rolling-baseline.ts
50490
+ var ROLLING_BASELINE_CLASSIFIER_ID, DEFAULT_MIN_SAMPLES_FOR_PREDICTION, STDDEV_FLOOR, RollingBaselineClassifier;
50491
+ var init_rolling_baseline = __esm({
50492
+ "src/anomaly-detection/classifiers/rolling-baseline.ts"() {
50493
+ ROLLING_BASELINE_CLASSIFIER_ID = "rolling-baseline";
50494
+ DEFAULT_MIN_SAMPLES_FOR_PREDICTION = 7;
50495
+ STDDEV_FLOOR = 0.5;
50496
+ RollingBaselineClassifier = class {
50497
+ classifierId = ROLLING_BASELINE_CLASSIFIER_ID;
50498
+ stateStore;
50499
+ minSamplesForPrediction;
50500
+ /** In-memory cache of per-agent state; loaded lazily on first touch. */
50501
+ cache = /* @__PURE__ */ new Map();
50502
+ /** Agents whose in-memory state has been mutated since last train(). */
50503
+ dirty = /* @__PURE__ */ new Set();
50504
+ constructor(opts) {
50505
+ this.stateStore = opts.stateStore;
50506
+ this.minSamplesForPrediction = opts.minSamplesForPrediction ?? DEFAULT_MIN_SAMPLES_FOR_PREDICTION;
50507
+ }
50508
+ async observe(vector) {
50509
+ const state = await this.loadOrInit(vector.agent_id);
50510
+ for (const [featureName, observed] of Object.entries(vector.features)) {
50511
+ if (!Number.isFinite(observed)) continue;
50512
+ const welford = state.features[featureName] ?? {
50513
+ n: 0,
50514
+ mean: 0,
50515
+ m2: 0
50516
+ };
50517
+ const nextN = welford.n + 1;
50518
+ const delta = observed - welford.mean;
50519
+ const nextMean = welford.mean + delta / nextN;
50520
+ const delta2 = observed - nextMean;
50521
+ const nextM2 = welford.m2 + delta * delta2;
50522
+ state.features[featureName] = { n: nextN, mean: nextMean, m2: nextM2 };
50523
+ }
50524
+ state.observation_count += 1;
50525
+ state.last_observed_at = vector.observed_at;
50526
+ this.dirty.add(vector.agent_id);
50527
+ }
50528
+ async predict(vector) {
50529
+ const state = await this.loadOrInit(vector.agent_id);
50530
+ if (state.observation_count < this.minSamplesForPrediction) {
50531
+ return {
50532
+ anomaly_score: 0,
50533
+ explanation: [],
50534
+ feature_contributions: [],
50535
+ baseline_ready: false
50536
+ };
50537
+ }
50538
+ const contributions = [];
50539
+ let sumSquaredZ = 0;
50540
+ for (const [featureName, observed] of Object.entries(vector.features)) {
50541
+ if (!Number.isFinite(observed)) continue;
50542
+ const welford = state.features[featureName];
50543
+ if (!welford || welford.n < 2) continue;
50544
+ const variance = welford.m2 / (welford.n - 1);
50545
+ const stddev = Math.max(Math.sqrt(variance), STDDEV_FLOOR);
50546
+ const z = (observed - welford.mean) / stddev;
50547
+ sumSquaredZ += z * z;
50548
+ contributions.push({
50549
+ feature_name: featureName,
50550
+ observed,
50551
+ baseline_mean: welford.mean,
50552
+ baseline_stddev: stddev,
50553
+ z_score: z
50554
+ });
50555
+ }
50556
+ contributions.sort(
50557
+ (a, b) => Math.abs(b.z_score) - Math.abs(a.z_score)
50558
+ );
50559
+ const anomalyScore = Math.sqrt(sumSquaredZ);
50560
+ const explanation = contributions.map(
50561
+ (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)})`
50562
+ );
50563
+ return {
50564
+ anomaly_score: anomalyScore,
50565
+ explanation,
50566
+ feature_contributions: contributions,
50567
+ baseline_ready: true
50568
+ };
50569
+ }
50570
+ async train() {
50571
+ const dirtyAgents = [...this.dirty];
50572
+ for (const agentId of dirtyAgents) {
50573
+ const state = this.cache.get(agentId);
50574
+ if (!state) continue;
50575
+ await this.stateStore.saveState(this.classifierId, agentId, state);
50576
+ this.dirty.delete(agentId);
50577
+ }
50578
+ let sampleCount = 0;
50579
+ for (const state of this.cache.values()) {
50580
+ sampleCount += state.observation_count;
50581
+ }
50582
+ return {
50583
+ trained_at: (/* @__PURE__ */ new Date()).toISOString(),
50584
+ sample_count: sampleCount,
50585
+ agent_count: this.cache.size
50586
+ };
50587
+ }
50588
+ /** Test helper: read the in-memory state for an agent. */
50589
+ getAgentState(agentId) {
50590
+ return this.cache.get(agentId);
50591
+ }
50592
+ async loadOrInit(agentId) {
50593
+ const cached = this.cache.get(agentId);
50594
+ if (cached) return cached;
50595
+ const persisted = await this.stateStore.loadState(
50596
+ this.classifierId,
50597
+ agentId
50598
+ );
50599
+ const state = persisted ?? {
50600
+ observation_count: 0,
50601
+ last_observed_at: null,
50602
+ features: {}
50603
+ };
50604
+ this.cache.set(agentId, state);
50605
+ return state;
50606
+ }
50607
+ };
50608
+ }
50609
+ });
50610
+
50611
+ // src/anomaly-detection/feature-extractors/per-agent-activity.ts
50612
+ function emptyBucket() {
50613
+ return {
50614
+ tool_call_count: 0,
50615
+ egress_call_count: 0,
50616
+ credential_use_count: 0,
50617
+ audit_event_count: 0,
50618
+ recent_receipt_count: 0
50619
+ };
50620
+ }
50621
+ function bucketToFeatures(bucket) {
50622
+ return {
50623
+ tool_call_count: bucket.tool_call_count,
50624
+ egress_call_count: bucket.egress_call_count,
50625
+ credential_use_count: bucket.credential_use_count,
50626
+ audit_event_count: bucket.audit_event_count,
50627
+ recent_receipt_count: bucket.recent_receipt_count
50628
+ };
50629
+ }
50630
+ function classifyEntry(entry, bucket) {
50631
+ bucket.audit_event_count += 1;
50632
+ bucket.tool_call_count += 1;
50633
+ if (entry.operation.startsWith("proxy_call:")) {
50634
+ bucket.egress_call_count += 1;
50635
+ }
50636
+ if (entry.operation.startsWith("broker_secret_") || entry.operation.startsWith("broker_token_")) {
50637
+ bucket.credential_use_count += 1;
50638
+ }
50639
+ if (entry.operation.startsWith("composition_receipt_") || entry.operation === "reputation_record" || entry.operation === "reputation_query" || entry.operation === "reputation_publish") {
50640
+ bucket.recent_receipt_count += 1;
50641
+ }
50642
+ }
50643
+ async function extractPerAgentActivity(context) {
50644
+ const now = context.now();
50645
+ const sinceIso = new Date(now.getTime() - WINDOW_MS2).toISOString();
50646
+ const result = await context.auditLog.query({
50647
+ since: sinceIso,
50648
+ limit: QUERY_LIMIT6
50649
+ });
50650
+ const buckets = /* @__PURE__ */ new Map();
50651
+ for (const entry of result.entries) {
50652
+ const agentId = entry.identity_id && entry.identity_id.length > 0 ? entry.identity_id : SYSTEM_AGENT_BUCKET;
50653
+ let bucket = buckets.get(agentId);
50654
+ if (!bucket) {
50655
+ bucket = emptyBucket();
50656
+ buckets.set(agentId, bucket);
50657
+ }
50658
+ classifyEntry(entry, bucket);
50659
+ }
50660
+ const observedAt = now.toISOString();
50661
+ const vectors = [];
50662
+ for (const [agentId, bucket] of buckets.entries()) {
50663
+ vectors.push({
50664
+ agent_id: agentId,
50665
+ observed_at: observedAt,
50666
+ features: bucketToFeatures(bucket),
50667
+ window_label: PER_AGENT_ACTIVITY_WINDOW_LABEL
50668
+ });
50669
+ }
50670
+ vectors.sort((a, b) => a.agent_id < b.agent_id ? -1 : 1);
50671
+ return vectors;
50672
+ }
50673
+ var PER_AGENT_ACTIVITY_EXTRACTOR_ID, WINDOW_MS2, QUERY_LIMIT6, SYSTEM_AGENT_BUCKET, PER_AGENT_ACTIVITY_WINDOW_LABEL;
50674
+ var init_per_agent_activity = __esm({
50675
+ "src/anomaly-detection/feature-extractors/per-agent-activity.ts"() {
50676
+ PER_AGENT_ACTIVITY_EXTRACTOR_ID = "per-agent-activity";
50677
+ WINDOW_MS2 = 24 * 60 * 60 * 1e3;
50678
+ QUERY_LIMIT6 = 1e4;
50679
+ SYSTEM_AGENT_BUCKET = "system";
50680
+ PER_AGENT_ACTIVITY_WINDOW_LABEL = "24h_rolling";
50681
+ }
50682
+ });
50683
+
50684
+ // src/anomaly-detection/detectors/per-agent-activity-detector.ts
50685
+ var PER_AGENT_ACTIVITY_DETECTOR_ID, PerAgentActivityDetector, PendingClassifier;
50686
+ var init_per_agent_activity_detector = __esm({
50687
+ "src/anomaly-detection/detectors/per-agent-activity-detector.ts"() {
50688
+ init_types4();
50689
+ init_rolling_baseline();
50690
+ init_classifier_state_store();
50691
+ init_per_agent_activity();
50692
+ PER_AGENT_ACTIVITY_DETECTOR_ID = PER_AGENT_ACTIVITY_EXTRACTOR_ID;
50693
+ PerAgentActivityDetector = class extends AnomalyDetector {
50694
+ detectorId = PER_AGENT_ACTIVITY_DETECTOR_ID;
50695
+ 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).";
50696
+ classifier;
50697
+ explicitClassifier;
50698
+ minSamplesForPrediction;
50699
+ constructor(opts) {
50700
+ super();
50701
+ this.explicitClassifier = opts?.classifier !== void 0;
50702
+ if (opts?.classifier) {
50703
+ this.classifier = opts.classifier;
50704
+ } else {
50705
+ this.classifier = new PendingClassifier();
50706
+ }
50707
+ this.minSamplesForPrediction = opts?.minSamplesForPrediction;
50708
+ }
50709
+ async subscribe(context) {
50710
+ await super.subscribe(context);
50711
+ if (this.explicitClassifier) return;
50712
+ const stateStore = new ClassifierStateStore({
50713
+ storage: context.storage,
50714
+ masterKey: context.masterKey,
50715
+ fortressId: context.fortressId,
50716
+ now: context.now
50717
+ });
50718
+ const realClassifier = new RollingBaselineClassifier({
50719
+ stateStore,
50720
+ ...this.minSamplesForPrediction !== void 0 ? { minSamplesForPrediction: this.minSamplesForPrediction } : {}
50721
+ });
50722
+ this.classifier = realClassifier;
50723
+ }
50724
+ async featureExtract(context) {
50725
+ return extractPerAgentActivity(context);
50726
+ }
50727
+ };
50728
+ PendingClassifier = class {
50729
+ classifierId = "pending";
50730
+ async observe() {
50731
+ throw new Error(
50732
+ "anomaly-detector: classifier accessed before subscribe()"
50733
+ );
50734
+ }
50735
+ async predict() {
50736
+ throw new Error(
50737
+ "anomaly-detector: classifier accessed before subscribe()"
50738
+ );
50739
+ }
50740
+ async train() {
50741
+ throw new Error(
50742
+ "anomaly-detector: classifier accessed before subscribe()"
50743
+ );
50744
+ }
50745
+ };
50746
+ }
50747
+ });
50748
+
50749
+ // src/anomaly-detection/anomaly-catalog.ts
50750
+ function findCatalogEntry(detectorId, classifierId) {
50751
+ return ANOMALY_CATALOG.find(
50752
+ (e) => e.detectorId === detectorId && e.classifierId === classifierId
50753
+ );
50754
+ }
50755
+ var ANOMALY_CATALOG;
50756
+ var init_anomaly_catalog = __esm({
50757
+ "src/anomaly-detection/anomaly-catalog.ts"() {
50758
+ init_per_agent_activity_detector();
50759
+ init_rolling_baseline();
50760
+ ANOMALY_CATALOG = [
50761
+ {
50762
+ detectorId: PER_AGENT_ACTIVITY_DETECTOR_ID,
50763
+ classifierId: ROLLING_BASELINE_CLASSIFIER_ID,
50764
+ 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.",
50765
+ factory: () => new PerAgentActivityDetector()
50766
+ }
50767
+ ];
50768
+ }
50769
+ });
50770
+ function anomalySubscriptionsPath(storagePath) {
50771
+ return path.join(storagePath, "anomaly-subscriptions.json");
50772
+ }
50773
+ async function loadAnomalySubscriptions(storagePath) {
50774
+ const filePath = anomalySubscriptionsPath(storagePath);
50775
+ try {
50776
+ const raw = await promises.readFile(filePath, "utf8");
50777
+ const parsed = JSON.parse(raw);
50778
+ if (parsed.version !== FILE_VERSION2) return [];
50779
+ if (!Array.isArray(parsed.subscribed)) return [];
50780
+ return parsed.subscribed.filter(
50781
+ (t) => t !== null && typeof t === "object" && typeof t.detector_id === "string" && typeof t.classifier_id === "string"
50782
+ );
50783
+ } catch {
50784
+ return [];
50785
+ }
50786
+ }
50787
+ async function saveAnomalySubscriptions(storagePath, subscriptions) {
50788
+ const filePath = anomalySubscriptionsPath(storagePath);
50789
+ await promises.mkdir(path.dirname(filePath), { recursive: true });
50790
+ const payload = {
50791
+ version: FILE_VERSION2,
50792
+ // Deduplicate.
50793
+ subscribed: dedupe(subscriptions)
50794
+ };
50795
+ await promises.writeFile(filePath, JSON.stringify(payload, null, 2), {
50796
+ mode: 384
50797
+ });
50798
+ }
50799
+ function dedupe(items) {
50800
+ const seen = /* @__PURE__ */ new Set();
50801
+ const out = [];
50802
+ for (const item of items) {
50803
+ const key = `${item.detector_id}|${item.classifier_id}`;
50804
+ if (seen.has(key)) continue;
50805
+ seen.add(key);
50806
+ out.push(item);
50807
+ }
50808
+ return out;
50809
+ }
50810
+ var FILE_VERSION2;
50811
+ var init_anomaly_subscription_store = __esm({
50812
+ "src/anomaly-detection/anomaly-subscription-store.ts"() {
50813
+ FILE_VERSION2 = 1;
50814
+ }
50815
+ });
50816
+
50817
+ // src/cli/anomaly.ts
50818
+ var anomaly_exports = {};
50819
+ __export(anomaly_exports, {
50820
+ runAnomalyCommand: () => runAnomalyCommand
50821
+ });
50822
+ async function runAnomalyCommand(args) {
50823
+ const out = args.out ?? process.stdout;
50824
+ const err = args.err ?? process.stderr;
50825
+ const [sub, ...rest] = args.argv;
50826
+ if (!sub || sub === "--help" || sub === "-h") {
50827
+ printUsage8(out);
50828
+ return 0;
50829
+ }
50830
+ try {
50831
+ switch (sub) {
50832
+ case "detectors":
50833
+ return cmdDetectors(rest, { out });
50834
+ case "list-subscribed":
50835
+ return await cmdListSubscribed2({ out, args });
50836
+ case "subscribe":
50837
+ return await cmdSubscribe2(rest, { out, err, args });
50838
+ case "unsubscribe":
50839
+ return await cmdUnsubscribe2(rest, { out, err, args });
50840
+ case "findings":
50841
+ return await cmdFindings2(rest, { out, err, args });
50842
+ case "classifier-state":
50843
+ return await cmdClassifierState(rest, { out, err, args });
50844
+ default:
50845
+ err.write(`Unknown subcommand: ${sub}
50846
+ `);
50847
+ printUsage8(err);
50848
+ return 2;
50849
+ }
50850
+ } catch (e) {
50851
+ const msg = e instanceof Error ? e.message : String(e);
50852
+ err.write(`sanctuary anomaly: ${msg}
50853
+ `);
50854
+ return 1;
50855
+ }
50856
+ }
50857
+ function printUsage8(s) {
50858
+ s.write(`Usage: sanctuary anomaly <command> [args]
50859
+
50860
+ detectors list Catalog of available
50861
+ detector + classifier tuples.
50862
+ list-subscribed Subscriptions on this fortress.
50863
+ subscribe <detector-id> --classifier <id>
50864
+ Opt in. Writes the
50865
+ subscription file; the server
50866
+ picks it up on next boot.
50867
+ unsubscribe <detector-id> --classifier <id>
50868
+ Opt out.
50869
+ findings [opts] Read anomaly findings.
50870
+ --since <iso> observed_at >= iso.
50871
+ --severity <info|warn|alert> Filter by severity.
50872
+ --detector-id <id> Filter by emitting detector.
50873
+ --agent-id <id> Filter by agent attribution.
50874
+ --limit <n> Cap result count (default 100).
50875
+ findings show <finding-id> Full drift-inspector detail.
50876
+ classifier-state <detector-id> --classifier <id>
50877
+ Per-agent training state.
50878
+ `);
50879
+ }
50880
+ function flagValue4(argv, name) {
50881
+ const i = argv.indexOf(name);
50882
+ if (i === -1) return void 0;
50883
+ return argv[i + 1];
50884
+ }
50885
+ function cmdDetectors(argv, ctx) {
50886
+ const sub = argv[0];
50887
+ if (sub !== void 0 && sub !== "list") {
50888
+ ctx.out.write(`Unknown detectors subcommand: ${sub}
50889
+ `);
50890
+ return 2;
50891
+ }
50892
+ if (ANOMALY_CATALOG.length === 0) {
50893
+ ctx.out.write("(no detectors registered)\n");
50894
+ return 0;
50895
+ }
50896
+ for (const entry of ANOMALY_CATALOG) {
50897
+ ctx.out.write(
50898
+ `${entry.detectorId} [classifier: ${entry.classifierId}]
50899
+ ${entry.description}
50900
+ `
50901
+ );
50902
+ }
50903
+ return 0;
50904
+ }
50905
+ async function cmdListSubscribed2(ctx) {
50906
+ const storagePath = await resolveStoragePath3(ctx.args);
50907
+ const subscribed = await loadAnomalySubscriptions(storagePath);
50908
+ if (subscribed.length === 0) {
50909
+ ctx.out.write("(no subscriptions)\n");
50910
+ return 0;
50911
+ }
50912
+ for (const t of subscribed) {
50913
+ ctx.out.write(`${t.detector_id} [classifier: ${t.classifier_id}]
50914
+ `);
50915
+ }
50916
+ return 0;
50917
+ }
50918
+ async function cmdSubscribe2(argv, ctx) {
50919
+ const detectorId = argv[0];
50920
+ const classifierId = flagValue4(argv, "--classifier");
50921
+ if (!detectorId) {
50922
+ ctx.err.write("subscribe requires a detector-id\n");
50923
+ return 2;
50924
+ }
50925
+ if (!classifierId) {
50926
+ ctx.err.write("subscribe requires --classifier <id>\n");
50927
+ return 2;
50928
+ }
50929
+ const entry = findCatalogEntry(detectorId, classifierId);
50930
+ if (!entry) {
50931
+ ctx.err.write(
50932
+ `Unknown detector/classifier pair: ${detectorId} / ${classifierId}
50933
+ `
50934
+ );
50935
+ return 2;
50936
+ }
50937
+ const storagePath = await resolveStoragePath3(ctx.args);
50938
+ const subscribed = await loadAnomalySubscriptions(storagePath);
50939
+ const exists = subscribed.some(
50940
+ (t) => t.detector_id === detectorId && t.classifier_id === classifierId
50941
+ );
50942
+ if (exists) {
50943
+ ctx.out.write(
50944
+ `Already subscribed: ${detectorId} [classifier: ${classifierId}]
50945
+ `
50946
+ );
50947
+ return 0;
50948
+ }
50949
+ subscribed.push({ detector_id: detectorId, classifier_id: classifierId });
50950
+ await saveAnomalySubscriptions(storagePath, subscribed);
50951
+ ctx.out.write(
50952
+ `Subscribed: ${detectorId} [classifier: ${classifierId}]
50953
+ Restart Sanctuary or wait for the next dispatcher tick.
50954
+ `
50955
+ );
50956
+ return 0;
50957
+ }
50958
+ async function cmdUnsubscribe2(argv, ctx) {
50959
+ const detectorId = argv[0];
50960
+ const classifierId = flagValue4(argv, "--classifier");
50961
+ if (!detectorId) {
50962
+ ctx.err.write("unsubscribe requires a detector-id\n");
50963
+ return 2;
50964
+ }
50965
+ if (!classifierId) {
50966
+ ctx.err.write("unsubscribe requires --classifier <id>\n");
50967
+ return 2;
50968
+ }
50969
+ const storagePath = await resolveStoragePath3(ctx.args);
50970
+ const subscribed = await loadAnomalySubscriptions(storagePath);
50971
+ const filtered = subscribed.filter(
50972
+ (t) => !(t.detector_id === detectorId && t.classifier_id === classifierId)
50973
+ );
50974
+ if (filtered.length === subscribed.length) {
50975
+ ctx.out.write(
50976
+ `Not subscribed: ${detectorId} [classifier: ${classifierId}]
50977
+ `
50978
+ );
50979
+ return 0;
50980
+ }
50981
+ await saveAnomalySubscriptions(storagePath, filtered);
50982
+ ctx.out.write(
50983
+ `Unsubscribed: ${detectorId} [classifier: ${classifierId}]
50984
+ `
50985
+ );
50986
+ return 0;
50987
+ }
50988
+ async function cmdFindings2(argv, ctx) {
50989
+ if (argv[0] === "show") {
50990
+ return await cmdFindingsShow(argv.slice(1), ctx);
50991
+ }
50992
+ const filters = parseFindingFilters2(argv);
50993
+ const masterKey = await deriveFortressMasterKey(ctx);
50994
+ const storagePath = await resolveStoragePath3(ctx.args);
50995
+ const storage = new FilesystemStorage(`${storagePath}/state`);
50996
+ const fortressId = fortressIdFromStoragePath(storagePath);
50997
+ const store = new SentinelFindingStore({
50998
+ storage,
50999
+ masterKey,
51000
+ fortressId
51001
+ });
51002
+ const filterSentinelId = filters.detectorId !== void 0 ? `${ANOMALY_SENTINEL_ID_PREFIX}${filters.detectorId}` : void 0;
51003
+ const allFindings = await store.listFindings({
51004
+ limit: filters.limit ?? 100,
51005
+ ...filters.since !== void 0 ? { since: filters.since } : {},
51006
+ ...filters.severity !== void 0 ? { severity: filters.severity } : {},
51007
+ ...filterSentinelId !== void 0 ? { sentinelId: filterSentinelId } : {},
51008
+ ...filters.agentId !== void 0 ? { agentId: filters.agentId } : {}
51009
+ });
51010
+ const anomalyFindings = filterSentinelId !== void 0 ? allFindings : allFindings.filter(
51011
+ (f) => f.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)
51012
+ );
51013
+ if (anomalyFindings.length === 0) {
51014
+ ctx.out.write("(no findings)\n");
51015
+ return 0;
51016
+ }
51017
+ for (const finding of anomalyFindings) {
51018
+ const detectorId = finding.details["detector_id"] ?? "";
51019
+ const score = finding.details["anomaly_score"];
51020
+ const scoreStr = typeof score === "number" ? ` score=${score.toFixed(2)}` : "";
51021
+ ctx.out.write(
51022
+ `[${finding.observed_at}] ${finding.severity.toUpperCase()} ${detectorId}${finding.agent_id ? ` (agent ${finding.agent_id})` : ""}${scoreStr}: ${finding.summary}
51023
+ `
51024
+ );
51025
+ }
51026
+ return 0;
51027
+ }
51028
+ async function cmdFindingsShow(argv, ctx) {
51029
+ const findingId = argv[0];
51030
+ if (!findingId) {
51031
+ ctx.err.write("findings show requires a finding-id\n");
51032
+ return 2;
51033
+ }
51034
+ const masterKey = await deriveFortressMasterKey(ctx);
51035
+ const storagePath = await resolveStoragePath3(ctx.args);
51036
+ const storage = new FilesystemStorage(`${storagePath}/state`);
51037
+ const fortressId = fortressIdFromStoragePath(storagePath);
51038
+ const store = new SentinelFindingStore({ storage, masterKey, fortressId });
51039
+ const finding = await store.loadFinding(findingId);
51040
+ if (!finding) {
51041
+ ctx.err.write(`Finding not found: ${findingId}
51042
+ `);
51043
+ return 1;
51044
+ }
51045
+ if (!finding.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)) {
51046
+ ctx.err.write(
51047
+ `Finding ${findingId} is not an anomaly finding; try sanctuary sentinel findings.
51048
+ `
51049
+ );
51050
+ return 1;
51051
+ }
51052
+ ctx.out.write(JSON.stringify(finding, null, 2) + "\n");
51053
+ return 0;
51054
+ }
51055
+ async function cmdClassifierState(argv, ctx) {
51056
+ const detectorId = argv[0];
51057
+ const classifierId = flagValue4(argv, "--classifier");
51058
+ if (!detectorId) {
51059
+ ctx.err.write("classifier-state requires a detector-id\n");
51060
+ return 2;
51061
+ }
51062
+ if (!classifierId) {
51063
+ ctx.err.write("classifier-state requires --classifier <id>\n");
51064
+ return 2;
51065
+ }
51066
+ const entry = findCatalogEntry(detectorId, classifierId);
51067
+ if (!entry) {
51068
+ ctx.err.write(
51069
+ `Unknown detector/classifier pair: ${detectorId} / ${classifierId}
51070
+ `
51071
+ );
51072
+ return 2;
51073
+ }
51074
+ const masterKey = await deriveFortressMasterKey(ctx);
51075
+ const storagePath = await resolveStoragePath3(ctx.args);
51076
+ const storage = new FilesystemStorage(`${storagePath}/state`);
51077
+ const fortressId = fortressIdFromStoragePath(storagePath);
51078
+ const stateStore = new ClassifierStateStore({
51079
+ storage,
51080
+ masterKey,
51081
+ fortressId
51082
+ });
51083
+ const agentIds = await stateStore.listAgents(classifierId);
51084
+ if (agentIds.length === 0) {
51085
+ ctx.out.write("(no classifier state yet)\n");
51086
+ return 0;
51087
+ }
51088
+ for (const agentId of agentIds) {
51089
+ try {
51090
+ const raw = await stateStore.loadState(classifierId, agentId);
51091
+ if (raw === null) continue;
51092
+ const sampleCount = typeof raw.sample_count === "number" ? raw.sample_count : "?";
51093
+ ctx.out.write(`${agentId}: sample_count=${sampleCount}
51094
+ `);
51095
+ } catch {
51096
+ ctx.out.write(`${agentId}: (load failed)
51097
+ `);
51098
+ }
51099
+ }
51100
+ return 0;
51101
+ }
51102
+ function parseFindingFilters2(argv) {
51103
+ const filters = {};
51104
+ for (let i = 0; i < argv.length; i += 1) {
51105
+ const arg = argv[i];
51106
+ if (arg === "--since" && argv[i + 1]) {
51107
+ filters.since = argv[++i];
51108
+ } else if (arg === "--severity" && argv[i + 1]) {
51109
+ const next = argv[++i];
51110
+ if (next === "info" || next === "warn" || next === "alert") {
51111
+ filters.severity = next;
51112
+ }
51113
+ } else if (arg === "--detector-id" && argv[i + 1]) {
51114
+ filters.detectorId = argv[++i];
51115
+ } else if (arg === "--agent-id" && argv[i + 1]) {
51116
+ filters.agentId = argv[++i];
51117
+ } else if (arg === "--limit" && argv[i + 1]) {
51118
+ const n = Number.parseInt(argv[++i], 10);
51119
+ if (!Number.isNaN(n) && n > 0) filters.limit = n;
51120
+ }
51121
+ }
51122
+ return filters;
51123
+ }
51124
+ async function resolveStoragePath3(args) {
51125
+ if (args.storagePath) return args.storagePath;
51126
+ const config = await loadConfig();
51127
+ return config.storage_path;
51128
+ }
51129
+ async function deriveFortressMasterKey(ctx) {
51130
+ const storagePath = await resolveStoragePath3(ctx.args);
51131
+ const storage = new FilesystemStorage(`${storagePath}/state`);
51132
+ let passphrase = ctx.args.passphrase ?? process.env["SANCTUARY_PASSPHRASE"];
51133
+ if (!passphrase) {
51134
+ const resolved = await getOrCreatePassphrase();
51135
+ passphrase = resolved.value;
51136
+ }
51137
+ let existingParams;
51138
+ try {
51139
+ const raw = await storage.read("_meta", "key-params");
51140
+ if (raw) existingParams = JSON.parse(bytesToString(raw));
51141
+ } catch {
51142
+ }
51143
+ const { key: masterKey, params } = await deriveMasterKey(
51144
+ passphrase,
51145
+ existingParams
51146
+ );
51147
+ if (!existingParams) {
51148
+ await storage.write(
51149
+ "_meta",
51150
+ "key-params",
51151
+ stringToBytes(JSON.stringify(params))
51152
+ );
51153
+ }
51154
+ return masterKey;
51155
+ }
51156
+ var init_anomaly = __esm({
51157
+ "src/cli/anomaly.ts"() {
51158
+ init_config();
51159
+ init_filesystem();
51160
+ init_key_derivation();
51161
+ init_encoding();
51162
+ init_passphrase();
51163
+ init_wiring();
51164
+ init_sentinel_finding_store();
51165
+ init_anomaly_catalog();
51166
+ init_anomaly_subscription_store();
51167
+ init_classifier_state_store();
51168
+ init_types4();
51169
+ }
51170
+ });
51171
+
49566
51172
  // src/mcp/broker-server.ts
49567
51173
  var broker_server_exports = {};
49568
51174
  __export(broker_server_exports, {
@@ -50432,6 +52038,11 @@ async function main() {
50432
52038
  const code = await runDidWebCommand2({ argv: args.slice(1) });
50433
52039
  process.exit(code);
50434
52040
  }
52041
+ if (args[0] === "anomaly") {
52042
+ const { runAnomalyCommand: runAnomalyCommand2 } = await Promise.resolve().then(() => (init_anomaly(), anomaly_exports));
52043
+ const code = await runAnomalyCommand2({ argv: args.slice(1) });
52044
+ process.exit(code);
52045
+ }
50435
52046
  if (args[0] === "broker-server") {
50436
52047
  const { openBroker: openBroker2 } = await Promise.resolve().then(() => (init_open(), open_exports));
50437
52048
  const { createBrokerMcpServer: createBrokerMcpServer2 } = await Promise.resolve().then(() => (init_broker_server(), broker_server_exports));