@sanctuary-framework/mcp-server 1.2.11 → 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/index.cjs CHANGED
@@ -16707,13 +16707,418 @@ function crossHarnessSummary(details, sender) {
16707
16707
  return `${sender} -> operator approval`;
16708
16708
  }
16709
16709
  var COORDINATION_VIEW_AUDIT_OPS = {
16710
+ /** v1.3 Omega-1: operator opened the chronological handoff list. */
16710
16711
  VIEW_OPENED: "operator_coordination_view_opened",
16711
- ENTRY_DRILLED: "operator_handoff_entry_drilled"
16712
+ /** v1.3 Omega-1: operator drilled into a single handoff for detail. */
16713
+ ENTRY_DRILLED: "operator_handoff_entry_drilled",
16714
+ /**
16715
+ * v1.3 Omega-3: operator opened the Workflows sibling-view (list of
16716
+ * multi-handoff workflows grouped by `workflow-grouper`). Mirrors
16717
+ * VIEW_OPENED's shape so the dashboard activity feed can group both
16718
+ * as "operator coordination surfaces."
16719
+ */
16720
+ WORKFLOW_VIEW_OPENED: "operator_workflow_view_opened",
16721
+ /**
16722
+ * v1.3 Omega-3: operator drilled into a single workflow for its
16723
+ * timeline + member-handoffs detail. Mirrors ENTRY_DRILLED's shape.
16724
+ */
16725
+ WORKFLOW_DRILLED: "operator_workflow_drilled",
16726
+ /**
16727
+ * v1.3 Omega-3: server-side state transition observed on a
16728
+ * workflow (e.g., in_progress -> stalled). Emitted by the route
16729
+ * layer after the state tracker diffs against its prior snapshot.
16730
+ * Distinct from the operator-action events above: this records what
16731
+ * the workflow itself is doing, not what the operator clicked.
16732
+ */
16733
+ WORKFLOW_STATE_CHANGED: "coordination_workflow_state_changed"
16712
16734
  };
16713
16735
 
16736
+ // src/coordination/context-transfer-extractor.ts
16737
+ var SUMMARY_MAX_CHARS = 240;
16738
+ var CATEGORY_VALUES = [
16739
+ "memory",
16740
+ "credentials",
16741
+ "plans",
16742
+ "outputs",
16743
+ "audit-refs",
16744
+ "other"
16745
+ ];
16746
+ async function extractContextTransferBreakdown(detail, deps = {}) {
16747
+ const pathA = tryStructuredPath(detail);
16748
+ if (pathA) return pathA;
16749
+ const pathB = tryCompositionPath(detail);
16750
+ if (pathB) return pathB;
16751
+ const pathC = tryHeuristicPath(detail);
16752
+ if (pathC.confidence >= 0.5 || !deps.substrateSelector) {
16753
+ return pathC;
16754
+ }
16755
+ const assist = await tryLlmAssistPath(detail, deps.substrateSelector);
16756
+ return assist ?? pathC;
16757
+ }
16758
+ function tryStructuredPath(detail) {
16759
+ const details = sourceDetails(detail.source_audit_entry);
16760
+ if (!details) return null;
16761
+ const transferredRaw = details["transferred"];
16762
+ const withheldRaw = details["withheld"];
16763
+ if (transferredRaw === void 0 && withheldRaw === void 0) return null;
16764
+ const transferred = parseExplicitContextItems(transferredRaw);
16765
+ const withheld = parseExplicitContextItems(withheldRaw);
16766
+ return {
16767
+ handoff_entry_id: detail.entry.entry_id,
16768
+ transferred,
16769
+ withheld,
16770
+ source: "structured",
16771
+ confidence: 1
16772
+ };
16773
+ }
16774
+ function tryCompositionPath(detail) {
16775
+ const op = detail.source_audit_entry.operation;
16776
+ if (!op.startsWith("composition_completed")) return null;
16777
+ const details = sourceDetails(detail.source_audit_entry);
16778
+ if (!details) return null;
16779
+ const receiptRaw = details["receipt"];
16780
+ const sourceStateRaw = details["source_state_snapshot"];
16781
+ if (receiptRaw === void 0) return null;
16782
+ const transferred = parseExplicitContextItems(receiptRaw);
16783
+ const withheld = [];
16784
+ if (Array.isArray(sourceStateRaw)) {
16785
+ const transferredKeys = new Set(
16786
+ transferred.map((t) => `${t.category}:${t.summary}`)
16787
+ );
16788
+ for (const item of parseExplicitContextItems(sourceStateRaw)) {
16789
+ const key = `${item.category}:${item.summary}`;
16790
+ if (!transferredKeys.has(key)) withheld.push(item);
16791
+ }
16792
+ }
16793
+ return {
16794
+ handoff_entry_id: detail.entry.entry_id,
16795
+ transferred,
16796
+ withheld,
16797
+ source: "composition",
16798
+ confidence: 0.9
16799
+ };
16800
+ }
16801
+ function tryHeuristicPath(detail) {
16802
+ const entry = detail.entry;
16803
+ const audit = detail.source_audit_entry;
16804
+ const details = sourceDetails(audit);
16805
+ if (audit.operation === "cross_harness_approval_aggregated") {
16806
+ const ruleId = optString2(details, "policy_rule_id");
16807
+ if (ruleId) {
16808
+ const category = categoryFromPolicyRuleId(ruleId);
16809
+ const summary = `${ruleId} (${entry.source_agent_id} -> operator)`;
16810
+ return {
16811
+ handoff_entry_id: entry.entry_id,
16812
+ transferred: [
16813
+ {
16814
+ category,
16815
+ summary: truncate(summary, SUMMARY_MAX_CHARS),
16816
+ size_hint: "minimal"
16817
+ }
16818
+ ],
16819
+ withheld: [],
16820
+ source: "heuristic",
16821
+ confidence: 0.5
16822
+ };
16823
+ }
16824
+ }
16825
+ if (audit.operation === "v1.1_local_handoff") {
16826
+ const reasonClass = optString2(details, "reason_class");
16827
+ const newStatus = optString2(details, "new_status");
16828
+ const previousStatus = optString2(details, "previous_status");
16829
+ const transferred = [];
16830
+ const withheld = [];
16831
+ if (newStatus === "denied" || newStatus === "failed") {
16832
+ withheld.push({
16833
+ category: "other",
16834
+ summary: truncate(
16835
+ `handoff ${newStatus}${reasonClass ? ` (${reasonClass})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16836
+ SUMMARY_MAX_CHARS
16837
+ ),
16838
+ size_hint: "minimal"
16839
+ });
16840
+ } else if (newStatus === "accepted" || newStatus === "completed") {
16841
+ transferred.push({
16842
+ category: "other",
16843
+ summary: truncate(
16844
+ `handoff ${newStatus}${previousStatus ? ` (from ${previousStatus})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16845
+ SUMMARY_MAX_CHARS
16846
+ ),
16847
+ size_hint: "small"
16848
+ });
16849
+ } else {
16850
+ transferred.push({
16851
+ category: "other",
16852
+ summary: truncate(
16853
+ `handoff ${entry.source_agent_id} -> ${entry.target_agent_id}${newStatus ? ` (${newStatus})` : ""}`,
16854
+ SUMMARY_MAX_CHARS
16855
+ ),
16856
+ size_hint: "minimal"
16857
+ });
16858
+ }
16859
+ return {
16860
+ handoff_entry_id: entry.entry_id,
16861
+ transferred,
16862
+ withheld,
16863
+ source: "heuristic",
16864
+ confidence: reasonClass || newStatus ? 0.5 : 0.3
16865
+ };
16866
+ }
16867
+ return {
16868
+ handoff_entry_id: entry.entry_id,
16869
+ transferred: [
16870
+ {
16871
+ category: "other",
16872
+ summary: truncate(
16873
+ `handoff ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16874
+ SUMMARY_MAX_CHARS
16875
+ ),
16876
+ size_hint: "minimal"
16877
+ }
16878
+ ],
16879
+ withheld: [],
16880
+ source: "heuristic",
16881
+ confidence: 0.3
16882
+ };
16883
+ }
16884
+ async function tryLlmAssistPath(detail, selector) {
16885
+ const entry = detail.entry;
16886
+ const audit = detail.source_audit_entry;
16887
+ const probe = `event=${audit.operation} sender=${entry.source_agent_id} target=${entry.target_agent_id} summary=${entry.context_transfer_summary}`;
16888
+ try {
16889
+ const response = await selector.invokeClassify("sentinel-scoring", {
16890
+ kind: "classify",
16891
+ items: [probe],
16892
+ categories: [...CATEGORY_VALUES]
16893
+ });
16894
+ if (response.body.kind !== "classify") return null;
16895
+ const top = response.body.results[0];
16896
+ if (!top || !isCategory(top.category) || top.confidence < 0.4) {
16897
+ return null;
16898
+ }
16899
+ return {
16900
+ handoff_entry_id: entry.entry_id,
16901
+ transferred: [
16902
+ {
16903
+ category: top.category,
16904
+ summary: truncate(
16905
+ `LLM-classified handoff ${entry.source_agent_id} -> ${entry.target_agent_id}: ${top.category} (confidence ${top.confidence.toFixed(2)})`,
16906
+ SUMMARY_MAX_CHARS
16907
+ ),
16908
+ size_hint: "minimal"
16909
+ }
16910
+ ],
16911
+ withheld: [],
16912
+ source: "llm-assist",
16913
+ confidence: 0.6
16914
+ };
16915
+ } catch {
16916
+ return null;
16917
+ }
16918
+ }
16919
+ function sourceDetails(audit) {
16920
+ return audit.details;
16921
+ }
16922
+ function optString2(details, key) {
16923
+ if (!details) return null;
16924
+ const value = details[key];
16925
+ if (typeof value !== "string" || value.length === 0) return null;
16926
+ return value;
16927
+ }
16928
+ function isCategory(value) {
16929
+ return CATEGORY_VALUES.includes(value);
16930
+ }
16931
+ function truncate(s, cap) {
16932
+ return s.length <= cap ? s : `${s.slice(0, cap - 3)}...`;
16933
+ }
16934
+ function parseExplicitContextItems(raw) {
16935
+ if (raw === null || raw === void 0) return [];
16936
+ if (Array.isArray(raw)) {
16937
+ const out = [];
16938
+ for (const entry of raw) {
16939
+ if (typeof entry === "string") {
16940
+ out.push({
16941
+ category: "other",
16942
+ summary: truncate(entry, SUMMARY_MAX_CHARS),
16943
+ size_hint: "minimal"
16944
+ });
16945
+ continue;
16946
+ }
16947
+ if (entry && typeof entry === "object") {
16948
+ const obj = entry;
16949
+ const category = isCategoryValue(obj["category"]) ? obj["category"] : "other";
16950
+ const summary = typeof obj["summary"] === "string" ? truncate(obj["summary"], SUMMARY_MAX_CHARS) : "(unspecified)";
16951
+ const sizeHint = isSizeHintValue(obj["size_hint"]) ? obj["size_hint"] : "minimal";
16952
+ out.push({ category, summary, size_hint: sizeHint });
16953
+ }
16954
+ }
16955
+ return out;
16956
+ }
16957
+ if (typeof raw === "object" && raw !== null) {
16958
+ const out = [];
16959
+ for (const [k, v] of Object.entries(raw)) {
16960
+ const category = isCategoryValue(k) ? k : "other";
16961
+ if (Array.isArray(v)) {
16962
+ for (const item of v) {
16963
+ if (typeof item === "string") {
16964
+ out.push({
16965
+ category,
16966
+ summary: truncate(item, SUMMARY_MAX_CHARS),
16967
+ size_hint: "minimal"
16968
+ });
16969
+ }
16970
+ }
16971
+ }
16972
+ }
16973
+ return out;
16974
+ }
16975
+ return [];
16976
+ }
16977
+ function isCategoryValue(v) {
16978
+ return typeof v === "string" && CATEGORY_VALUES.includes(v);
16979
+ }
16980
+ function isSizeHintValue(v) {
16981
+ return typeof v === "string" && (v === "minimal" || v === "small" || v === "medium" || v === "large");
16982
+ }
16983
+ function categoryFromPolicyRuleId(ruleId) {
16984
+ const lower = ruleId.toLowerCase();
16985
+ if (lower.includes("credential") || lower.includes("broker_secret")) {
16986
+ return "credentials";
16987
+ }
16988
+ if (lower.includes("memory") || lower.includes("state_read")) {
16989
+ return "memory";
16990
+ }
16991
+ if (lower.includes("plan")) {
16992
+ return "plans";
16993
+ }
16994
+ if (lower.includes("export") || lower.includes("output")) {
16995
+ return "outputs";
16996
+ }
16997
+ if (lower.includes("audit")) {
16998
+ return "audit-refs";
16999
+ }
17000
+ return "other";
17001
+ }
17002
+ var CONTEXT_TRANSFER_AUDIT_OPS = {
17003
+ DECODED: "operator_handoff_context_transfer_decoded"
17004
+ };
17005
+ var HEURISTIC_WINDOW_MS = 5 * 60 * 1e3;
17006
+ var STALL_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
17007
+ var CYCLE_COMPLETION_MIN_HOPS = 2;
17008
+ function groupHandoffsIntoWorkflows(handoffs, opts) {
17009
+ if (handoffs.length === 0) return [];
17010
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
17011
+ const linkedGroups = /* @__PURE__ */ new Map();
17012
+ const unlinked = [];
17013
+ for (const h of handoffs) {
17014
+ if (h.workflow_link !== null && h.workflow_link.length > 0) {
17015
+ let bucket = linkedGroups.get(h.workflow_link);
17016
+ if (!bucket) {
17017
+ bucket = [];
17018
+ linkedGroups.set(h.workflow_link, bucket);
17019
+ }
17020
+ bucket.push(h);
17021
+ } else {
17022
+ unlinked.push(h);
17023
+ }
17024
+ }
17025
+ const sortedUnlinked = [...unlinked].sort(
17026
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17027
+ );
17028
+ const heuristicChains = [];
17029
+ for (const h of sortedUnlinked) {
17030
+ const joinedIdx = findExtendableChain(heuristicChains, h);
17031
+ if (joinedIdx !== null) {
17032
+ heuristicChains[joinedIdx].push(h);
17033
+ } else {
17034
+ heuristicChains.push([h]);
17035
+ }
17036
+ }
17037
+ const workflows = [];
17038
+ for (const members of linkedGroups.values()) {
17039
+ workflows.push(materialize(members, now));
17040
+ }
17041
+ for (const members of heuristicChains) {
17042
+ workflows.push(materialize(members, now));
17043
+ }
17044
+ workflows.sort(
17045
+ (a, b) => a.last_activity_at < b.last_activity_at ? 1 : -1
17046
+ );
17047
+ return workflows;
17048
+ }
17049
+ function determineWorkflowState(members, now) {
17050
+ if (members.length === 0) return "unknown";
17051
+ const sorted = [...members].sort(
17052
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17053
+ );
17054
+ const last = sorted[sorted.length - 1];
17055
+ const root = sorted[0];
17056
+ const lastMs = Date.parse(last.observed_at);
17057
+ if (!Number.isFinite(lastMs)) return "unknown";
17058
+ if (last.target_agent_id === OPERATOR_PSEUDO_AGENT) {
17059
+ return "completed";
17060
+ }
17061
+ if (sorted.length > CYCLE_COMPLETION_MIN_HOPS && last.target_agent_id === root.source_agent_id) {
17062
+ return "completed";
17063
+ }
17064
+ const ageMs = now.getTime() - lastMs;
17065
+ if (ageMs > STALL_THRESHOLD_MS) {
17066
+ return "stalled";
17067
+ }
17068
+ return "in_progress";
17069
+ }
17070
+ function workflowIdFromRoot(rootEntryId) {
17071
+ return crypto.createHash("sha256").update(`workflow:${rootEntryId}`).digest("hex").slice(0, 32);
17072
+ }
17073
+ function findExtendableChain(chains, h) {
17074
+ const hMs = Date.parse(h.observed_at);
17075
+ if (!Number.isFinite(hMs)) return null;
17076
+ let bestIdx = null;
17077
+ let bestGapMs = Number.POSITIVE_INFINITY;
17078
+ for (let i = 0; i < chains.length; i += 1) {
17079
+ const chain = chains[i];
17080
+ const last = chain[chain.length - 1];
17081
+ const lastMs = Date.parse(last.observed_at);
17082
+ if (!Number.isFinite(lastMs)) continue;
17083
+ const gapMs = Math.abs(hMs - lastMs);
17084
+ if (gapMs > HEURISTIC_WINDOW_MS) continue;
17085
+ if (!sharesAgent(last, h)) continue;
17086
+ if (gapMs < bestGapMs) {
17087
+ bestGapMs = gapMs;
17088
+ bestIdx = i;
17089
+ }
17090
+ }
17091
+ return bestIdx;
17092
+ }
17093
+ function sharesAgent(a, b) {
17094
+ 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;
17095
+ }
17096
+ function materialize(members, now) {
17097
+ const sorted = [...members].sort(
17098
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17099
+ );
17100
+ const root = sorted[0];
17101
+ const last = sorted[sorted.length - 1];
17102
+ const involved = /* @__PURE__ */ new Set();
17103
+ for (const h of sorted) {
17104
+ if (h.source_agent_id) involved.add(h.source_agent_id);
17105
+ if (h.target_agent_id) involved.add(h.target_agent_id);
17106
+ }
17107
+ return {
17108
+ workflow_id: workflowIdFromRoot(root.entry_id),
17109
+ root_handoff: root,
17110
+ member_handoffs: sorted,
17111
+ state: determineWorkflowState(sorted, now),
17112
+ started_at: root.observed_at,
17113
+ last_activity_at: last.observed_at,
17114
+ involved_agents: [...involved].sort()
17115
+ };
17116
+ }
17117
+
16714
17118
  // src/coordination/handoff-routes.ts
16715
17119
  var COORDINATION_API_PREFIX = "/api/coordination";
16716
17120
  var COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
17121
+ var COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
16717
17122
  var COORDINATION_LIST_DEFAULT_LIMIT = 50;
16718
17123
  var COORDINATION_LIST_MAX_LIMIT = 500;
16719
17124
  var HandoffEventBridge = class {
@@ -16752,6 +17157,101 @@ function matchEntryRoute2(path) {
16752
17157
  if (rest.includes("/")) return null;
16753
17158
  return { entryId: decodeURIComponent(rest) };
16754
17159
  }
17160
+ function matchWorkflowRoute(path) {
17161
+ const prefix = `${COORDINATION_WORKFLOWS_PREFIX}/`;
17162
+ if (!path.startsWith(prefix)) return null;
17163
+ const rest = path.slice(prefix.length);
17164
+ if (rest.length === 0 || rest === "stream") return null;
17165
+ if (rest.includes("/")) return null;
17166
+ return { workflowId: decodeURIComponent(rest) };
17167
+ }
17168
+ async function computeWorkflowsAndTrackTransitions(deps) {
17169
+ const handoffs = await deps.handoffLog.query({ limit: 500 });
17170
+ const workflows = groupHandoffsIntoWorkflows(handoffs, {
17171
+ ...deps.now !== void 0 ? { now: deps.now() } : {}
17172
+ });
17173
+ const transitions = deps.workflowStateTracker ? deps.workflowStateTracker.observe(workflows) : [];
17174
+ for (const change of transitions) {
17175
+ deps.auditLog.append(
17176
+ "l2",
17177
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_STATE_CHANGED,
17178
+ deps.operatorId,
17179
+ {
17180
+ fortress_id: deps.handoffLog.getFortressId(),
17181
+ workflow_id: change.workflow_id,
17182
+ previous_state: change.previous_state,
17183
+ new_state: change.new_state
17184
+ }
17185
+ );
17186
+ }
17187
+ return { workflows, transitions };
17188
+ }
17189
+ function filterWorkflowList(workflows, opts) {
17190
+ let filtered = workflows;
17191
+ if (opts.state) {
17192
+ filtered = filtered.filter((w) => w.state === opts.state);
17193
+ }
17194
+ if (opts.agentId) {
17195
+ filtered = filtered.filter((w) => w.involved_agents.includes(opts.agentId));
17196
+ }
17197
+ if (opts.since) {
17198
+ filtered = filtered.filter((w) => w.last_activity_at >= opts.since);
17199
+ }
17200
+ return filtered.slice(0, opts.limit);
17201
+ }
17202
+ function isWorkflowState(value) {
17203
+ return value === "in_progress" || value === "completed" || value === "stalled" || value === "unknown";
17204
+ }
17205
+ async function handleWorkflowStream(deps, res) {
17206
+ res.writeHead(200, {
17207
+ "Content-Type": "text/event-stream",
17208
+ "Cache-Control": "no-cache, no-transform",
17209
+ Connection: "keep-alive",
17210
+ "X-Accel-Buffering": "no"
17211
+ });
17212
+ const initial = await computeWorkflowsAndTrackTransitions(deps);
17213
+ res.write(
17214
+ `event: workflow_snapshot
17215
+ data: ${JSON.stringify({ workflows: initial.workflows })}
17216
+
17217
+ `
17218
+ );
17219
+ if (initial.transitions.length > 0) {
17220
+ res.write(
17221
+ `event: workflow_state_changed
17222
+ data: ${JSON.stringify({ transitions: initial.transitions })}
17223
+
17224
+ `
17225
+ );
17226
+ }
17227
+ const unsubscribe = deps.events.subscribe(() => {
17228
+ void (async () => {
17229
+ try {
17230
+ const tick = await computeWorkflowsAndTrackTransitions(deps);
17231
+ res.write(
17232
+ `event: workflow_snapshot
17233
+ data: ${JSON.stringify({ workflows: tick.workflows })}
17234
+
17235
+ `
17236
+ );
17237
+ if (tick.transitions.length > 0) {
17238
+ res.write(
17239
+ `event: workflow_state_changed
17240
+ data: ${JSON.stringify({ transitions: tick.transitions })}
17241
+
17242
+ `
17243
+ );
17244
+ }
17245
+ } catch {
17246
+ }
17247
+ })();
17248
+ });
17249
+ const cleanup = () => {
17250
+ unsubscribe();
17251
+ };
17252
+ res.on("close", cleanup);
17253
+ res.on("error", cleanup);
17254
+ }
16755
17255
  async function handleStream3(deps, res) {
16756
17256
  res.writeHead(200, {
16757
17257
  "Content-Type": "text/event-stream",
@@ -16835,6 +17335,67 @@ async function handleCoordinationRoute(deps, req, res) {
16835
17335
  writeJSON6(res, 200, { ok: true, data: { entries } });
16836
17336
  return true;
16837
17337
  }
17338
+ if (method === "GET" && path === `${COORDINATION_WORKFLOWS_PREFIX}/stream`) {
17339
+ await handleWorkflowStream(deps, res);
17340
+ return true;
17341
+ }
17342
+ if (method === "GET" && path === COORDINATION_WORKFLOWS_PREFIX) {
17343
+ const limit = parseLimit4(
17344
+ url.searchParams.get("limit"),
17345
+ COORDINATION_LIST_DEFAULT_LIMIT,
17346
+ COORDINATION_LIST_MAX_LIMIT
17347
+ );
17348
+ const rawState = url.searchParams.get("state");
17349
+ const state = rawState && isWorkflowState(rawState) ? rawState : void 0;
17350
+ const since = url.searchParams.get("since") ?? void 0;
17351
+ const agentId = url.searchParams.get("agent_id") ?? void 0;
17352
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
17353
+ const filtered = filterWorkflowList(computed.workflows, {
17354
+ ...state !== void 0 ? { state } : {},
17355
+ ...agentId !== void 0 ? { agentId } : {},
17356
+ ...since !== void 0 ? { since } : {},
17357
+ limit
17358
+ });
17359
+ deps.auditLog.append(
17360
+ "l2",
17361
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_VIEW_OPENED,
17362
+ deps.operatorId,
17363
+ {
17364
+ fortress_id: deps.handoffLog.getFortressId(),
17365
+ result_count: filtered.length,
17366
+ ...state !== void 0 ? { state } : {},
17367
+ ...agentId !== void 0 ? { agent_id: agentId } : {},
17368
+ ...since !== void 0 ? { since } : {}
17369
+ }
17370
+ );
17371
+ writeJSON6(res, 200, { ok: true, data: { workflows: filtered } });
17372
+ return true;
17373
+ }
17374
+ const workflowMatch = matchWorkflowRoute(path);
17375
+ if (method === "GET" && workflowMatch) {
17376
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
17377
+ const wf = computed.workflows.find(
17378
+ (w) => w.workflow_id === workflowMatch.workflowId
17379
+ );
17380
+ if (!wf) {
17381
+ writeJSON6(res, 404, { ok: false, error: "not_found" });
17382
+ return true;
17383
+ }
17384
+ deps.auditLog.append(
17385
+ "l2",
17386
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_DRILLED,
17387
+ deps.operatorId,
17388
+ {
17389
+ fortress_id: deps.handoffLog.getFortressId(),
17390
+ workflow_id: wf.workflow_id,
17391
+ state: wf.state,
17392
+ member_count: wf.member_handoffs.length,
17393
+ involved_agent_count: wf.involved_agents.length
17394
+ }
17395
+ );
17396
+ writeJSON6(res, 200, { ok: true, data: { workflow: wf } });
17397
+ return true;
17398
+ }
16838
17399
  const entryMatch = matchEntryRoute2(path);
16839
17400
  if (method === "GET" && entryMatch) {
16840
17401
  const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
@@ -16854,7 +17415,29 @@ async function handleCoordinationRoute(deps, req, res) {
16854
17415
  target_agent_id: detail.entry.target_agent_id
16855
17416
  }
16856
17417
  );
16857
- writeJSON6(res, 200, { ok: true, data: detail });
17418
+ let breakdown = null;
17419
+ try {
17420
+ breakdown = await extractContextTransferBreakdown(
17421
+ detail,
17422
+ deps.contextTransfer ?? {}
17423
+ );
17424
+ deps.auditLog.append(
17425
+ "l2",
17426
+ CONTEXT_TRANSFER_AUDIT_OPS.DECODED,
17427
+ deps.operatorId,
17428
+ {
17429
+ fortress_id: deps.handoffLog.getFortressId(),
17430
+ entry_id: detail.entry.entry_id,
17431
+ extractor_path: breakdown.source,
17432
+ confidence: breakdown.confidence,
17433
+ transferred_count: breakdown.transferred.length,
17434
+ withheld_count: breakdown.withheld.length
17435
+ }
17436
+ );
17437
+ } catch {
17438
+ }
17439
+ const responseData = breakdown !== null ? { ...detail, context_transfer_breakdown: breakdown } : detail;
17440
+ writeJSON6(res, 200, { ok: true, data: responseData });
16858
17441
  return true;
16859
17442
  }
16860
17443
  writeJSON6(res, 404, { ok: false, error: "not_found", path });
@@ -16954,6 +17537,8 @@ var DashboardApprovalChannel = class {
16954
17537
  */
16955
17538
  handoffLog = null;
16956
17539
  handoffEventBridge = null;
17540
+ handoffContextTransfer = null;
17541
+ workflowStateTracker = null;
16957
17542
  handoffAuditLog = null;
16958
17543
  handoffOperatorId = null;
16959
17544
  constructor(config) {
@@ -17034,6 +17619,8 @@ var DashboardApprovalChannel = class {
17034
17619
  this.handoffEventBridge = opts.eventBridge ?? null;
17035
17620
  this.handoffAuditLog = opts.auditLog ?? null;
17036
17621
  this.handoffOperatorId = opts.operatorId ?? null;
17622
+ this.handoffContextTransfer = opts.contextTransfer ?? null;
17623
+ this.workflowStateTracker = opts.workflowStateTracker ?? null;
17037
17624
  }
17038
17625
  /**
17039
17626
  * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
@@ -17091,7 +17678,9 @@ var DashboardApprovalChannel = class {
17091
17678
  handoffLog: this.handoffLog,
17092
17679
  auditLog: this.handoffAuditLog,
17093
17680
  operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
17094
- events: this.handoffEventBridge
17681
+ events: this.handoffEventBridge,
17682
+ ...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
17683
+ ...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
17095
17684
  },
17096
17685
  req,
17097
17686
  res
@@ -21311,13 +21900,33 @@ var SentinelDispatcher = class {
21311
21900
  }
21312
21901
  }
21313
21902
  };
21903
+
21904
+ // src/anomaly-detection/classifier-state-store.ts
21905
+ init_encryption();
21906
+ init_encoding();
21907
+
21908
+ // src/anomaly-detection/classifiers/cusum.ts
21909
+ var CUSUM_CLASSIFIER_ID = "cusum";
21910
+
21911
+ // src/anomaly-detection/classifiers/psi.ts
21912
+ var PSI_CLASSIFIER_ID = "psi";
21913
+
21914
+ // src/anomaly-detection/anomaly-pipeline.ts
21314
21915
  var ANOMALY_AUDIT_OPS = {
21315
21916
  DETECTOR_REGISTERED: "anomaly_detector_registered",
21316
21917
  DETECTOR_UNREGISTERED: "anomaly_detector_unregistered",
21317
21918
  FINDING_EMITTED: "anomaly_finding_emitted",
21318
21919
  EVALUATION_FAILED: "anomaly_evaluation_failed",
21319
21920
  TRAINING_COMPLETED: "anomaly_training_completed",
21320
- TRAINING_FAILED: "anomaly_training_failed"
21921
+ TRAINING_FAILED: "anomaly_training_failed",
21922
+ /** Chi-2: a classifier was attached to an existing detector. */
21923
+ CLASSIFIER_SUBSCRIBED: "anomaly_classifier_subscribed",
21924
+ /** Chi-2: a classifier was detached from an existing detector. */
21925
+ CLASSIFIER_UNSUBSCRIBED: "anomaly_classifier_unsubscribed",
21926
+ /** Chi-2: CUSUM-flagged mean-shift drift on a per-agent feature. */
21927
+ CUSUM_DRIFT_DETECTED: "anomaly_cusum_drift_detected",
21928
+ /** Chi-2: PSI-flagged distribution shift on a per-agent feature. */
21929
+ PSI_DISTRIBUTION_SHIFT_DETECTED: "anomaly_psi_distribution_shift_detected"
21321
21930
  };
21322
21931
  var DEFAULT_TICK_INTERVAL_MS2 = 6e4;
21323
21932
  var AnomalyPipelineDispatcher = class {
@@ -21408,34 +22017,37 @@ var AnomalyPipelineDispatcher = class {
21408
22017
  const stamped = await this.routeFinding(detectorId, raw);
21409
22018
  findings.push(stamped);
21410
22019
  }
21411
- try {
21412
- const trainingResult = await detector.classifier.train();
21413
- this.auditLog.append(
21414
- "l2",
21415
- ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
21416
- this.identityId,
21417
- {
21418
- detector_id: detectorId,
21419
- classifier_id: detector.classifier.classifierId,
21420
- trained_at: trainingResult.trained_at,
21421
- sample_count: trainingResult.sample_count,
21422
- agent_count: trainingResult.agent_count,
21423
- fortress_id: this.fortressId
21424
- }
21425
- );
21426
- } catch (trainErr) {
21427
- const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
21428
- this.auditLog.append(
21429
- "l2",
21430
- ANOMALY_AUDIT_OPS.TRAINING_FAILED,
21431
- this.identityId,
21432
- {
21433
- detector_id: detectorId,
21434
- error_message: message,
21435
- fortress_id: this.fortressId
21436
- },
21437
- "failure"
21438
- );
22020
+ for (const classifier of detector.getAllClassifiers()) {
22021
+ try {
22022
+ const trainingResult = await classifier.train();
22023
+ this.auditLog.append(
22024
+ "l2",
22025
+ ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
22026
+ this.identityId,
22027
+ {
22028
+ detector_id: detectorId,
22029
+ classifier_id: classifier.classifierId,
22030
+ trained_at: trainingResult.trained_at,
22031
+ sample_count: trainingResult.sample_count,
22032
+ agent_count: trainingResult.agent_count,
22033
+ fortress_id: this.fortressId
22034
+ }
22035
+ );
22036
+ } catch (trainErr) {
22037
+ const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
22038
+ this.auditLog.append(
22039
+ "l2",
22040
+ ANOMALY_AUDIT_OPS.TRAINING_FAILED,
22041
+ this.identityId,
22042
+ {
22043
+ detector_id: detectorId,
22044
+ classifier_id: classifier.classifierId,
22045
+ error_message: message,
22046
+ fortress_id: this.fortressId
22047
+ },
22048
+ "failure"
22049
+ );
22050
+ }
21439
22051
  }
21440
22052
  } catch (err) {
21441
22053
  const message = err instanceof Error ? err.message : String(err);
@@ -21498,6 +22110,7 @@ var AnomalyPipelineDispatcher = class {
21498
22110
  observed_at: raw.observed_at || this.now().toISOString()
21499
22111
  };
21500
22112
  await this.findingStore.saveFinding(stamped);
22113
+ const classifierId = stamped.details["classifier_id"] ?? null;
21501
22114
  this.auditLog.append(
21502
22115
  "l2",
21503
22116
  ANOMALY_AUDIT_OPS.FINDING_EMITTED,
@@ -21507,13 +22120,79 @@ var AnomalyPipelineDispatcher = class {
21507
22120
  finding_id: stamped.finding_id,
21508
22121
  severity: stamped.severity,
21509
22122
  anomaly_score: stamped.details["anomaly_score"] ?? null,
22123
+ ...classifierId !== null ? { classifier_id: classifierId } : {},
21510
22124
  ...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
21511
22125
  fortress_id: this.fortressId
21512
22126
  }
21513
22127
  );
22128
+ const specificOp = classifierSpecificAuditOp(classifierId);
22129
+ if (specificOp !== null) {
22130
+ this.auditLog.append("l2", specificOp, this.identityId, {
22131
+ detector_id: detectorId,
22132
+ finding_id: stamped.finding_id,
22133
+ severity: stamped.severity,
22134
+ anomaly_score: stamped.details["anomaly_score"] ?? null,
22135
+ ...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
22136
+ fortress_id: this.fortressId
22137
+ });
22138
+ }
21514
22139
  this.emit({ type: "finding", finding: stamped });
21515
22140
  return stamped;
21516
22141
  }
22142
+ /**
22143
+ * Chi-2: attach an additional classifier to an already-registered
22144
+ * detector. Emits ANOMALY_CLASSIFIER_SUBSCRIBED on success. The
22145
+ * factory is called with the fortress AnomalyContext so the
22146
+ * classifier can build its own state-store binding. Idempotent: a
22147
+ * second call with the same classifierId returns false.
22148
+ */
22149
+ async addClassifierToDetector(detectorId, factory) {
22150
+ const detector = this.detectors.get(detectorId);
22151
+ if (!detector) return false;
22152
+ const context = {
22153
+ fortressId: this.fortressId,
22154
+ auditLog: this.auditLog,
22155
+ storage: this.storage,
22156
+ masterKey: this.masterKey,
22157
+ now: this.now
22158
+ };
22159
+ const classifier = factory(context);
22160
+ const added = detector.addClassifier(classifier);
22161
+ if (!added) return false;
22162
+ this.auditLog.append(
22163
+ "l2",
22164
+ ANOMALY_AUDIT_OPS.CLASSIFIER_SUBSCRIBED,
22165
+ this.identityId,
22166
+ {
22167
+ detector_id: detectorId,
22168
+ classifier_id: classifier.classifierId,
22169
+ fortress_id: this.fortressId
22170
+ }
22171
+ );
22172
+ return true;
22173
+ }
22174
+ /**
22175
+ * Chi-2: detach an additional classifier from an already-registered
22176
+ * detector. Emits ANOMALY_CLASSIFIER_UNSUBSCRIBED on success. The
22177
+ * primary classifier cannot be detached (returns false).
22178
+ */
22179
+ async removeClassifierFromDetector(detectorId, classifierId) {
22180
+ const detector = this.detectors.get(detectorId);
22181
+ if (!detector) return false;
22182
+ const removed = detector.removeClassifier(classifierId);
22183
+ if (!removed) return false;
22184
+ this.auditLog.append(
22185
+ "l2",
22186
+ ANOMALY_AUDIT_OPS.CLASSIFIER_UNSUBSCRIBED,
22187
+ this.identityId,
22188
+ {
22189
+ detector_id: detectorId,
22190
+ classifier_id: classifierId,
22191
+ fortress_id: this.fortressId
22192
+ }
22193
+ );
22194
+ return true;
22195
+ }
21517
22196
  emit(event) {
21518
22197
  for (const listener of this.listeners) {
21519
22198
  try {
@@ -21523,6 +22202,80 @@ var AnomalyPipelineDispatcher = class {
21523
22202
  }
21524
22203
  }
21525
22204
  };
22205
+ function classifierSpecificAuditOp(classifierId) {
22206
+ if (classifierId === null) return null;
22207
+ if (classifierId === CUSUM_CLASSIFIER_ID) {
22208
+ return ANOMALY_AUDIT_OPS.CUSUM_DRIFT_DETECTED;
22209
+ }
22210
+ if (classifierId === PSI_CLASSIFIER_ID) {
22211
+ return ANOMALY_AUDIT_OPS.PSI_DISTRIBUTION_SHIFT_DETECTED;
22212
+ }
22213
+ return null;
22214
+ }
22215
+
22216
+ // src/coordination/workflow-state-tracker.ts
22217
+ var WorkflowStateTracker = class {
22218
+ states = /* @__PURE__ */ new Map();
22219
+ now;
22220
+ constructor(opts) {
22221
+ this.now = opts?.now ?? (() => /* @__PURE__ */ new Date());
22222
+ }
22223
+ /**
22224
+ * Diff the supplied workflow list against the last-observed states.
22225
+ * Returns the set of transitions detected this call; the tracker
22226
+ * mutates its internal map to reflect the new states.
22227
+ *
22228
+ * Transitions emitted:
22229
+ * - First observation of a workflow (`previous_state` is the
22230
+ * sentinel `unobserved`). Lets the route handler audit-emit
22231
+ * the initial state so the operator sees workflows as they
22232
+ * surface, not only when they change.
22233
+ * - Subsequent observation where `previous_state !== new_state`.
22234
+ */
22235
+ observe(workflows) {
22236
+ const out = [];
22237
+ const observedAt = this.now().toISOString();
22238
+ for (const wf of workflows) {
22239
+ const prior = this.states.get(wf.workflow_id);
22240
+ if (prior === void 0) {
22241
+ out.push({
22242
+ workflow_id: wf.workflow_id,
22243
+ previous_state: "unobserved",
22244
+ new_state: wf.state,
22245
+ observed_at: observedAt
22246
+ });
22247
+ this.states.set(wf.workflow_id, wf.state);
22248
+ continue;
22249
+ }
22250
+ if (prior !== wf.state) {
22251
+ out.push({
22252
+ workflow_id: wf.workflow_id,
22253
+ previous_state: prior,
22254
+ new_state: wf.state,
22255
+ observed_at: observedAt
22256
+ });
22257
+ this.states.set(wf.workflow_id, wf.state);
22258
+ }
22259
+ }
22260
+ return out;
22261
+ }
22262
+ /**
22263
+ * Drop a workflow's recorded state. Surfaced for tests + future
22264
+ * "operator dismissed this workflow" affordance; not currently
22265
+ * called by the production wiring.
22266
+ */
22267
+ forget(workflowId) {
22268
+ this.states.delete(workflowId);
22269
+ }
22270
+ /** Reset the tracker. Tests use this between runs. */
22271
+ reset() {
22272
+ this.states.clear();
22273
+ }
22274
+ /** Read-only view of the current snapshot. Useful for diagnostics. */
22275
+ snapshot() {
22276
+ return new Map(this.states);
22277
+ }
22278
+ };
21526
22279
 
21527
22280
  // src/sentinel/sentinel.ts
21528
22281
  var Sentinel = class {
@@ -24757,7 +25510,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24757
25510
  const now = (/* @__PURE__ */ new Date()).toISOString();
24758
25511
  const canonicalBytes = canonicalize2(outcome);
24759
25512
  const canonicalString = new TextDecoder().decode(canonicalBytes);
24760
- const sha25611 = createCommitment(canonicalString);
25513
+ const sha25612 = createCommitment(canonicalString);
24761
25514
  let pedersenData;
24762
25515
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
24763
25516
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -24769,7 +25522,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24769
25522
  const commitmentPayload = {
24770
25523
  bridge_commitment_id: commitmentId,
24771
25524
  session_id: outcome.session_id,
24772
- sha256_commitment: sha25611.commitment,
25525
+ sha256_commitment: sha25612.commitment,
24773
25526
  terms_hash: outcome.terms_hash,
24774
25527
  committer_did: identity.did,
24775
25528
  committed_at: now,
@@ -24780,8 +25533,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24780
25533
  return {
24781
25534
  bridge_commitment_id: commitmentId,
24782
25535
  session_id: outcome.session_id,
24783
- sha256_commitment: sha25611.commitment,
24784
- blinding_factor: sha25611.blinding_factor,
25536
+ sha256_commitment: sha25612.commitment,
25537
+ blinding_factor: sha25612.blinding_factor,
24785
25538
  committer_did: identity.did,
24786
25539
  signature: toBase64url(signature),
24787
25540
  pedersen_commitment: pedersenData,
@@ -38202,6 +38955,136 @@ function tryParseClassification3(text) {
38202
38955
  }
38203
38956
  }
38204
38957
 
38958
+ // src/query-anonymity/header-strip.ts
38959
+ var QUERY_ANONYMITY_AUDIT_OPS = {
38960
+ HEADERS_STRIPPED: "query_anonymity_headers_stripped"
38961
+ };
38962
+ var CANONICAL_STRIP_LIST = [
38963
+ // Browser / runtime fingerprinting.
38964
+ { name: "user-agent", reason: "user-agent" },
38965
+ { name: "sec-ch-ua", reason: "fingerprintable-extension" },
38966
+ { name: "sec-ch-ua-mobile", reason: "fingerprintable-extension" },
38967
+ { name: "sec-ch-ua-platform", reason: "fingerprintable-extension" },
38968
+ { name: "sec-ch-ua-platform-version", reason: "fingerprintable-extension" },
38969
+ { name: "sec-ch-ua-arch", reason: "fingerprintable-extension" },
38970
+ { name: "sec-ch-ua-bitness", reason: "fingerprintable-extension" },
38971
+ { name: "sec-ch-ua-model", reason: "fingerprintable-extension" },
38972
+ { name: "sec-ch-ua-full-version-list", reason: "fingerprintable-extension" },
38973
+ // Locale fingerprint.
38974
+ { name: "accept-language", reason: "locale-fingerprint" },
38975
+ // Request-origin leak.
38976
+ { name: "referer", reason: "leaking-network-info" },
38977
+ { name: "referrer-policy", reason: "leaking-network-info" },
38978
+ { name: "origin", reason: "leaking-network-info" },
38979
+ // Forwarded-by / IP-derived network info.
38980
+ { name: "via", reason: "leaking-network-info" },
38981
+ { name: "forwarded", reason: "leaking-network-info" },
38982
+ { name: "x-forwarded-for", reason: "leaking-network-info" },
38983
+ { name: "x-real-ip", reason: "leaking-network-info" },
38984
+ { name: "x-client-ip", reason: "leaking-network-info" },
38985
+ // DNT / GPC are technically anti-tracking signals but they
38986
+ // themselves form a fingerprint (operators who set DNT=1 are a
38987
+ // smaller subset). Strip to keep the substrate ignorant of
38988
+ // operator preferences.
38989
+ { name: "dnt", reason: "unnecessary-metadata" },
38990
+ { name: "sec-gpc", reason: "unnecessary-metadata" }
38991
+ ];
38992
+ var REQUIRED_HEADERS = [
38993
+ "authorization",
38994
+ "content-type",
38995
+ "content-length",
38996
+ "host",
38997
+ "accept",
38998
+ "x-api-key",
38999
+ // Anthropic API auth
39000
+ "anthropic-version",
39001
+ // Anthropic API contract version
39002
+ "anthropic-beta",
39003
+ // optional Anthropic beta opt-in
39004
+ "openai-organization",
39005
+ // optional OpenAI org id
39006
+ "x-stainless-package-version",
39007
+ // allowed for Anthropic + OpenAI SDK contract compat
39008
+ "x-goog-api-key",
39009
+ // Google AI Studio
39010
+ "x-goog-user-project"
39011
+ // Google AI Studio
39012
+ ];
39013
+ var REQUIRED_HEADER_SET = new Set(
39014
+ REQUIRED_HEADERS.map((h) => h.toLowerCase())
39015
+ );
39016
+ var STRIP_REASON_BY_NAME = new Map(
39017
+ CANONICAL_STRIP_LIST.map((h) => [h.name.toLowerCase(), h.reason])
39018
+ );
39019
+ function stripHeaders(headers) {
39020
+ const stripped = {};
39021
+ const removed = [];
39022
+ for (const [name, value] of Object.entries(headers)) {
39023
+ const lower = name.toLowerCase();
39024
+ if (REQUIRED_HEADER_SET.has(lower)) {
39025
+ stripped[name] = value;
39026
+ continue;
39027
+ }
39028
+ const reason = STRIP_REASON_BY_NAME.get(lower);
39029
+ if (reason !== void 0) {
39030
+ removed.push({ name, reason });
39031
+ continue;
39032
+ }
39033
+ stripped[name] = value;
39034
+ }
39035
+ return { stripped, removed };
39036
+ }
39037
+ function defeatUndiciDefaultsInto(headers) {
39038
+ if (headers["user-agent"] === void 0 && headers["User-Agent"] === void 0) {
39039
+ headers["User-Agent"] = "";
39040
+ }
39041
+ if (headers["accept-language"] === void 0 && headers["Accept-Language"] === void 0) {
39042
+ headers["Accept-Language"] = "";
39043
+ }
39044
+ return headers;
39045
+ }
39046
+ function createAnonymizedFetch(baseFetch, onAudit) {
39047
+ const wrapped = async (input, init) => {
39048
+ const headers = normalizeHeadersInit(init?.headers);
39049
+ const result = stripHeaders(headers);
39050
+ defeatUndiciDefaultsInto(result.stripped);
39051
+ const preservedRequired = Object.keys(result.stripped).filter(
39052
+ (k) => REQUIRED_HEADER_SET.has(k.toLowerCase())
39053
+ );
39054
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
39055
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
39056
+ if (onAudit) {
39057
+ onAudit({
39058
+ url,
39059
+ method,
39060
+ stripped_count: result.removed.length,
39061
+ removed: result.removed,
39062
+ required_preserved: preservedRequired
39063
+ });
39064
+ }
39065
+ return baseFetch(input, { ...init, headers: result.stripped });
39066
+ };
39067
+ return wrapped;
39068
+ }
39069
+ function normalizeHeadersInit(raw) {
39070
+ if (raw === void 0) return {};
39071
+ if (typeof Headers !== "undefined" && raw instanceof Headers) {
39072
+ const out = {};
39073
+ raw.forEach((value, key) => {
39074
+ out[key] = value;
39075
+ });
39076
+ return out;
39077
+ }
39078
+ if (Array.isArray(raw)) {
39079
+ const out = {};
39080
+ for (const [k, v] of raw) {
39081
+ if (k !== void 0 && v !== void 0) out[k] = v;
39082
+ }
39083
+ return out;
39084
+ }
39085
+ return { ...raw };
39086
+ }
39087
+
38205
39088
  // src/intelligence/substrates/hybrid/per-surface-router.ts
38206
39089
  function resolveHybridChoice(rules, surface) {
38207
39090
  if (!rules) return null;
@@ -38262,7 +39145,21 @@ var SubstrateSelector = class {
38262
39145
  this.auditLog = cfg.auditLog;
38263
39146
  this.identityId = cfg.identityId;
38264
39147
  this.redactor = cfg.redactor ?? IDENTITY_REDACTOR;
38265
- this.fetchImpl = cfg.fetchImpl;
39148
+ const baseFetch = cfg.fetchImpl ?? globalThis.fetch;
39149
+ this.fetchImpl = createAnonymizedFetch(baseFetch, (event) => {
39150
+ this.auditLog.append(
39151
+ "l2",
39152
+ QUERY_ANONYMITY_AUDIT_OPS.HEADERS_STRIPPED,
39153
+ this.identityId,
39154
+ {
39155
+ url: event.url,
39156
+ method: event.method,
39157
+ stripped_count: event.stripped_count,
39158
+ removed: event.removed,
39159
+ required_preserved: event.required_preserved
39160
+ }
39161
+ );
39162
+ });
38266
39163
  this.config = buildDefaultConfig();
38267
39164
  }
38268
39165
  /**
@@ -39188,6 +40085,150 @@ var EXIT_BUNDLE_ARTIFACT_KINDS = [
39188
40085
  "placeholder_vault_metadata"
39189
40086
  ];
39190
40087
 
40088
+ // src/recognition/did-web.ts
40089
+ init_encoding();
40090
+ init_hashing();
40091
+ var DEFAULT_TIMEOUT_MS4 = 5e3;
40092
+ var 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;
40093
+ async function resolveDidWeb(did, opts) {
40094
+ const parsed = parseDidWeb(did);
40095
+ const url = didToUrl(parsed);
40096
+ if (!opts.allowed_hosts.includes(parsed.authority_host)) {
40097
+ return {
40098
+ ok: false,
40099
+ failure: "host_not_allowed",
40100
+ message: `did-web: authority_host '${parsed.authority_host}' is not in the operator's allowed_hosts allowlist; resolution refused (no-outbound-by-default)`,
40101
+ url
40102
+ };
40103
+ }
40104
+ const timeoutMs = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
40105
+ const fetcher = opts.fetcher ?? defaultFetcher;
40106
+ const controller = new AbortController();
40107
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
40108
+ let response;
40109
+ try {
40110
+ response = await fetcher(url, { signal: controller.signal });
40111
+ } catch (err) {
40112
+ clearTimeout(timer);
40113
+ const message = err instanceof Error ? err.message : String(err);
40114
+ if (controller.signal.aborted) {
40115
+ return {
40116
+ ok: false,
40117
+ failure: "timeout",
40118
+ message: `did-web: resolution exceeded ${timeoutMs}ms`,
40119
+ url
40120
+ };
40121
+ }
40122
+ return {
40123
+ ok: false,
40124
+ failure: "fetch_failed",
40125
+ message: `did-web: fetch error: ${message}`,
40126
+ url
40127
+ };
40128
+ }
40129
+ clearTimeout(timer);
40130
+ if (response.status === 404) {
40131
+ return {
40132
+ ok: false,
40133
+ failure: "not_found",
40134
+ message: `did-web: 404 from authority host`,
40135
+ url
40136
+ };
40137
+ }
40138
+ if (!response.ok) {
40139
+ return {
40140
+ ok: false,
40141
+ failure: "fetch_failed",
40142
+ message: `did-web: authority host returned ${response.status}`,
40143
+ url
40144
+ };
40145
+ }
40146
+ let body;
40147
+ try {
40148
+ body = await response.json();
40149
+ } catch (err) {
40150
+ const message = err instanceof Error ? err.message : String(err);
40151
+ return {
40152
+ ok: false,
40153
+ failure: "invalid_json",
40154
+ message: `did-web: invalid JSON: ${message}`,
40155
+ url
40156
+ };
40157
+ }
40158
+ if (!isDidDocument(body, did)) {
40159
+ return {
40160
+ ok: false,
40161
+ failure: "invalid_json",
40162
+ message: `did-web: response body is not a valid DID Document for ${did}`,
40163
+ url
40164
+ };
40165
+ }
40166
+ if (opts.expected_public_key !== void 0) {
40167
+ const expectedX = toBase64url(opts.expected_public_key);
40168
+ const actualX = body.verificationMethod[0]?.publicKeyJwk.x;
40169
+ if (actualX !== expectedX) {
40170
+ return {
40171
+ ok: false,
40172
+ failure: "signature_mismatch",
40173
+ message: `did-web: verificationMethod public key does not match expected key`,
40174
+ url
40175
+ };
40176
+ }
40177
+ }
40178
+ return { ok: true, did_document: body, url };
40179
+ }
40180
+ function parseDidWeb(did) {
40181
+ if (!did.startsWith("did:web:")) {
40182
+ throw new Error(`did-web: '${did}' is not a did:web identifier`);
40183
+ }
40184
+ const rest = did.slice("did:web:".length);
40185
+ const segments = rest.split(":");
40186
+ const authorityHost = segments[0];
40187
+ if (!HOST_RE.test(authorityHost)) {
40188
+ throw new Error(`did-web: '${authorityHost}' is not a valid DNS host`);
40189
+ }
40190
+ const parsed = { authority_host: authorityHost };
40191
+ if (segments.length === 1) return parsed;
40192
+ if (segments.length === 5 && segments[1] === "fortress" && segments[3] === "agent") {
40193
+ parsed.fortress_id = segments[2];
40194
+ parsed.agent_label = segments[4];
40195
+ return parsed;
40196
+ }
40197
+ throw new Error(
40198
+ `did-web: '${did}' does not match the supported shapes (bare did:web:<host> or did:web:<host>:fortress:<fid>:agent:<alabel>)`
40199
+ );
40200
+ }
40201
+ function didToUrl(parsed) {
40202
+ if (parsed.fortress_id === void 0 || parsed.agent_label === void 0) {
40203
+ return `https://${parsed.authority_host}/.well-known/did.json`;
40204
+ }
40205
+ return `https://${parsed.authority_host}/fortress/${parsed.fortress_id}/agent/${parsed.agent_label}/did.json`;
40206
+ }
40207
+ function isDidDocument(value, expectedDid) {
40208
+ if (!value || typeof value !== "object") return false;
40209
+ const v = value;
40210
+ if (v["id"] !== expectedDid) return false;
40211
+ if (!Array.isArray(v["@context"])) return false;
40212
+ const vm = v["verificationMethod"];
40213
+ if (!Array.isArray(vm) || vm.length === 0) return false;
40214
+ const first = vm[0];
40215
+ if (!first || typeof first["id"] !== "string") return false;
40216
+ const jwk = first["publicKeyJwk"];
40217
+ if (!jwk || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519") return false;
40218
+ if (typeof jwk["x"] !== "string") return false;
40219
+ if (!Array.isArray(v["authentication"])) return false;
40220
+ if (!Array.isArray(v["assertionMethod"])) return false;
40221
+ return true;
40222
+ }
40223
+ async function defaultFetcher(url, init) {
40224
+ const response = await fetch(url, init);
40225
+ return {
40226
+ ok: response.ok,
40227
+ status: response.status,
40228
+ json: () => response.json()
40229
+ };
40230
+ }
40231
+
39191
40232
  // src/exit/bundle.ts
39192
40233
  init_hashing();
39193
40234
  init_encoding();
@@ -39612,6 +40653,11 @@ async function verifyExitBundle(bundleDir, options = {}) {
39612
40653
 
39613
40654
  // src/exit/bundle.ts
39614
40655
  var ARTIFACT_DIR = "artifacts";
40656
+ var EXIT_BUNDLE_DID_WEB_AUDIT_OPS = {
40657
+ EXPORT_INCLUDED: "exit_bundle_did_web_export_included",
40658
+ IMPORT_VERIFIED: "exit_bundle_did_web_import_verified",
40659
+ AUTHORITY_HOST: "exit_bundle_did_web_authority_host"
40660
+ };
39615
40661
  var EXIT_IMPORT_NAMESPACE = "_exit_imports";
39616
40662
  var EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
39617
40663
  var EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
@@ -39903,6 +40949,7 @@ async function exportExitBundle(opts) {
39903
40949
  "placeholder_vault_metadata"
39904
40950
  )
39905
40951
  );
40952
+ const didWebBinding = validateExportDidWeb(opts.didWeb);
39906
40953
  const body = {
39907
40954
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
39908
40955
  exported_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -39910,7 +40957,8 @@ async function exportExitBundle(opts) {
39910
40957
  identity_id: identity.identity_id,
39911
40958
  fortress_id: identity.did,
39912
40959
  fortress_master_pubkey: identity.public_key,
39913
- did: identity.did
40960
+ did: identity.did,
40961
+ ...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
39914
40962
  },
39915
40963
  source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
39916
40964
  artifacts,
@@ -39932,6 +40980,18 @@ async function exportExitBundle(opts) {
39932
40980
  };
39933
40981
  const manifestBytes = jsonBytes(manifest);
39934
40982
  await promises.writeFile(path.join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
40983
+ if (didWebBinding !== void 0) {
40984
+ opts.auditLog.append(
40985
+ "l1",
40986
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.EXPORT_INCLUDED,
40987
+ identity.identity_id,
40988
+ {
40989
+ approval_id: exportApprovalAuditId,
40990
+ identifier: didWebBinding.identifier,
40991
+ authority_host: didWebBinding.authority_host
40992
+ }
40993
+ );
40994
+ }
39935
40995
  await opts.auditLog.flush();
39936
40996
  return {
39937
40997
  bundle_dir: bundleDir,
@@ -39943,6 +41003,30 @@ async function exportExitBundle(opts) {
39943
41003
  ]
39944
41004
  };
39945
41005
  }
41006
+ function validateExportDidWeb(binding) {
41007
+ if (binding === void 0) return void 0;
41008
+ if (!binding.identifier || typeof binding.identifier !== "string") {
41009
+ throw new Error(
41010
+ "exit-bundle: did_web.identifier must be a non-empty did:web URI"
41011
+ );
41012
+ }
41013
+ if (!binding.authority_host || typeof binding.authority_host !== "string") {
41014
+ throw new Error(
41015
+ "exit-bundle: did_web.authority_host must be a non-empty DNS host"
41016
+ );
41017
+ }
41018
+ const parsed = parseDidWeb(binding.identifier);
41019
+ if (parsed.authority_host.toLowerCase() !== binding.authority_host.toLowerCase()) {
41020
+ throw new Error(
41021
+ `exit-bundle: did_web.identifier authority host '${parsed.authority_host}' does not match did_web.authority_host '${binding.authority_host}'`
41022
+ );
41023
+ }
41024
+ return {
41025
+ identifier: binding.identifier,
41026
+ authority_host: binding.authority_host,
41027
+ ...binding.published_at !== void 0 ? { published_at: binding.published_at } : {}
41028
+ };
41029
+ }
39946
41030
  function publicKeysFromIdentityArtifact(identityArtifact) {
39947
41031
  const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
39948
41032
  return {
@@ -40157,6 +41241,87 @@ async function importExitBundle(opts) {
40157
41241
  };
40158
41242
  }
40159
41243
  const manifest = await readManifest(opts.bundleDir);
41244
+ const importWarnings = [];
41245
+ const manifestDidWeb = manifest.body.identity_binding.did_web;
41246
+ if (manifestDidWeb !== void 0 && !opts.skipDidWebVerify) {
41247
+ const expectedPublicKey = fromBase64url(
41248
+ manifest.body.identity_binding.fortress_master_pubkey
41249
+ );
41250
+ const resolveOpts = {
41251
+ allowed_hosts: opts.didWebAllowedHosts ?? [],
41252
+ expected_public_key: expectedPublicKey,
41253
+ ...opts.didWebFetcher !== void 0 ? { fetcher: opts.didWebFetcher } : {},
41254
+ ...opts.didWebTimeoutMs !== void 0 ? { timeout_ms: opts.didWebTimeoutMs } : {}
41255
+ };
41256
+ const resolution = await resolveDidWeb(
41257
+ manifestDidWeb.identifier,
41258
+ resolveOpts
41259
+ );
41260
+ const authorityHost = manifestDidWeb.authority_host;
41261
+ opts.auditLog.append(
41262
+ "l1",
41263
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.AUTHORITY_HOST,
41264
+ manifest.body.identity_binding.identity_id,
41265
+ { authority_host: authorityHost, identifier: manifestDidWeb.identifier }
41266
+ );
41267
+ if (resolution.ok) {
41268
+ opts.auditLog.append(
41269
+ "l1",
41270
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41271
+ manifest.body.identity_binding.identity_id,
41272
+ {
41273
+ outcome: "success",
41274
+ identifier: manifestDidWeb.identifier,
41275
+ authority_host: authorityHost,
41276
+ resolved_url: resolution.url
41277
+ }
41278
+ );
41279
+ } else if (resolution.failure === "signature_mismatch") {
41280
+ opts.auditLog.append(
41281
+ "l1",
41282
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41283
+ manifest.body.identity_binding.identity_id,
41284
+ {
41285
+ outcome: "mismatch",
41286
+ identifier: manifestDidWeb.identifier,
41287
+ authority_host: authorityHost,
41288
+ resolved_url: resolution.url
41289
+ }
41290
+ );
41291
+ await opts.auditLog.flush();
41292
+ throw new ExitBundleImportError(
41293
+ "did_web_mismatch",
41294
+ `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.`
41295
+ );
41296
+ } else {
41297
+ opts.auditLog.append(
41298
+ "l1",
41299
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41300
+ manifest.body.identity_binding.identity_id,
41301
+ {
41302
+ outcome: "resolution_failure",
41303
+ failure: resolution.failure,
41304
+ identifier: manifestDidWeb.identifier,
41305
+ authority_host: authorityHost,
41306
+ resolved_url: resolution.url
41307
+ }
41308
+ );
41309
+ importWarnings.push(
41310
+ `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.`
41311
+ );
41312
+ }
41313
+ } else if (manifestDidWeb !== void 0 && opts.skipDidWebVerify) {
41314
+ opts.auditLog.append(
41315
+ "l1",
41316
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41317
+ manifest.body.identity_binding.identity_id,
41318
+ {
41319
+ outcome: "skipped",
41320
+ identifier: manifestDidWeb.identifier,
41321
+ authority_host: manifestDidWeb.authority_host
41322
+ }
41323
+ );
41324
+ }
40160
41325
  const identityArtifact = await loadExitArtifact(
40161
41326
  opts.bundleDir,
40162
41327
  manifest,
@@ -40221,7 +41386,7 @@ async function importExitBundle(opts) {
40221
41386
  unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
40222
41387
  },
40223
41388
  staged_artifacts: [],
40224
- warnings: verification.warnings,
41389
+ warnings: [...verification.warnings, ...importWarnings],
40225
41390
  unsupported_artifacts: verification.unsupported_artifacts
40226
41391
  };
40227
41392
  }
@@ -40388,7 +41553,7 @@ async function importExitBundle(opts) {
40388
41553
  state: stateResult,
40389
41554
  reputation: reputationResult,
40390
41555
  staged_artifacts: stagedArtifacts,
40391
- warnings: verification.warnings,
41556
+ warnings: [...verification.warnings, ...importWarnings],
40392
41557
  unsupported_artifacts: verification.unsupported_artifacts
40393
41558
  };
40394
41559
  }
@@ -40611,6 +41776,26 @@ ${policyErr.message}
40611
41776
  }
40612
41777
  throw policyErr;
40613
41778
  }
41779
+ const includeDidWebFlag = flagValue(argv, "--include-did-web");
41780
+ const includeDidWebDisabled = includeDidWebFlag === "false";
41781
+ const didWebIdentifier = flagValue(argv, "--did-web");
41782
+ const didWebAuthorityHost = flagValue(argv, "--did-web-authority-host");
41783
+ const didWebPublishedAt = flagValue(argv, "--did-web-published-at");
41784
+ let exportDidWeb;
41785
+ if (!includeDidWebDisabled && didWebIdentifier !== void 0) {
41786
+ if (didWebAuthorityHost === void 0) {
41787
+ write(
41788
+ err,
41789
+ "Error: --did-web requires --did-web-authority-host=<host>\n"
41790
+ );
41791
+ return 2;
41792
+ }
41793
+ exportDidWeb = {
41794
+ identifier: didWebIdentifier,
41795
+ authority_host: didWebAuthorityHost,
41796
+ ...didWebPublishedAt !== void 0 ? { published_at: didWebPublishedAt } : {}
41797
+ };
41798
+ }
40614
41799
  const result = await exportExitBundle({
40615
41800
  bundleDir: outDir,
40616
41801
  storage: ctx.storage,
@@ -40622,7 +41807,8 @@ ${policyErr.message}
40622
41807
  config,
40623
41808
  stateStoragePath: ctx.stateStoragePath,
40624
41809
  stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
40625
- keySource: ctx.keySource
41810
+ keySource: ctx.keySource,
41811
+ ...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
40626
41812
  });
40627
41813
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
40628
41814
  else {
@@ -40700,6 +41886,11 @@ ${policyErr.message}
40700
41886
  write(err, "--conflict must be skip, overwrite, or version\n");
40701
41887
  return 2;
40702
41888
  }
41889
+ const didWebAllowedHosts = repeatedFlagValues(
41890
+ argv,
41891
+ "--did-web-allowed-host"
41892
+ );
41893
+ const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
40703
41894
  let result;
40704
41895
  try {
40705
41896
  result = await importExitBundle({
@@ -40715,7 +41906,9 @@ ${policyErr.message}
40715
41906
  conflictResolution: conflict,
40716
41907
  sourcePassphrase: flagValue(argv, "--source-passphrase"),
40717
41908
  sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
40718
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
41909
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
41910
+ ...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
41911
+ skipDidWebVerify
40719
41912
  });
40720
41913
  } catch (e) {
40721
41914
  if (e instanceof InvalidExitBundleError) {
@@ -41452,12 +42645,14 @@ ${err.message}
41452
42645
  fortressId: fortressIdForAggregator
41453
42646
  });
41454
42647
  const handoffEventBridge = new HandoffEventBridge();
42648
+ const workflowStateTracker = new WorkflowStateTracker();
41455
42649
  if (dashboard) {
41456
42650
  dashboard.setHandoffLog({
41457
42651
  handoffLog,
41458
42652
  eventBridge: handoffEventBridge,
41459
42653
  auditLog,
41460
- operatorId: aggregatorIdentityId
42654
+ operatorId: aggregatorIdentityId,
42655
+ workflowStateTracker
41461
42656
  });
41462
42657
  }
41463
42658
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);