@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.js CHANGED
@@ -16700,13 +16700,418 @@ function crossHarnessSummary(details, sender) {
16700
16700
  return `${sender} -> operator approval`;
16701
16701
  }
16702
16702
  var COORDINATION_VIEW_AUDIT_OPS = {
16703
+ /** v1.3 Omega-1: operator opened the chronological handoff list. */
16703
16704
  VIEW_OPENED: "operator_coordination_view_opened",
16704
- ENTRY_DRILLED: "operator_handoff_entry_drilled"
16705
+ /** v1.3 Omega-1: operator drilled into a single handoff for detail. */
16706
+ ENTRY_DRILLED: "operator_handoff_entry_drilled",
16707
+ /**
16708
+ * v1.3 Omega-3: operator opened the Workflows sibling-view (list of
16709
+ * multi-handoff workflows grouped by `workflow-grouper`). Mirrors
16710
+ * VIEW_OPENED's shape so the dashboard activity feed can group both
16711
+ * as "operator coordination surfaces."
16712
+ */
16713
+ WORKFLOW_VIEW_OPENED: "operator_workflow_view_opened",
16714
+ /**
16715
+ * v1.3 Omega-3: operator drilled into a single workflow for its
16716
+ * timeline + member-handoffs detail. Mirrors ENTRY_DRILLED's shape.
16717
+ */
16718
+ WORKFLOW_DRILLED: "operator_workflow_drilled",
16719
+ /**
16720
+ * v1.3 Omega-3: server-side state transition observed on a
16721
+ * workflow (e.g., in_progress -> stalled). Emitted by the route
16722
+ * layer after the state tracker diffs against its prior snapshot.
16723
+ * Distinct from the operator-action events above: this records what
16724
+ * the workflow itself is doing, not what the operator clicked.
16725
+ */
16726
+ WORKFLOW_STATE_CHANGED: "coordination_workflow_state_changed"
16705
16727
  };
16706
16728
 
16729
+ // src/coordination/context-transfer-extractor.ts
16730
+ var SUMMARY_MAX_CHARS = 240;
16731
+ var CATEGORY_VALUES = [
16732
+ "memory",
16733
+ "credentials",
16734
+ "plans",
16735
+ "outputs",
16736
+ "audit-refs",
16737
+ "other"
16738
+ ];
16739
+ async function extractContextTransferBreakdown(detail, deps = {}) {
16740
+ const pathA = tryStructuredPath(detail);
16741
+ if (pathA) return pathA;
16742
+ const pathB = tryCompositionPath(detail);
16743
+ if (pathB) return pathB;
16744
+ const pathC = tryHeuristicPath(detail);
16745
+ if (pathC.confidence >= 0.5 || !deps.substrateSelector) {
16746
+ return pathC;
16747
+ }
16748
+ const assist = await tryLlmAssistPath(detail, deps.substrateSelector);
16749
+ return assist ?? pathC;
16750
+ }
16751
+ function tryStructuredPath(detail) {
16752
+ const details = sourceDetails(detail.source_audit_entry);
16753
+ if (!details) return null;
16754
+ const transferredRaw = details["transferred"];
16755
+ const withheldRaw = details["withheld"];
16756
+ if (transferredRaw === void 0 && withheldRaw === void 0) return null;
16757
+ const transferred = parseExplicitContextItems(transferredRaw);
16758
+ const withheld = parseExplicitContextItems(withheldRaw);
16759
+ return {
16760
+ handoff_entry_id: detail.entry.entry_id,
16761
+ transferred,
16762
+ withheld,
16763
+ source: "structured",
16764
+ confidence: 1
16765
+ };
16766
+ }
16767
+ function tryCompositionPath(detail) {
16768
+ const op = detail.source_audit_entry.operation;
16769
+ if (!op.startsWith("composition_completed")) return null;
16770
+ const details = sourceDetails(detail.source_audit_entry);
16771
+ if (!details) return null;
16772
+ const receiptRaw = details["receipt"];
16773
+ const sourceStateRaw = details["source_state_snapshot"];
16774
+ if (receiptRaw === void 0) return null;
16775
+ const transferred = parseExplicitContextItems(receiptRaw);
16776
+ const withheld = [];
16777
+ if (Array.isArray(sourceStateRaw)) {
16778
+ const transferredKeys = new Set(
16779
+ transferred.map((t) => `${t.category}:${t.summary}`)
16780
+ );
16781
+ for (const item of parseExplicitContextItems(sourceStateRaw)) {
16782
+ const key = `${item.category}:${item.summary}`;
16783
+ if (!transferredKeys.has(key)) withheld.push(item);
16784
+ }
16785
+ }
16786
+ return {
16787
+ handoff_entry_id: detail.entry.entry_id,
16788
+ transferred,
16789
+ withheld,
16790
+ source: "composition",
16791
+ confidence: 0.9
16792
+ };
16793
+ }
16794
+ function tryHeuristicPath(detail) {
16795
+ const entry = detail.entry;
16796
+ const audit = detail.source_audit_entry;
16797
+ const details = sourceDetails(audit);
16798
+ if (audit.operation === "cross_harness_approval_aggregated") {
16799
+ const ruleId = optString2(details, "policy_rule_id");
16800
+ if (ruleId) {
16801
+ const category = categoryFromPolicyRuleId(ruleId);
16802
+ const summary = `${ruleId} (${entry.source_agent_id} -> operator)`;
16803
+ return {
16804
+ handoff_entry_id: entry.entry_id,
16805
+ transferred: [
16806
+ {
16807
+ category,
16808
+ summary: truncate(summary, SUMMARY_MAX_CHARS),
16809
+ size_hint: "minimal"
16810
+ }
16811
+ ],
16812
+ withheld: [],
16813
+ source: "heuristic",
16814
+ confidence: 0.5
16815
+ };
16816
+ }
16817
+ }
16818
+ if (audit.operation === "v1.1_local_handoff") {
16819
+ const reasonClass = optString2(details, "reason_class");
16820
+ const newStatus = optString2(details, "new_status");
16821
+ const previousStatus = optString2(details, "previous_status");
16822
+ const transferred = [];
16823
+ const withheld = [];
16824
+ if (newStatus === "denied" || newStatus === "failed") {
16825
+ withheld.push({
16826
+ category: "other",
16827
+ summary: truncate(
16828
+ `handoff ${newStatus}${reasonClass ? ` (${reasonClass})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16829
+ SUMMARY_MAX_CHARS
16830
+ ),
16831
+ size_hint: "minimal"
16832
+ });
16833
+ } else if (newStatus === "accepted" || newStatus === "completed") {
16834
+ transferred.push({
16835
+ category: "other",
16836
+ summary: truncate(
16837
+ `handoff ${newStatus}${previousStatus ? ` (from ${previousStatus})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16838
+ SUMMARY_MAX_CHARS
16839
+ ),
16840
+ size_hint: "small"
16841
+ });
16842
+ } else {
16843
+ transferred.push({
16844
+ category: "other",
16845
+ summary: truncate(
16846
+ `handoff ${entry.source_agent_id} -> ${entry.target_agent_id}${newStatus ? ` (${newStatus})` : ""}`,
16847
+ SUMMARY_MAX_CHARS
16848
+ ),
16849
+ size_hint: "minimal"
16850
+ });
16851
+ }
16852
+ return {
16853
+ handoff_entry_id: entry.entry_id,
16854
+ transferred,
16855
+ withheld,
16856
+ source: "heuristic",
16857
+ confidence: reasonClass || newStatus ? 0.5 : 0.3
16858
+ };
16859
+ }
16860
+ return {
16861
+ handoff_entry_id: entry.entry_id,
16862
+ transferred: [
16863
+ {
16864
+ category: "other",
16865
+ summary: truncate(
16866
+ `handoff ${entry.source_agent_id} -> ${entry.target_agent_id}`,
16867
+ SUMMARY_MAX_CHARS
16868
+ ),
16869
+ size_hint: "minimal"
16870
+ }
16871
+ ],
16872
+ withheld: [],
16873
+ source: "heuristic",
16874
+ confidence: 0.3
16875
+ };
16876
+ }
16877
+ async function tryLlmAssistPath(detail, selector) {
16878
+ const entry = detail.entry;
16879
+ const audit = detail.source_audit_entry;
16880
+ const probe = `event=${audit.operation} sender=${entry.source_agent_id} target=${entry.target_agent_id} summary=${entry.context_transfer_summary}`;
16881
+ try {
16882
+ const response = await selector.invokeClassify("sentinel-scoring", {
16883
+ kind: "classify",
16884
+ items: [probe],
16885
+ categories: [...CATEGORY_VALUES]
16886
+ });
16887
+ if (response.body.kind !== "classify") return null;
16888
+ const top = response.body.results[0];
16889
+ if (!top || !isCategory(top.category) || top.confidence < 0.4) {
16890
+ return null;
16891
+ }
16892
+ return {
16893
+ handoff_entry_id: entry.entry_id,
16894
+ transferred: [
16895
+ {
16896
+ category: top.category,
16897
+ summary: truncate(
16898
+ `LLM-classified handoff ${entry.source_agent_id} -> ${entry.target_agent_id}: ${top.category} (confidence ${top.confidence.toFixed(2)})`,
16899
+ SUMMARY_MAX_CHARS
16900
+ ),
16901
+ size_hint: "minimal"
16902
+ }
16903
+ ],
16904
+ withheld: [],
16905
+ source: "llm-assist",
16906
+ confidence: 0.6
16907
+ };
16908
+ } catch {
16909
+ return null;
16910
+ }
16911
+ }
16912
+ function sourceDetails(audit) {
16913
+ return audit.details;
16914
+ }
16915
+ function optString2(details, key) {
16916
+ if (!details) return null;
16917
+ const value = details[key];
16918
+ if (typeof value !== "string" || value.length === 0) return null;
16919
+ return value;
16920
+ }
16921
+ function isCategory(value) {
16922
+ return CATEGORY_VALUES.includes(value);
16923
+ }
16924
+ function truncate(s, cap) {
16925
+ return s.length <= cap ? s : `${s.slice(0, cap - 3)}...`;
16926
+ }
16927
+ function parseExplicitContextItems(raw) {
16928
+ if (raw === null || raw === void 0) return [];
16929
+ if (Array.isArray(raw)) {
16930
+ const out = [];
16931
+ for (const entry of raw) {
16932
+ if (typeof entry === "string") {
16933
+ out.push({
16934
+ category: "other",
16935
+ summary: truncate(entry, SUMMARY_MAX_CHARS),
16936
+ size_hint: "minimal"
16937
+ });
16938
+ continue;
16939
+ }
16940
+ if (entry && typeof entry === "object") {
16941
+ const obj = entry;
16942
+ const category = isCategoryValue(obj["category"]) ? obj["category"] : "other";
16943
+ const summary = typeof obj["summary"] === "string" ? truncate(obj["summary"], SUMMARY_MAX_CHARS) : "(unspecified)";
16944
+ const sizeHint = isSizeHintValue(obj["size_hint"]) ? obj["size_hint"] : "minimal";
16945
+ out.push({ category, summary, size_hint: sizeHint });
16946
+ }
16947
+ }
16948
+ return out;
16949
+ }
16950
+ if (typeof raw === "object" && raw !== null) {
16951
+ const out = [];
16952
+ for (const [k, v] of Object.entries(raw)) {
16953
+ const category = isCategoryValue(k) ? k : "other";
16954
+ if (Array.isArray(v)) {
16955
+ for (const item of v) {
16956
+ if (typeof item === "string") {
16957
+ out.push({
16958
+ category,
16959
+ summary: truncate(item, SUMMARY_MAX_CHARS),
16960
+ size_hint: "minimal"
16961
+ });
16962
+ }
16963
+ }
16964
+ }
16965
+ }
16966
+ return out;
16967
+ }
16968
+ return [];
16969
+ }
16970
+ function isCategoryValue(v) {
16971
+ return typeof v === "string" && CATEGORY_VALUES.includes(v);
16972
+ }
16973
+ function isSizeHintValue(v) {
16974
+ return typeof v === "string" && (v === "minimal" || v === "small" || v === "medium" || v === "large");
16975
+ }
16976
+ function categoryFromPolicyRuleId(ruleId) {
16977
+ const lower = ruleId.toLowerCase();
16978
+ if (lower.includes("credential") || lower.includes("broker_secret")) {
16979
+ return "credentials";
16980
+ }
16981
+ if (lower.includes("memory") || lower.includes("state_read")) {
16982
+ return "memory";
16983
+ }
16984
+ if (lower.includes("plan")) {
16985
+ return "plans";
16986
+ }
16987
+ if (lower.includes("export") || lower.includes("output")) {
16988
+ return "outputs";
16989
+ }
16990
+ if (lower.includes("audit")) {
16991
+ return "audit-refs";
16992
+ }
16993
+ return "other";
16994
+ }
16995
+ var CONTEXT_TRANSFER_AUDIT_OPS = {
16996
+ DECODED: "operator_handoff_context_transfer_decoded"
16997
+ };
16998
+ var HEURISTIC_WINDOW_MS = 5 * 60 * 1e3;
16999
+ var STALL_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
17000
+ var CYCLE_COMPLETION_MIN_HOPS = 2;
17001
+ function groupHandoffsIntoWorkflows(handoffs, opts) {
17002
+ if (handoffs.length === 0) return [];
17003
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
17004
+ const linkedGroups = /* @__PURE__ */ new Map();
17005
+ const unlinked = [];
17006
+ for (const h of handoffs) {
17007
+ if (h.workflow_link !== null && h.workflow_link.length > 0) {
17008
+ let bucket = linkedGroups.get(h.workflow_link);
17009
+ if (!bucket) {
17010
+ bucket = [];
17011
+ linkedGroups.set(h.workflow_link, bucket);
17012
+ }
17013
+ bucket.push(h);
17014
+ } else {
17015
+ unlinked.push(h);
17016
+ }
17017
+ }
17018
+ const sortedUnlinked = [...unlinked].sort(
17019
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17020
+ );
17021
+ const heuristicChains = [];
17022
+ for (const h of sortedUnlinked) {
17023
+ const joinedIdx = findExtendableChain(heuristicChains, h);
17024
+ if (joinedIdx !== null) {
17025
+ heuristicChains[joinedIdx].push(h);
17026
+ } else {
17027
+ heuristicChains.push([h]);
17028
+ }
17029
+ }
17030
+ const workflows = [];
17031
+ for (const members of linkedGroups.values()) {
17032
+ workflows.push(materialize(members, now));
17033
+ }
17034
+ for (const members of heuristicChains) {
17035
+ workflows.push(materialize(members, now));
17036
+ }
17037
+ workflows.sort(
17038
+ (a, b) => a.last_activity_at < b.last_activity_at ? 1 : -1
17039
+ );
17040
+ return workflows;
17041
+ }
17042
+ function determineWorkflowState(members, now) {
17043
+ if (members.length === 0) return "unknown";
17044
+ const sorted = [...members].sort(
17045
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17046
+ );
17047
+ const last = sorted[sorted.length - 1];
17048
+ const root = sorted[0];
17049
+ const lastMs = Date.parse(last.observed_at);
17050
+ if (!Number.isFinite(lastMs)) return "unknown";
17051
+ if (last.target_agent_id === OPERATOR_PSEUDO_AGENT) {
17052
+ return "completed";
17053
+ }
17054
+ if (sorted.length > CYCLE_COMPLETION_MIN_HOPS && last.target_agent_id === root.source_agent_id) {
17055
+ return "completed";
17056
+ }
17057
+ const ageMs = now.getTime() - lastMs;
17058
+ if (ageMs > STALL_THRESHOLD_MS) {
17059
+ return "stalled";
17060
+ }
17061
+ return "in_progress";
17062
+ }
17063
+ function workflowIdFromRoot(rootEntryId) {
17064
+ return createHash("sha256").update(`workflow:${rootEntryId}`).digest("hex").slice(0, 32);
17065
+ }
17066
+ function findExtendableChain(chains, h) {
17067
+ const hMs = Date.parse(h.observed_at);
17068
+ if (!Number.isFinite(hMs)) return null;
17069
+ let bestIdx = null;
17070
+ let bestGapMs = Number.POSITIVE_INFINITY;
17071
+ for (let i = 0; i < chains.length; i += 1) {
17072
+ const chain = chains[i];
17073
+ const last = chain[chain.length - 1];
17074
+ const lastMs = Date.parse(last.observed_at);
17075
+ if (!Number.isFinite(lastMs)) continue;
17076
+ const gapMs = Math.abs(hMs - lastMs);
17077
+ if (gapMs > HEURISTIC_WINDOW_MS) continue;
17078
+ if (!sharesAgent(last, h)) continue;
17079
+ if (gapMs < bestGapMs) {
17080
+ bestGapMs = gapMs;
17081
+ bestIdx = i;
17082
+ }
17083
+ }
17084
+ return bestIdx;
17085
+ }
17086
+ function sharesAgent(a, b) {
17087
+ 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;
17088
+ }
17089
+ function materialize(members, now) {
17090
+ const sorted = [...members].sort(
17091
+ (a, b) => a.observed_at < b.observed_at ? -1 : 1
17092
+ );
17093
+ const root = sorted[0];
17094
+ const last = sorted[sorted.length - 1];
17095
+ const involved = /* @__PURE__ */ new Set();
17096
+ for (const h of sorted) {
17097
+ if (h.source_agent_id) involved.add(h.source_agent_id);
17098
+ if (h.target_agent_id) involved.add(h.target_agent_id);
17099
+ }
17100
+ return {
17101
+ workflow_id: workflowIdFromRoot(root.entry_id),
17102
+ root_handoff: root,
17103
+ member_handoffs: sorted,
17104
+ state: determineWorkflowState(sorted, now),
17105
+ started_at: root.observed_at,
17106
+ last_activity_at: last.observed_at,
17107
+ involved_agents: [...involved].sort()
17108
+ };
17109
+ }
17110
+
16707
17111
  // src/coordination/handoff-routes.ts
16708
17112
  var COORDINATION_API_PREFIX = "/api/coordination";
16709
17113
  var COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
17114
+ var COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
16710
17115
  var COORDINATION_LIST_DEFAULT_LIMIT = 50;
16711
17116
  var COORDINATION_LIST_MAX_LIMIT = 500;
16712
17117
  var HandoffEventBridge = class {
@@ -16745,6 +17150,101 @@ function matchEntryRoute2(path) {
16745
17150
  if (rest.includes("/")) return null;
16746
17151
  return { entryId: decodeURIComponent(rest) };
16747
17152
  }
17153
+ function matchWorkflowRoute(path) {
17154
+ const prefix = `${COORDINATION_WORKFLOWS_PREFIX}/`;
17155
+ if (!path.startsWith(prefix)) return null;
17156
+ const rest = path.slice(prefix.length);
17157
+ if (rest.length === 0 || rest === "stream") return null;
17158
+ if (rest.includes("/")) return null;
17159
+ return { workflowId: decodeURIComponent(rest) };
17160
+ }
17161
+ async function computeWorkflowsAndTrackTransitions(deps) {
17162
+ const handoffs = await deps.handoffLog.query({ limit: 500 });
17163
+ const workflows = groupHandoffsIntoWorkflows(handoffs, {
17164
+ ...deps.now !== void 0 ? { now: deps.now() } : {}
17165
+ });
17166
+ const transitions = deps.workflowStateTracker ? deps.workflowStateTracker.observe(workflows) : [];
17167
+ for (const change of transitions) {
17168
+ deps.auditLog.append(
17169
+ "l2",
17170
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_STATE_CHANGED,
17171
+ deps.operatorId,
17172
+ {
17173
+ fortress_id: deps.handoffLog.getFortressId(),
17174
+ workflow_id: change.workflow_id,
17175
+ previous_state: change.previous_state,
17176
+ new_state: change.new_state
17177
+ }
17178
+ );
17179
+ }
17180
+ return { workflows, transitions };
17181
+ }
17182
+ function filterWorkflowList(workflows, opts) {
17183
+ let filtered = workflows;
17184
+ if (opts.state) {
17185
+ filtered = filtered.filter((w) => w.state === opts.state);
17186
+ }
17187
+ if (opts.agentId) {
17188
+ filtered = filtered.filter((w) => w.involved_agents.includes(opts.agentId));
17189
+ }
17190
+ if (opts.since) {
17191
+ filtered = filtered.filter((w) => w.last_activity_at >= opts.since);
17192
+ }
17193
+ return filtered.slice(0, opts.limit);
17194
+ }
17195
+ function isWorkflowState(value) {
17196
+ return value === "in_progress" || value === "completed" || value === "stalled" || value === "unknown";
17197
+ }
17198
+ async function handleWorkflowStream(deps, res) {
17199
+ res.writeHead(200, {
17200
+ "Content-Type": "text/event-stream",
17201
+ "Cache-Control": "no-cache, no-transform",
17202
+ Connection: "keep-alive",
17203
+ "X-Accel-Buffering": "no"
17204
+ });
17205
+ const initial = await computeWorkflowsAndTrackTransitions(deps);
17206
+ res.write(
17207
+ `event: workflow_snapshot
17208
+ data: ${JSON.stringify({ workflows: initial.workflows })}
17209
+
17210
+ `
17211
+ );
17212
+ if (initial.transitions.length > 0) {
17213
+ res.write(
17214
+ `event: workflow_state_changed
17215
+ data: ${JSON.stringify({ transitions: initial.transitions })}
17216
+
17217
+ `
17218
+ );
17219
+ }
17220
+ const unsubscribe = deps.events.subscribe(() => {
17221
+ void (async () => {
17222
+ try {
17223
+ const tick = await computeWorkflowsAndTrackTransitions(deps);
17224
+ res.write(
17225
+ `event: workflow_snapshot
17226
+ data: ${JSON.stringify({ workflows: tick.workflows })}
17227
+
17228
+ `
17229
+ );
17230
+ if (tick.transitions.length > 0) {
17231
+ res.write(
17232
+ `event: workflow_state_changed
17233
+ data: ${JSON.stringify({ transitions: tick.transitions })}
17234
+
17235
+ `
17236
+ );
17237
+ }
17238
+ } catch {
17239
+ }
17240
+ })();
17241
+ });
17242
+ const cleanup = () => {
17243
+ unsubscribe();
17244
+ };
17245
+ res.on("close", cleanup);
17246
+ res.on("error", cleanup);
17247
+ }
16748
17248
  async function handleStream3(deps, res) {
16749
17249
  res.writeHead(200, {
16750
17250
  "Content-Type": "text/event-stream",
@@ -16828,6 +17328,67 @@ async function handleCoordinationRoute(deps, req, res) {
16828
17328
  writeJSON6(res, 200, { ok: true, data: { entries } });
16829
17329
  return true;
16830
17330
  }
17331
+ if (method === "GET" && path === `${COORDINATION_WORKFLOWS_PREFIX}/stream`) {
17332
+ await handleWorkflowStream(deps, res);
17333
+ return true;
17334
+ }
17335
+ if (method === "GET" && path === COORDINATION_WORKFLOWS_PREFIX) {
17336
+ const limit = parseLimit4(
17337
+ url.searchParams.get("limit"),
17338
+ COORDINATION_LIST_DEFAULT_LIMIT,
17339
+ COORDINATION_LIST_MAX_LIMIT
17340
+ );
17341
+ const rawState = url.searchParams.get("state");
17342
+ const state = rawState && isWorkflowState(rawState) ? rawState : void 0;
17343
+ const since = url.searchParams.get("since") ?? void 0;
17344
+ const agentId = url.searchParams.get("agent_id") ?? void 0;
17345
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
17346
+ const filtered = filterWorkflowList(computed.workflows, {
17347
+ ...state !== void 0 ? { state } : {},
17348
+ ...agentId !== void 0 ? { agentId } : {},
17349
+ ...since !== void 0 ? { since } : {},
17350
+ limit
17351
+ });
17352
+ deps.auditLog.append(
17353
+ "l2",
17354
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_VIEW_OPENED,
17355
+ deps.operatorId,
17356
+ {
17357
+ fortress_id: deps.handoffLog.getFortressId(),
17358
+ result_count: filtered.length,
17359
+ ...state !== void 0 ? { state } : {},
17360
+ ...agentId !== void 0 ? { agent_id: agentId } : {},
17361
+ ...since !== void 0 ? { since } : {}
17362
+ }
17363
+ );
17364
+ writeJSON6(res, 200, { ok: true, data: { workflows: filtered } });
17365
+ return true;
17366
+ }
17367
+ const workflowMatch = matchWorkflowRoute(path);
17368
+ if (method === "GET" && workflowMatch) {
17369
+ const computed = await computeWorkflowsAndTrackTransitions(deps);
17370
+ const wf = computed.workflows.find(
17371
+ (w) => w.workflow_id === workflowMatch.workflowId
17372
+ );
17373
+ if (!wf) {
17374
+ writeJSON6(res, 404, { ok: false, error: "not_found" });
17375
+ return true;
17376
+ }
17377
+ deps.auditLog.append(
17378
+ "l2",
17379
+ COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_DRILLED,
17380
+ deps.operatorId,
17381
+ {
17382
+ fortress_id: deps.handoffLog.getFortressId(),
17383
+ workflow_id: wf.workflow_id,
17384
+ state: wf.state,
17385
+ member_count: wf.member_handoffs.length,
17386
+ involved_agent_count: wf.involved_agents.length
17387
+ }
17388
+ );
17389
+ writeJSON6(res, 200, { ok: true, data: { workflow: wf } });
17390
+ return true;
17391
+ }
16831
17392
  const entryMatch = matchEntryRoute2(path);
16832
17393
  if (method === "GET" && entryMatch) {
16833
17394
  const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
@@ -16847,7 +17408,29 @@ async function handleCoordinationRoute(deps, req, res) {
16847
17408
  target_agent_id: detail.entry.target_agent_id
16848
17409
  }
16849
17410
  );
16850
- writeJSON6(res, 200, { ok: true, data: detail });
17411
+ let breakdown = null;
17412
+ try {
17413
+ breakdown = await extractContextTransferBreakdown(
17414
+ detail,
17415
+ deps.contextTransfer ?? {}
17416
+ );
17417
+ deps.auditLog.append(
17418
+ "l2",
17419
+ CONTEXT_TRANSFER_AUDIT_OPS.DECODED,
17420
+ deps.operatorId,
17421
+ {
17422
+ fortress_id: deps.handoffLog.getFortressId(),
17423
+ entry_id: detail.entry.entry_id,
17424
+ extractor_path: breakdown.source,
17425
+ confidence: breakdown.confidence,
17426
+ transferred_count: breakdown.transferred.length,
17427
+ withheld_count: breakdown.withheld.length
17428
+ }
17429
+ );
17430
+ } catch {
17431
+ }
17432
+ const responseData = breakdown !== null ? { ...detail, context_transfer_breakdown: breakdown } : detail;
17433
+ writeJSON6(res, 200, { ok: true, data: responseData });
16851
17434
  return true;
16852
17435
  }
16853
17436
  writeJSON6(res, 404, { ok: false, error: "not_found", path });
@@ -16947,6 +17530,8 @@ var DashboardApprovalChannel = class {
16947
17530
  */
16948
17531
  handoffLog = null;
16949
17532
  handoffEventBridge = null;
17533
+ handoffContextTransfer = null;
17534
+ workflowStateTracker = null;
16950
17535
  handoffAuditLog = null;
16951
17536
  handoffOperatorId = null;
16952
17537
  constructor(config) {
@@ -17027,6 +17612,8 @@ var DashboardApprovalChannel = class {
17027
17612
  this.handoffEventBridge = opts.eventBridge ?? null;
17028
17613
  this.handoffAuditLog = opts.auditLog ?? null;
17029
17614
  this.handoffOperatorId = opts.operatorId ?? null;
17615
+ this.handoffContextTransfer = opts.contextTransfer ?? null;
17616
+ this.workflowStateTracker = opts.workflowStateTracker ?? null;
17030
17617
  }
17031
17618
  /**
17032
17619
  * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
@@ -17084,7 +17671,9 @@ var DashboardApprovalChannel = class {
17084
17671
  handoffLog: this.handoffLog,
17085
17672
  auditLog: this.handoffAuditLog,
17086
17673
  operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
17087
- events: this.handoffEventBridge
17674
+ events: this.handoffEventBridge,
17675
+ ...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
17676
+ ...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
17088
17677
  },
17089
17678
  req,
17090
17679
  res
@@ -21304,13 +21893,33 @@ var SentinelDispatcher = class {
21304
21893
  }
21305
21894
  }
21306
21895
  };
21896
+
21897
+ // src/anomaly-detection/classifier-state-store.ts
21898
+ init_encryption();
21899
+ init_encoding();
21900
+
21901
+ // src/anomaly-detection/classifiers/cusum.ts
21902
+ var CUSUM_CLASSIFIER_ID = "cusum";
21903
+
21904
+ // src/anomaly-detection/classifiers/psi.ts
21905
+ var PSI_CLASSIFIER_ID = "psi";
21906
+
21907
+ // src/anomaly-detection/anomaly-pipeline.ts
21307
21908
  var ANOMALY_AUDIT_OPS = {
21308
21909
  DETECTOR_REGISTERED: "anomaly_detector_registered",
21309
21910
  DETECTOR_UNREGISTERED: "anomaly_detector_unregistered",
21310
21911
  FINDING_EMITTED: "anomaly_finding_emitted",
21311
21912
  EVALUATION_FAILED: "anomaly_evaluation_failed",
21312
21913
  TRAINING_COMPLETED: "anomaly_training_completed",
21313
- TRAINING_FAILED: "anomaly_training_failed"
21914
+ TRAINING_FAILED: "anomaly_training_failed",
21915
+ /** Chi-2: a classifier was attached to an existing detector. */
21916
+ CLASSIFIER_SUBSCRIBED: "anomaly_classifier_subscribed",
21917
+ /** Chi-2: a classifier was detached from an existing detector. */
21918
+ CLASSIFIER_UNSUBSCRIBED: "anomaly_classifier_unsubscribed",
21919
+ /** Chi-2: CUSUM-flagged mean-shift drift on a per-agent feature. */
21920
+ CUSUM_DRIFT_DETECTED: "anomaly_cusum_drift_detected",
21921
+ /** Chi-2: PSI-flagged distribution shift on a per-agent feature. */
21922
+ PSI_DISTRIBUTION_SHIFT_DETECTED: "anomaly_psi_distribution_shift_detected"
21314
21923
  };
21315
21924
  var DEFAULT_TICK_INTERVAL_MS2 = 6e4;
21316
21925
  var AnomalyPipelineDispatcher = class {
@@ -21401,34 +22010,37 @@ var AnomalyPipelineDispatcher = class {
21401
22010
  const stamped = await this.routeFinding(detectorId, raw);
21402
22011
  findings.push(stamped);
21403
22012
  }
21404
- try {
21405
- const trainingResult = await detector.classifier.train();
21406
- this.auditLog.append(
21407
- "l2",
21408
- ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
21409
- this.identityId,
21410
- {
21411
- detector_id: detectorId,
21412
- classifier_id: detector.classifier.classifierId,
21413
- trained_at: trainingResult.trained_at,
21414
- sample_count: trainingResult.sample_count,
21415
- agent_count: trainingResult.agent_count,
21416
- fortress_id: this.fortressId
21417
- }
21418
- );
21419
- } catch (trainErr) {
21420
- const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
21421
- this.auditLog.append(
21422
- "l2",
21423
- ANOMALY_AUDIT_OPS.TRAINING_FAILED,
21424
- this.identityId,
21425
- {
21426
- detector_id: detectorId,
21427
- error_message: message,
21428
- fortress_id: this.fortressId
21429
- },
21430
- "failure"
21431
- );
22013
+ for (const classifier of detector.getAllClassifiers()) {
22014
+ try {
22015
+ const trainingResult = await classifier.train();
22016
+ this.auditLog.append(
22017
+ "l2",
22018
+ ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
22019
+ this.identityId,
22020
+ {
22021
+ detector_id: detectorId,
22022
+ classifier_id: classifier.classifierId,
22023
+ trained_at: trainingResult.trained_at,
22024
+ sample_count: trainingResult.sample_count,
22025
+ agent_count: trainingResult.agent_count,
22026
+ fortress_id: this.fortressId
22027
+ }
22028
+ );
22029
+ } catch (trainErr) {
22030
+ const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
22031
+ this.auditLog.append(
22032
+ "l2",
22033
+ ANOMALY_AUDIT_OPS.TRAINING_FAILED,
22034
+ this.identityId,
22035
+ {
22036
+ detector_id: detectorId,
22037
+ classifier_id: classifier.classifierId,
22038
+ error_message: message,
22039
+ fortress_id: this.fortressId
22040
+ },
22041
+ "failure"
22042
+ );
22043
+ }
21432
22044
  }
21433
22045
  } catch (err) {
21434
22046
  const message = err instanceof Error ? err.message : String(err);
@@ -21491,6 +22103,7 @@ var AnomalyPipelineDispatcher = class {
21491
22103
  observed_at: raw.observed_at || this.now().toISOString()
21492
22104
  };
21493
22105
  await this.findingStore.saveFinding(stamped);
22106
+ const classifierId = stamped.details["classifier_id"] ?? null;
21494
22107
  this.auditLog.append(
21495
22108
  "l2",
21496
22109
  ANOMALY_AUDIT_OPS.FINDING_EMITTED,
@@ -21500,13 +22113,79 @@ var AnomalyPipelineDispatcher = class {
21500
22113
  finding_id: stamped.finding_id,
21501
22114
  severity: stamped.severity,
21502
22115
  anomaly_score: stamped.details["anomaly_score"] ?? null,
22116
+ ...classifierId !== null ? { classifier_id: classifierId } : {},
21503
22117
  ...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
21504
22118
  fortress_id: this.fortressId
21505
22119
  }
21506
22120
  );
22121
+ const specificOp = classifierSpecificAuditOp(classifierId);
22122
+ if (specificOp !== null) {
22123
+ this.auditLog.append("l2", specificOp, this.identityId, {
22124
+ detector_id: detectorId,
22125
+ finding_id: stamped.finding_id,
22126
+ severity: stamped.severity,
22127
+ anomaly_score: stamped.details["anomaly_score"] ?? null,
22128
+ ...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
22129
+ fortress_id: this.fortressId
22130
+ });
22131
+ }
21507
22132
  this.emit({ type: "finding", finding: stamped });
21508
22133
  return stamped;
21509
22134
  }
22135
+ /**
22136
+ * Chi-2: attach an additional classifier to an already-registered
22137
+ * detector. Emits ANOMALY_CLASSIFIER_SUBSCRIBED on success. The
22138
+ * factory is called with the fortress AnomalyContext so the
22139
+ * classifier can build its own state-store binding. Idempotent: a
22140
+ * second call with the same classifierId returns false.
22141
+ */
22142
+ async addClassifierToDetector(detectorId, factory) {
22143
+ const detector = this.detectors.get(detectorId);
22144
+ if (!detector) return false;
22145
+ const context = {
22146
+ fortressId: this.fortressId,
22147
+ auditLog: this.auditLog,
22148
+ storage: this.storage,
22149
+ masterKey: this.masterKey,
22150
+ now: this.now
22151
+ };
22152
+ const classifier = factory(context);
22153
+ const added = detector.addClassifier(classifier);
22154
+ if (!added) return false;
22155
+ this.auditLog.append(
22156
+ "l2",
22157
+ ANOMALY_AUDIT_OPS.CLASSIFIER_SUBSCRIBED,
22158
+ this.identityId,
22159
+ {
22160
+ detector_id: detectorId,
22161
+ classifier_id: classifier.classifierId,
22162
+ fortress_id: this.fortressId
22163
+ }
22164
+ );
22165
+ return true;
22166
+ }
22167
+ /**
22168
+ * Chi-2: detach an additional classifier from an already-registered
22169
+ * detector. Emits ANOMALY_CLASSIFIER_UNSUBSCRIBED on success. The
22170
+ * primary classifier cannot be detached (returns false).
22171
+ */
22172
+ async removeClassifierFromDetector(detectorId, classifierId) {
22173
+ const detector = this.detectors.get(detectorId);
22174
+ if (!detector) return false;
22175
+ const removed = detector.removeClassifier(classifierId);
22176
+ if (!removed) return false;
22177
+ this.auditLog.append(
22178
+ "l2",
22179
+ ANOMALY_AUDIT_OPS.CLASSIFIER_UNSUBSCRIBED,
22180
+ this.identityId,
22181
+ {
22182
+ detector_id: detectorId,
22183
+ classifier_id: classifierId,
22184
+ fortress_id: this.fortressId
22185
+ }
22186
+ );
22187
+ return true;
22188
+ }
21510
22189
  emit(event) {
21511
22190
  for (const listener of this.listeners) {
21512
22191
  try {
@@ -21516,6 +22195,80 @@ var AnomalyPipelineDispatcher = class {
21516
22195
  }
21517
22196
  }
21518
22197
  };
22198
+ function classifierSpecificAuditOp(classifierId) {
22199
+ if (classifierId === null) return null;
22200
+ if (classifierId === CUSUM_CLASSIFIER_ID) {
22201
+ return ANOMALY_AUDIT_OPS.CUSUM_DRIFT_DETECTED;
22202
+ }
22203
+ if (classifierId === PSI_CLASSIFIER_ID) {
22204
+ return ANOMALY_AUDIT_OPS.PSI_DISTRIBUTION_SHIFT_DETECTED;
22205
+ }
22206
+ return null;
22207
+ }
22208
+
22209
+ // src/coordination/workflow-state-tracker.ts
22210
+ var WorkflowStateTracker = class {
22211
+ states = /* @__PURE__ */ new Map();
22212
+ now;
22213
+ constructor(opts) {
22214
+ this.now = opts?.now ?? (() => /* @__PURE__ */ new Date());
22215
+ }
22216
+ /**
22217
+ * Diff the supplied workflow list against the last-observed states.
22218
+ * Returns the set of transitions detected this call; the tracker
22219
+ * mutates its internal map to reflect the new states.
22220
+ *
22221
+ * Transitions emitted:
22222
+ * - First observation of a workflow (`previous_state` is the
22223
+ * sentinel `unobserved`). Lets the route handler audit-emit
22224
+ * the initial state so the operator sees workflows as they
22225
+ * surface, not only when they change.
22226
+ * - Subsequent observation where `previous_state !== new_state`.
22227
+ */
22228
+ observe(workflows) {
22229
+ const out = [];
22230
+ const observedAt = this.now().toISOString();
22231
+ for (const wf of workflows) {
22232
+ const prior = this.states.get(wf.workflow_id);
22233
+ if (prior === void 0) {
22234
+ out.push({
22235
+ workflow_id: wf.workflow_id,
22236
+ previous_state: "unobserved",
22237
+ new_state: wf.state,
22238
+ observed_at: observedAt
22239
+ });
22240
+ this.states.set(wf.workflow_id, wf.state);
22241
+ continue;
22242
+ }
22243
+ if (prior !== wf.state) {
22244
+ out.push({
22245
+ workflow_id: wf.workflow_id,
22246
+ previous_state: prior,
22247
+ new_state: wf.state,
22248
+ observed_at: observedAt
22249
+ });
22250
+ this.states.set(wf.workflow_id, wf.state);
22251
+ }
22252
+ }
22253
+ return out;
22254
+ }
22255
+ /**
22256
+ * Drop a workflow's recorded state. Surfaced for tests + future
22257
+ * "operator dismissed this workflow" affordance; not currently
22258
+ * called by the production wiring.
22259
+ */
22260
+ forget(workflowId) {
22261
+ this.states.delete(workflowId);
22262
+ }
22263
+ /** Reset the tracker. Tests use this between runs. */
22264
+ reset() {
22265
+ this.states.clear();
22266
+ }
22267
+ /** Read-only view of the current snapshot. Useful for diagnostics. */
22268
+ snapshot() {
22269
+ return new Map(this.states);
22270
+ }
22271
+ };
21519
22272
 
21520
22273
  // src/sentinel/sentinel.ts
21521
22274
  var Sentinel = class {
@@ -24750,7 +25503,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24750
25503
  const now = (/* @__PURE__ */ new Date()).toISOString();
24751
25504
  const canonicalBytes = canonicalize2(outcome);
24752
25505
  const canonicalString = new TextDecoder().decode(canonicalBytes);
24753
- const sha25611 = createCommitment(canonicalString);
25506
+ const sha25612 = createCommitment(canonicalString);
24754
25507
  let pedersenData;
24755
25508
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
24756
25509
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -24762,7 +25515,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24762
25515
  const commitmentPayload = {
24763
25516
  bridge_commitment_id: commitmentId,
24764
25517
  session_id: outcome.session_id,
24765
- sha256_commitment: sha25611.commitment,
25518
+ sha256_commitment: sha25612.commitment,
24766
25519
  terms_hash: outcome.terms_hash,
24767
25520
  committer_did: identity.did,
24768
25521
  committed_at: now,
@@ -24773,8 +25526,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
24773
25526
  return {
24774
25527
  bridge_commitment_id: commitmentId,
24775
25528
  session_id: outcome.session_id,
24776
- sha256_commitment: sha25611.commitment,
24777
- blinding_factor: sha25611.blinding_factor,
25529
+ sha256_commitment: sha25612.commitment,
25530
+ blinding_factor: sha25612.blinding_factor,
24778
25531
  committer_did: identity.did,
24779
25532
  signature: toBase64url(signature),
24780
25533
  pedersen_commitment: pedersenData,
@@ -38195,6 +38948,136 @@ function tryParseClassification3(text) {
38195
38948
  }
38196
38949
  }
38197
38950
 
38951
+ // src/query-anonymity/header-strip.ts
38952
+ var QUERY_ANONYMITY_AUDIT_OPS = {
38953
+ HEADERS_STRIPPED: "query_anonymity_headers_stripped"
38954
+ };
38955
+ var CANONICAL_STRIP_LIST = [
38956
+ // Browser / runtime fingerprinting.
38957
+ { name: "user-agent", reason: "user-agent" },
38958
+ { name: "sec-ch-ua", reason: "fingerprintable-extension" },
38959
+ { name: "sec-ch-ua-mobile", reason: "fingerprintable-extension" },
38960
+ { name: "sec-ch-ua-platform", reason: "fingerprintable-extension" },
38961
+ { name: "sec-ch-ua-platform-version", reason: "fingerprintable-extension" },
38962
+ { name: "sec-ch-ua-arch", reason: "fingerprintable-extension" },
38963
+ { name: "sec-ch-ua-bitness", reason: "fingerprintable-extension" },
38964
+ { name: "sec-ch-ua-model", reason: "fingerprintable-extension" },
38965
+ { name: "sec-ch-ua-full-version-list", reason: "fingerprintable-extension" },
38966
+ // Locale fingerprint.
38967
+ { name: "accept-language", reason: "locale-fingerprint" },
38968
+ // Request-origin leak.
38969
+ { name: "referer", reason: "leaking-network-info" },
38970
+ { name: "referrer-policy", reason: "leaking-network-info" },
38971
+ { name: "origin", reason: "leaking-network-info" },
38972
+ // Forwarded-by / IP-derived network info.
38973
+ { name: "via", reason: "leaking-network-info" },
38974
+ { name: "forwarded", reason: "leaking-network-info" },
38975
+ { name: "x-forwarded-for", reason: "leaking-network-info" },
38976
+ { name: "x-real-ip", reason: "leaking-network-info" },
38977
+ { name: "x-client-ip", reason: "leaking-network-info" },
38978
+ // DNT / GPC are technically anti-tracking signals but they
38979
+ // themselves form a fingerprint (operators who set DNT=1 are a
38980
+ // smaller subset). Strip to keep the substrate ignorant of
38981
+ // operator preferences.
38982
+ { name: "dnt", reason: "unnecessary-metadata" },
38983
+ { name: "sec-gpc", reason: "unnecessary-metadata" }
38984
+ ];
38985
+ var REQUIRED_HEADERS = [
38986
+ "authorization",
38987
+ "content-type",
38988
+ "content-length",
38989
+ "host",
38990
+ "accept",
38991
+ "x-api-key",
38992
+ // Anthropic API auth
38993
+ "anthropic-version",
38994
+ // Anthropic API contract version
38995
+ "anthropic-beta",
38996
+ // optional Anthropic beta opt-in
38997
+ "openai-organization",
38998
+ // optional OpenAI org id
38999
+ "x-stainless-package-version",
39000
+ // allowed for Anthropic + OpenAI SDK contract compat
39001
+ "x-goog-api-key",
39002
+ // Google AI Studio
39003
+ "x-goog-user-project"
39004
+ // Google AI Studio
39005
+ ];
39006
+ var REQUIRED_HEADER_SET = new Set(
39007
+ REQUIRED_HEADERS.map((h) => h.toLowerCase())
39008
+ );
39009
+ var STRIP_REASON_BY_NAME = new Map(
39010
+ CANONICAL_STRIP_LIST.map((h) => [h.name.toLowerCase(), h.reason])
39011
+ );
39012
+ function stripHeaders(headers) {
39013
+ const stripped = {};
39014
+ const removed = [];
39015
+ for (const [name, value] of Object.entries(headers)) {
39016
+ const lower = name.toLowerCase();
39017
+ if (REQUIRED_HEADER_SET.has(lower)) {
39018
+ stripped[name] = value;
39019
+ continue;
39020
+ }
39021
+ const reason = STRIP_REASON_BY_NAME.get(lower);
39022
+ if (reason !== void 0) {
39023
+ removed.push({ name, reason });
39024
+ continue;
39025
+ }
39026
+ stripped[name] = value;
39027
+ }
39028
+ return { stripped, removed };
39029
+ }
39030
+ function defeatUndiciDefaultsInto(headers) {
39031
+ if (headers["user-agent"] === void 0 && headers["User-Agent"] === void 0) {
39032
+ headers["User-Agent"] = "";
39033
+ }
39034
+ if (headers["accept-language"] === void 0 && headers["Accept-Language"] === void 0) {
39035
+ headers["Accept-Language"] = "";
39036
+ }
39037
+ return headers;
39038
+ }
39039
+ function createAnonymizedFetch(baseFetch, onAudit) {
39040
+ const wrapped = async (input, init) => {
39041
+ const headers = normalizeHeadersInit(init?.headers);
39042
+ const result = stripHeaders(headers);
39043
+ defeatUndiciDefaultsInto(result.stripped);
39044
+ const preservedRequired = Object.keys(result.stripped).filter(
39045
+ (k) => REQUIRED_HEADER_SET.has(k.toLowerCase())
39046
+ );
39047
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
39048
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
39049
+ if (onAudit) {
39050
+ onAudit({
39051
+ url,
39052
+ method,
39053
+ stripped_count: result.removed.length,
39054
+ removed: result.removed,
39055
+ required_preserved: preservedRequired
39056
+ });
39057
+ }
39058
+ return baseFetch(input, { ...init, headers: result.stripped });
39059
+ };
39060
+ return wrapped;
39061
+ }
39062
+ function normalizeHeadersInit(raw) {
39063
+ if (raw === void 0) return {};
39064
+ if (typeof Headers !== "undefined" && raw instanceof Headers) {
39065
+ const out = {};
39066
+ raw.forEach((value, key) => {
39067
+ out[key] = value;
39068
+ });
39069
+ return out;
39070
+ }
39071
+ if (Array.isArray(raw)) {
39072
+ const out = {};
39073
+ for (const [k, v] of raw) {
39074
+ if (k !== void 0 && v !== void 0) out[k] = v;
39075
+ }
39076
+ return out;
39077
+ }
39078
+ return { ...raw };
39079
+ }
39080
+
38198
39081
  // src/intelligence/substrates/hybrid/per-surface-router.ts
38199
39082
  function resolveHybridChoice(rules, surface) {
38200
39083
  if (!rules) return null;
@@ -38255,7 +39138,21 @@ var SubstrateSelector = class {
38255
39138
  this.auditLog = cfg.auditLog;
38256
39139
  this.identityId = cfg.identityId;
38257
39140
  this.redactor = cfg.redactor ?? IDENTITY_REDACTOR;
38258
- this.fetchImpl = cfg.fetchImpl;
39141
+ const baseFetch = cfg.fetchImpl ?? globalThis.fetch;
39142
+ this.fetchImpl = createAnonymizedFetch(baseFetch, (event) => {
39143
+ this.auditLog.append(
39144
+ "l2",
39145
+ QUERY_ANONYMITY_AUDIT_OPS.HEADERS_STRIPPED,
39146
+ this.identityId,
39147
+ {
39148
+ url: event.url,
39149
+ method: event.method,
39150
+ stripped_count: event.stripped_count,
39151
+ removed: event.removed,
39152
+ required_preserved: event.required_preserved
39153
+ }
39154
+ );
39155
+ });
38259
39156
  this.config = buildDefaultConfig();
38260
39157
  }
38261
39158
  /**
@@ -39181,6 +40078,150 @@ var EXIT_BUNDLE_ARTIFACT_KINDS = [
39181
40078
  "placeholder_vault_metadata"
39182
40079
  ];
39183
40080
 
40081
+ // src/recognition/did-web.ts
40082
+ init_encoding();
40083
+ init_hashing();
40084
+ var DEFAULT_TIMEOUT_MS4 = 5e3;
40085
+ 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;
40086
+ async function resolveDidWeb(did, opts) {
40087
+ const parsed = parseDidWeb(did);
40088
+ const url = didToUrl(parsed);
40089
+ if (!opts.allowed_hosts.includes(parsed.authority_host)) {
40090
+ return {
40091
+ ok: false,
40092
+ failure: "host_not_allowed",
40093
+ message: `did-web: authority_host '${parsed.authority_host}' is not in the operator's allowed_hosts allowlist; resolution refused (no-outbound-by-default)`,
40094
+ url
40095
+ };
40096
+ }
40097
+ const timeoutMs = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
40098
+ const fetcher = opts.fetcher ?? defaultFetcher;
40099
+ const controller = new AbortController();
40100
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
40101
+ let response;
40102
+ try {
40103
+ response = await fetcher(url, { signal: controller.signal });
40104
+ } catch (err) {
40105
+ clearTimeout(timer);
40106
+ const message = err instanceof Error ? err.message : String(err);
40107
+ if (controller.signal.aborted) {
40108
+ return {
40109
+ ok: false,
40110
+ failure: "timeout",
40111
+ message: `did-web: resolution exceeded ${timeoutMs}ms`,
40112
+ url
40113
+ };
40114
+ }
40115
+ return {
40116
+ ok: false,
40117
+ failure: "fetch_failed",
40118
+ message: `did-web: fetch error: ${message}`,
40119
+ url
40120
+ };
40121
+ }
40122
+ clearTimeout(timer);
40123
+ if (response.status === 404) {
40124
+ return {
40125
+ ok: false,
40126
+ failure: "not_found",
40127
+ message: `did-web: 404 from authority host`,
40128
+ url
40129
+ };
40130
+ }
40131
+ if (!response.ok) {
40132
+ return {
40133
+ ok: false,
40134
+ failure: "fetch_failed",
40135
+ message: `did-web: authority host returned ${response.status}`,
40136
+ url
40137
+ };
40138
+ }
40139
+ let body;
40140
+ try {
40141
+ body = await response.json();
40142
+ } catch (err) {
40143
+ const message = err instanceof Error ? err.message : String(err);
40144
+ return {
40145
+ ok: false,
40146
+ failure: "invalid_json",
40147
+ message: `did-web: invalid JSON: ${message}`,
40148
+ url
40149
+ };
40150
+ }
40151
+ if (!isDidDocument(body, did)) {
40152
+ return {
40153
+ ok: false,
40154
+ failure: "invalid_json",
40155
+ message: `did-web: response body is not a valid DID Document for ${did}`,
40156
+ url
40157
+ };
40158
+ }
40159
+ if (opts.expected_public_key !== void 0) {
40160
+ const expectedX = toBase64url(opts.expected_public_key);
40161
+ const actualX = body.verificationMethod[0]?.publicKeyJwk.x;
40162
+ if (actualX !== expectedX) {
40163
+ return {
40164
+ ok: false,
40165
+ failure: "signature_mismatch",
40166
+ message: `did-web: verificationMethod public key does not match expected key`,
40167
+ url
40168
+ };
40169
+ }
40170
+ }
40171
+ return { ok: true, did_document: body, url };
40172
+ }
40173
+ function parseDidWeb(did) {
40174
+ if (!did.startsWith("did:web:")) {
40175
+ throw new Error(`did-web: '${did}' is not a did:web identifier`);
40176
+ }
40177
+ const rest = did.slice("did:web:".length);
40178
+ const segments = rest.split(":");
40179
+ const authorityHost = segments[0];
40180
+ if (!HOST_RE.test(authorityHost)) {
40181
+ throw new Error(`did-web: '${authorityHost}' is not a valid DNS host`);
40182
+ }
40183
+ const parsed = { authority_host: authorityHost };
40184
+ if (segments.length === 1) return parsed;
40185
+ if (segments.length === 5 && segments[1] === "fortress" && segments[3] === "agent") {
40186
+ parsed.fortress_id = segments[2];
40187
+ parsed.agent_label = segments[4];
40188
+ return parsed;
40189
+ }
40190
+ throw new Error(
40191
+ `did-web: '${did}' does not match the supported shapes (bare did:web:<host> or did:web:<host>:fortress:<fid>:agent:<alabel>)`
40192
+ );
40193
+ }
40194
+ function didToUrl(parsed) {
40195
+ if (parsed.fortress_id === void 0 || parsed.agent_label === void 0) {
40196
+ return `https://${parsed.authority_host}/.well-known/did.json`;
40197
+ }
40198
+ return `https://${parsed.authority_host}/fortress/${parsed.fortress_id}/agent/${parsed.agent_label}/did.json`;
40199
+ }
40200
+ function isDidDocument(value, expectedDid) {
40201
+ if (!value || typeof value !== "object") return false;
40202
+ const v = value;
40203
+ if (v["id"] !== expectedDid) return false;
40204
+ if (!Array.isArray(v["@context"])) return false;
40205
+ const vm = v["verificationMethod"];
40206
+ if (!Array.isArray(vm) || vm.length === 0) return false;
40207
+ const first = vm[0];
40208
+ if (!first || typeof first["id"] !== "string") return false;
40209
+ const jwk = first["publicKeyJwk"];
40210
+ if (!jwk || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519") return false;
40211
+ if (typeof jwk["x"] !== "string") return false;
40212
+ if (!Array.isArray(v["authentication"])) return false;
40213
+ if (!Array.isArray(v["assertionMethod"])) return false;
40214
+ return true;
40215
+ }
40216
+ async function defaultFetcher(url, init) {
40217
+ const response = await fetch(url, init);
40218
+ return {
40219
+ ok: response.ok,
40220
+ status: response.status,
40221
+ json: () => response.json()
40222
+ };
40223
+ }
40224
+
39184
40225
  // src/exit/bundle.ts
39185
40226
  init_hashing();
39186
40227
  init_encoding();
@@ -39605,6 +40646,11 @@ async function verifyExitBundle(bundleDir, options = {}) {
39605
40646
 
39606
40647
  // src/exit/bundle.ts
39607
40648
  var ARTIFACT_DIR = "artifacts";
40649
+ var EXIT_BUNDLE_DID_WEB_AUDIT_OPS = {
40650
+ EXPORT_INCLUDED: "exit_bundle_did_web_export_included",
40651
+ IMPORT_VERIFIED: "exit_bundle_did_web_import_verified",
40652
+ AUTHORITY_HOST: "exit_bundle_did_web_authority_host"
40653
+ };
39608
40654
  var EXIT_IMPORT_NAMESPACE = "_exit_imports";
39609
40655
  var EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
39610
40656
  var EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
@@ -39896,6 +40942,7 @@ async function exportExitBundle(opts) {
39896
40942
  "placeholder_vault_metadata"
39897
40943
  )
39898
40944
  );
40945
+ const didWebBinding = validateExportDidWeb(opts.didWeb);
39899
40946
  const body = {
39900
40947
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
39901
40948
  exported_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -39903,7 +40950,8 @@ async function exportExitBundle(opts) {
39903
40950
  identity_id: identity.identity_id,
39904
40951
  fortress_id: identity.did,
39905
40952
  fortress_master_pubkey: identity.public_key,
39906
- did: identity.did
40953
+ did: identity.did,
40954
+ ...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
39907
40955
  },
39908
40956
  source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
39909
40957
  artifacts,
@@ -39925,6 +40973,18 @@ async function exportExitBundle(opts) {
39925
40973
  };
39926
40974
  const manifestBytes = jsonBytes(manifest);
39927
40975
  await writeFile(join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
40976
+ if (didWebBinding !== void 0) {
40977
+ opts.auditLog.append(
40978
+ "l1",
40979
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.EXPORT_INCLUDED,
40980
+ identity.identity_id,
40981
+ {
40982
+ approval_id: exportApprovalAuditId,
40983
+ identifier: didWebBinding.identifier,
40984
+ authority_host: didWebBinding.authority_host
40985
+ }
40986
+ );
40987
+ }
39928
40988
  await opts.auditLog.flush();
39929
40989
  return {
39930
40990
  bundle_dir: bundleDir,
@@ -39936,6 +40996,30 @@ async function exportExitBundle(opts) {
39936
40996
  ]
39937
40997
  };
39938
40998
  }
40999
+ function validateExportDidWeb(binding) {
41000
+ if (binding === void 0) return void 0;
41001
+ if (!binding.identifier || typeof binding.identifier !== "string") {
41002
+ throw new Error(
41003
+ "exit-bundle: did_web.identifier must be a non-empty did:web URI"
41004
+ );
41005
+ }
41006
+ if (!binding.authority_host || typeof binding.authority_host !== "string") {
41007
+ throw new Error(
41008
+ "exit-bundle: did_web.authority_host must be a non-empty DNS host"
41009
+ );
41010
+ }
41011
+ const parsed = parseDidWeb(binding.identifier);
41012
+ if (parsed.authority_host.toLowerCase() !== binding.authority_host.toLowerCase()) {
41013
+ throw new Error(
41014
+ `exit-bundle: did_web.identifier authority host '${parsed.authority_host}' does not match did_web.authority_host '${binding.authority_host}'`
41015
+ );
41016
+ }
41017
+ return {
41018
+ identifier: binding.identifier,
41019
+ authority_host: binding.authority_host,
41020
+ ...binding.published_at !== void 0 ? { published_at: binding.published_at } : {}
41021
+ };
41022
+ }
39939
41023
  function publicKeysFromIdentityArtifact(identityArtifact) {
39940
41024
  const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
39941
41025
  return {
@@ -40150,6 +41234,87 @@ async function importExitBundle(opts) {
40150
41234
  };
40151
41235
  }
40152
41236
  const manifest = await readManifest(opts.bundleDir);
41237
+ const importWarnings = [];
41238
+ const manifestDidWeb = manifest.body.identity_binding.did_web;
41239
+ if (manifestDidWeb !== void 0 && !opts.skipDidWebVerify) {
41240
+ const expectedPublicKey = fromBase64url(
41241
+ manifest.body.identity_binding.fortress_master_pubkey
41242
+ );
41243
+ const resolveOpts = {
41244
+ allowed_hosts: opts.didWebAllowedHosts ?? [],
41245
+ expected_public_key: expectedPublicKey,
41246
+ ...opts.didWebFetcher !== void 0 ? { fetcher: opts.didWebFetcher } : {},
41247
+ ...opts.didWebTimeoutMs !== void 0 ? { timeout_ms: opts.didWebTimeoutMs } : {}
41248
+ };
41249
+ const resolution = await resolveDidWeb(
41250
+ manifestDidWeb.identifier,
41251
+ resolveOpts
41252
+ );
41253
+ const authorityHost = manifestDidWeb.authority_host;
41254
+ opts.auditLog.append(
41255
+ "l1",
41256
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.AUTHORITY_HOST,
41257
+ manifest.body.identity_binding.identity_id,
41258
+ { authority_host: authorityHost, identifier: manifestDidWeb.identifier }
41259
+ );
41260
+ if (resolution.ok) {
41261
+ opts.auditLog.append(
41262
+ "l1",
41263
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41264
+ manifest.body.identity_binding.identity_id,
41265
+ {
41266
+ outcome: "success",
41267
+ identifier: manifestDidWeb.identifier,
41268
+ authority_host: authorityHost,
41269
+ resolved_url: resolution.url
41270
+ }
41271
+ );
41272
+ } else if (resolution.failure === "signature_mismatch") {
41273
+ opts.auditLog.append(
41274
+ "l1",
41275
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41276
+ manifest.body.identity_binding.identity_id,
41277
+ {
41278
+ outcome: "mismatch",
41279
+ identifier: manifestDidWeb.identifier,
41280
+ authority_host: authorityHost,
41281
+ resolved_url: resolution.url
41282
+ }
41283
+ );
41284
+ await opts.auditLog.flush();
41285
+ throw new ExitBundleImportError(
41286
+ "did_web_mismatch",
41287
+ `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.`
41288
+ );
41289
+ } else {
41290
+ opts.auditLog.append(
41291
+ "l1",
41292
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41293
+ manifest.body.identity_binding.identity_id,
41294
+ {
41295
+ outcome: "resolution_failure",
41296
+ failure: resolution.failure,
41297
+ identifier: manifestDidWeb.identifier,
41298
+ authority_host: authorityHost,
41299
+ resolved_url: resolution.url
41300
+ }
41301
+ );
41302
+ importWarnings.push(
41303
+ `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.`
41304
+ );
41305
+ }
41306
+ } else if (manifestDidWeb !== void 0 && opts.skipDidWebVerify) {
41307
+ opts.auditLog.append(
41308
+ "l1",
41309
+ EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
41310
+ manifest.body.identity_binding.identity_id,
41311
+ {
41312
+ outcome: "skipped",
41313
+ identifier: manifestDidWeb.identifier,
41314
+ authority_host: manifestDidWeb.authority_host
41315
+ }
41316
+ );
41317
+ }
40153
41318
  const identityArtifact = await loadExitArtifact(
40154
41319
  opts.bundleDir,
40155
41320
  manifest,
@@ -40214,7 +41379,7 @@ async function importExitBundle(opts) {
40214
41379
  unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
40215
41380
  },
40216
41381
  staged_artifacts: [],
40217
- warnings: verification.warnings,
41382
+ warnings: [...verification.warnings, ...importWarnings],
40218
41383
  unsupported_artifacts: verification.unsupported_artifacts
40219
41384
  };
40220
41385
  }
@@ -40381,7 +41546,7 @@ async function importExitBundle(opts) {
40381
41546
  state: stateResult,
40382
41547
  reputation: reputationResult,
40383
41548
  staged_artifacts: stagedArtifacts,
40384
- warnings: verification.warnings,
41549
+ warnings: [...verification.warnings, ...importWarnings],
40385
41550
  unsupported_artifacts: verification.unsupported_artifacts
40386
41551
  };
40387
41552
  }
@@ -40604,6 +41769,26 @@ ${policyErr.message}
40604
41769
  }
40605
41770
  throw policyErr;
40606
41771
  }
41772
+ const includeDidWebFlag = flagValue(argv, "--include-did-web");
41773
+ const includeDidWebDisabled = includeDidWebFlag === "false";
41774
+ const didWebIdentifier = flagValue(argv, "--did-web");
41775
+ const didWebAuthorityHost = flagValue(argv, "--did-web-authority-host");
41776
+ const didWebPublishedAt = flagValue(argv, "--did-web-published-at");
41777
+ let exportDidWeb;
41778
+ if (!includeDidWebDisabled && didWebIdentifier !== void 0) {
41779
+ if (didWebAuthorityHost === void 0) {
41780
+ write(
41781
+ err,
41782
+ "Error: --did-web requires --did-web-authority-host=<host>\n"
41783
+ );
41784
+ return 2;
41785
+ }
41786
+ exportDidWeb = {
41787
+ identifier: didWebIdentifier,
41788
+ authority_host: didWebAuthorityHost,
41789
+ ...didWebPublishedAt !== void 0 ? { published_at: didWebPublishedAt } : {}
41790
+ };
41791
+ }
40607
41792
  const result = await exportExitBundle({
40608
41793
  bundleDir: outDir,
40609
41794
  storage: ctx.storage,
@@ -40615,7 +41800,8 @@ ${policyErr.message}
40615
41800
  config,
40616
41801
  stateStoragePath: ctx.stateStoragePath,
40617
41802
  stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
40618
- keySource: ctx.keySource
41803
+ keySource: ctx.keySource,
41804
+ ...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
40619
41805
  });
40620
41806
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
40621
41807
  else {
@@ -40693,6 +41879,11 @@ ${policyErr.message}
40693
41879
  write(err, "--conflict must be skip, overwrite, or version\n");
40694
41880
  return 2;
40695
41881
  }
41882
+ const didWebAllowedHosts = repeatedFlagValues(
41883
+ argv,
41884
+ "--did-web-allowed-host"
41885
+ );
41886
+ const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
40696
41887
  let result;
40697
41888
  try {
40698
41889
  result = await importExitBundle({
@@ -40708,7 +41899,9 @@ ${policyErr.message}
40708
41899
  conflictResolution: conflict,
40709
41900
  sourcePassphrase: flagValue(argv, "--source-passphrase"),
40710
41901
  sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
40711
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
41902
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
41903
+ ...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
41904
+ skipDidWebVerify
40712
41905
  });
40713
41906
  } catch (e) {
40714
41907
  if (e instanceof InvalidExitBundleError) {
@@ -41445,12 +42638,14 @@ ${err.message}
41445
42638
  fortressId: fortressIdForAggregator
41446
42639
  });
41447
42640
  const handoffEventBridge = new HandoffEventBridge();
42641
+ const workflowStateTracker = new WorkflowStateTracker();
41448
42642
  if (dashboard) {
41449
42643
  dashboard.setHandoffLog({
41450
42644
  handoffLog,
41451
42645
  eventBridge: handoffEventBridge,
41452
42646
  auditLog,
41453
- operatorId: aggregatorIdentityId
42647
+ operatorId: aggregatorIdentityId,
42648
+ workflowStateTracker
41454
42649
  });
41455
42650
  }
41456
42651
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);