@sanctuary-framework/mcp-server 1.2.12 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -16707,8 +16707,30 @@ 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
 
16714
16736
  // src/coordination/context-transfer-extractor.ts
@@ -16980,10 +17002,123 @@ function categoryFromPolicyRuleId(ruleId) {
16980
17002
  var CONTEXT_TRANSFER_AUDIT_OPS = {
16981
17003
  DECODED: "operator_handoff_context_transfer_decoded"
16982
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
+ }
16983
17117
 
16984
17118
  // src/coordination/handoff-routes.ts
16985
17119
  var COORDINATION_API_PREFIX = "/api/coordination";
16986
17120
  var COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
17121
+ var COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
16987
17122
  var COORDINATION_LIST_DEFAULT_LIMIT = 50;
16988
17123
  var COORDINATION_LIST_MAX_LIMIT = 500;
16989
17124
  var HandoffEventBridge = class {
@@ -17022,6 +17157,101 @@ function matchEntryRoute2(path) {
17022
17157
  if (rest.includes("/")) return null;
17023
17158
  return { entryId: decodeURIComponent(rest) };
17024
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
+ }
17025
17255
  async function handleStream3(deps, res) {
17026
17256
  res.writeHead(200, {
17027
17257
  "Content-Type": "text/event-stream",
@@ -17105,6 +17335,67 @@ async function handleCoordinationRoute(deps, req, res) {
17105
17335
  writeJSON6(res, 200, { ok: true, data: { entries } });
17106
17336
  return true;
17107
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
+ }
17108
17399
  const entryMatch = matchEntryRoute2(path);
17109
17400
  if (method === "GET" && entryMatch) {
17110
17401
  const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
@@ -17246,6 +17537,8 @@ var DashboardApprovalChannel = class {
17246
17537
  */
17247
17538
  handoffLog = null;
17248
17539
  handoffEventBridge = null;
17540
+ handoffContextTransfer = null;
17541
+ workflowStateTracker = null;
17249
17542
  handoffAuditLog = null;
17250
17543
  handoffOperatorId = null;
17251
17544
  constructor(config) {
@@ -17326,6 +17619,8 @@ var DashboardApprovalChannel = class {
17326
17619
  this.handoffEventBridge = opts.eventBridge ?? null;
17327
17620
  this.handoffAuditLog = opts.auditLog ?? null;
17328
17621
  this.handoffOperatorId = opts.operatorId ?? null;
17622
+ this.handoffContextTransfer = opts.contextTransfer ?? null;
17623
+ this.workflowStateTracker = opts.workflowStateTracker ?? null;
17329
17624
  }
17330
17625
  /**
17331
17626
  * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
@@ -17383,7 +17678,9 @@ var DashboardApprovalChannel = class {
17383
17678
  handoffLog: this.handoffLog,
17384
17679
  auditLog: this.handoffAuditLog,
17385
17680
  operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
17386
- events: this.handoffEventBridge
17681
+ events: this.handoffEventBridge,
17682
+ ...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
17683
+ ...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
17387
17684
  },
17388
17685
  req,
17389
17686
  res
@@ -21916,6 +22213,70 @@ function classifierSpecificAuditOp(classifierId) {
21916
22213
  return null;
21917
22214
  }
21918
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
+ };
22279
+
21919
22280
  // src/sentinel/sentinel.ts
21920
22281
  var Sentinel = class {
21921
22282
  /**
@@ -25149,7 +25510,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25149
25510
  const now = (/* @__PURE__ */ new Date()).toISOString();
25150
25511
  const canonicalBytes = canonicalize2(outcome);
25151
25512
  const canonicalString = new TextDecoder().decode(canonicalBytes);
25152
- const sha25611 = createCommitment(canonicalString);
25513
+ const sha25612 = createCommitment(canonicalString);
25153
25514
  let pedersenData;
25154
25515
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
25155
25516
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -25161,7 +25522,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25161
25522
  const commitmentPayload = {
25162
25523
  bridge_commitment_id: commitmentId,
25163
25524
  session_id: outcome.session_id,
25164
- sha256_commitment: sha25611.commitment,
25525
+ sha256_commitment: sha25612.commitment,
25165
25526
  terms_hash: outcome.terms_hash,
25166
25527
  committer_did: identity.did,
25167
25528
  committed_at: now,
@@ -25172,8 +25533,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25172
25533
  return {
25173
25534
  bridge_commitment_id: commitmentId,
25174
25535
  session_id: outcome.session_id,
25175
- sha256_commitment: sha25611.commitment,
25176
- blinding_factor: sha25611.blinding_factor,
25536
+ sha256_commitment: sha25612.commitment,
25537
+ blinding_factor: sha25612.blinding_factor,
25177
25538
  committer_did: identity.did,
25178
25539
  signature: toBase64url(signature),
25179
25540
  pedersen_commitment: pedersenData,
@@ -39724,6 +40085,150 @@ var EXIT_BUNDLE_ARTIFACT_KINDS = [
39724
40085
  "placeholder_vault_metadata"
39725
40086
  ];
39726
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
+
39727
40232
  // src/exit/bundle.ts
39728
40233
  init_hashing();
39729
40234
  init_encoding();
@@ -40148,6 +40653,11 @@ async function verifyExitBundle(bundleDir, options = {}) {
40148
40653
 
40149
40654
  // src/exit/bundle.ts
40150
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
+ };
40151
40661
  var EXIT_IMPORT_NAMESPACE = "_exit_imports";
40152
40662
  var EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
40153
40663
  var EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
@@ -40439,6 +40949,7 @@ async function exportExitBundle(opts) {
40439
40949
  "placeholder_vault_metadata"
40440
40950
  )
40441
40951
  );
40952
+ const didWebBinding = validateExportDidWeb(opts.didWeb);
40442
40953
  const body = {
40443
40954
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
40444
40955
  exported_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -40446,7 +40957,8 @@ async function exportExitBundle(opts) {
40446
40957
  identity_id: identity.identity_id,
40447
40958
  fortress_id: identity.did,
40448
40959
  fortress_master_pubkey: identity.public_key,
40449
- did: identity.did
40960
+ did: identity.did,
40961
+ ...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
40450
40962
  },
40451
40963
  source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
40452
40964
  artifacts,
@@ -40468,6 +40980,18 @@ async function exportExitBundle(opts) {
40468
40980
  };
40469
40981
  const manifestBytes = jsonBytes(manifest);
40470
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
+ }
40471
40995
  await opts.auditLog.flush();
40472
40996
  return {
40473
40997
  bundle_dir: bundleDir,
@@ -40479,6 +41003,30 @@ async function exportExitBundle(opts) {
40479
41003
  ]
40480
41004
  };
40481
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
+ }
40482
41030
  function publicKeysFromIdentityArtifact(identityArtifact) {
40483
41031
  const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
40484
41032
  return {
@@ -40693,6 +41241,87 @@ async function importExitBundle(opts) {
40693
41241
  };
40694
41242
  }
40695
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
+ }
40696
41325
  const identityArtifact = await loadExitArtifact(
40697
41326
  opts.bundleDir,
40698
41327
  manifest,
@@ -40757,7 +41386,7 @@ async function importExitBundle(opts) {
40757
41386
  unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
40758
41387
  },
40759
41388
  staged_artifacts: [],
40760
- warnings: verification.warnings,
41389
+ warnings: [...verification.warnings, ...importWarnings],
40761
41390
  unsupported_artifacts: verification.unsupported_artifacts
40762
41391
  };
40763
41392
  }
@@ -40924,7 +41553,7 @@ async function importExitBundle(opts) {
40924
41553
  state: stateResult,
40925
41554
  reputation: reputationResult,
40926
41555
  staged_artifacts: stagedArtifacts,
40927
- warnings: verification.warnings,
41556
+ warnings: [...verification.warnings, ...importWarnings],
40928
41557
  unsupported_artifacts: verification.unsupported_artifacts
40929
41558
  };
40930
41559
  }
@@ -41147,6 +41776,26 @@ ${policyErr.message}
41147
41776
  }
41148
41777
  throw policyErr;
41149
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
+ }
41150
41799
  const result = await exportExitBundle({
41151
41800
  bundleDir: outDir,
41152
41801
  storage: ctx.storage,
@@ -41158,7 +41807,8 @@ ${policyErr.message}
41158
41807
  config,
41159
41808
  stateStoragePath: ctx.stateStoragePath,
41160
41809
  stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
41161
- keySource: ctx.keySource
41810
+ keySource: ctx.keySource,
41811
+ ...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
41162
41812
  });
41163
41813
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
41164
41814
  else {
@@ -41236,6 +41886,11 @@ ${policyErr.message}
41236
41886
  write(err, "--conflict must be skip, overwrite, or version\n");
41237
41887
  return 2;
41238
41888
  }
41889
+ const didWebAllowedHosts = repeatedFlagValues(
41890
+ argv,
41891
+ "--did-web-allowed-host"
41892
+ );
41893
+ const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
41239
41894
  let result;
41240
41895
  try {
41241
41896
  result = await importExitBundle({
@@ -41251,7 +41906,9 @@ ${policyErr.message}
41251
41906
  conflictResolution: conflict,
41252
41907
  sourcePassphrase: flagValue(argv, "--source-passphrase"),
41253
41908
  sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
41254
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
41909
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
41910
+ ...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
41911
+ skipDidWebVerify
41255
41912
  });
41256
41913
  } catch (e) {
41257
41914
  if (e instanceof InvalidExitBundleError) {
@@ -41988,12 +42645,14 @@ ${err.message}
41988
42645
  fortressId: fortressIdForAggregator
41989
42646
  });
41990
42647
  const handoffEventBridge = new HandoffEventBridge();
42648
+ const workflowStateTracker = new WorkflowStateTracker();
41991
42649
  if (dashboard) {
41992
42650
  dashboard.setHandoffLog({
41993
42651
  handoffLog,
41994
42652
  eventBridge: handoffEventBridge,
41995
42653
  auditLog,
41996
- operatorId: aggregatorIdentityId
42654
+ operatorId: aggregatorIdentityId,
42655
+ workflowStateTracker
41997
42656
  });
41998
42657
  }
41999
42658
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);