@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.js CHANGED
@@ -16700,8 +16700,30 @@ 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
 
16707
16729
  // src/coordination/context-transfer-extractor.ts
@@ -16973,10 +16995,123 @@ function categoryFromPolicyRuleId(ruleId) {
16973
16995
  var CONTEXT_TRANSFER_AUDIT_OPS = {
16974
16996
  DECODED: "operator_handoff_context_transfer_decoded"
16975
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
+ }
16976
17110
 
16977
17111
  // src/coordination/handoff-routes.ts
16978
17112
  var COORDINATION_API_PREFIX = "/api/coordination";
16979
17113
  var COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
17114
+ var COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
16980
17115
  var COORDINATION_LIST_DEFAULT_LIMIT = 50;
16981
17116
  var COORDINATION_LIST_MAX_LIMIT = 500;
16982
17117
  var HandoffEventBridge = class {
@@ -17015,6 +17150,101 @@ function matchEntryRoute2(path) {
17015
17150
  if (rest.includes("/")) return null;
17016
17151
  return { entryId: decodeURIComponent(rest) };
17017
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
+ }
17018
17248
  async function handleStream3(deps, res) {
17019
17249
  res.writeHead(200, {
17020
17250
  "Content-Type": "text/event-stream",
@@ -17098,6 +17328,67 @@ async function handleCoordinationRoute(deps, req, res) {
17098
17328
  writeJSON6(res, 200, { ok: true, data: { entries } });
17099
17329
  return true;
17100
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
+ }
17101
17392
  const entryMatch = matchEntryRoute2(path);
17102
17393
  if (method === "GET" && entryMatch) {
17103
17394
  const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
@@ -17239,6 +17530,8 @@ var DashboardApprovalChannel = class {
17239
17530
  */
17240
17531
  handoffLog = null;
17241
17532
  handoffEventBridge = null;
17533
+ handoffContextTransfer = null;
17534
+ workflowStateTracker = null;
17242
17535
  handoffAuditLog = null;
17243
17536
  handoffOperatorId = null;
17244
17537
  constructor(config) {
@@ -17319,6 +17612,8 @@ var DashboardApprovalChannel = class {
17319
17612
  this.handoffEventBridge = opts.eventBridge ?? null;
17320
17613
  this.handoffAuditLog = opts.auditLog ?? null;
17321
17614
  this.handoffOperatorId = opts.operatorId ?? null;
17615
+ this.handoffContextTransfer = opts.contextTransfer ?? null;
17616
+ this.workflowStateTracker = opts.workflowStateTracker ?? null;
17322
17617
  }
17323
17618
  /**
17324
17619
  * v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
@@ -17376,7 +17671,9 @@ var DashboardApprovalChannel = class {
17376
17671
  handoffLog: this.handoffLog,
17377
17672
  auditLog: this.handoffAuditLog,
17378
17673
  operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
17379
- events: this.handoffEventBridge
17674
+ events: this.handoffEventBridge,
17675
+ ...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
17676
+ ...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
17380
17677
  },
17381
17678
  req,
17382
17679
  res
@@ -21909,6 +22206,70 @@ function classifierSpecificAuditOp(classifierId) {
21909
22206
  return null;
21910
22207
  }
21911
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
+ };
22272
+
21912
22273
  // src/sentinel/sentinel.ts
21913
22274
  var Sentinel = class {
21914
22275
  /**
@@ -25142,7 +25503,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25142
25503
  const now = (/* @__PURE__ */ new Date()).toISOString();
25143
25504
  const canonicalBytes = canonicalize2(outcome);
25144
25505
  const canonicalString = new TextDecoder().decode(canonicalBytes);
25145
- const sha25611 = createCommitment(canonicalString);
25506
+ const sha25612 = createCommitment(canonicalString);
25146
25507
  let pedersenData;
25147
25508
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
25148
25509
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -25154,7 +25515,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25154
25515
  const commitmentPayload = {
25155
25516
  bridge_commitment_id: commitmentId,
25156
25517
  session_id: outcome.session_id,
25157
- sha256_commitment: sha25611.commitment,
25518
+ sha256_commitment: sha25612.commitment,
25158
25519
  terms_hash: outcome.terms_hash,
25159
25520
  committer_did: identity.did,
25160
25521
  committed_at: now,
@@ -25165,8 +25526,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
25165
25526
  return {
25166
25527
  bridge_commitment_id: commitmentId,
25167
25528
  session_id: outcome.session_id,
25168
- sha256_commitment: sha25611.commitment,
25169
- blinding_factor: sha25611.blinding_factor,
25529
+ sha256_commitment: sha25612.commitment,
25530
+ blinding_factor: sha25612.blinding_factor,
25170
25531
  committer_did: identity.did,
25171
25532
  signature: toBase64url(signature),
25172
25533
  pedersen_commitment: pedersenData,
@@ -39717,6 +40078,150 @@ var EXIT_BUNDLE_ARTIFACT_KINDS = [
39717
40078
  "placeholder_vault_metadata"
39718
40079
  ];
39719
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
+
39720
40225
  // src/exit/bundle.ts
39721
40226
  init_hashing();
39722
40227
  init_encoding();
@@ -40141,6 +40646,11 @@ async function verifyExitBundle(bundleDir, options = {}) {
40141
40646
 
40142
40647
  // src/exit/bundle.ts
40143
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
+ };
40144
40654
  var EXIT_IMPORT_NAMESPACE = "_exit_imports";
40145
40655
  var EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
40146
40656
  var EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
@@ -40432,6 +40942,7 @@ async function exportExitBundle(opts) {
40432
40942
  "placeholder_vault_metadata"
40433
40943
  )
40434
40944
  );
40945
+ const didWebBinding = validateExportDidWeb(opts.didWeb);
40435
40946
  const body = {
40436
40947
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
40437
40948
  exported_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -40439,7 +40950,8 @@ async function exportExitBundle(opts) {
40439
40950
  identity_id: identity.identity_id,
40440
40951
  fortress_id: identity.did,
40441
40952
  fortress_master_pubkey: identity.public_key,
40442
- did: identity.did
40953
+ did: identity.did,
40954
+ ...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
40443
40955
  },
40444
40956
  source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
40445
40957
  artifacts,
@@ -40461,6 +40973,18 @@ async function exportExitBundle(opts) {
40461
40973
  };
40462
40974
  const manifestBytes = jsonBytes(manifest);
40463
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
+ }
40464
40988
  await opts.auditLog.flush();
40465
40989
  return {
40466
40990
  bundle_dir: bundleDir,
@@ -40472,6 +40996,30 @@ async function exportExitBundle(opts) {
40472
40996
  ]
40473
40997
  };
40474
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
+ }
40475
41023
  function publicKeysFromIdentityArtifact(identityArtifact) {
40476
41024
  const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
40477
41025
  return {
@@ -40686,6 +41234,87 @@ async function importExitBundle(opts) {
40686
41234
  };
40687
41235
  }
40688
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
+ }
40689
41318
  const identityArtifact = await loadExitArtifact(
40690
41319
  opts.bundleDir,
40691
41320
  manifest,
@@ -40750,7 +41379,7 @@ async function importExitBundle(opts) {
40750
41379
  unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
40751
41380
  },
40752
41381
  staged_artifacts: [],
40753
- warnings: verification.warnings,
41382
+ warnings: [...verification.warnings, ...importWarnings],
40754
41383
  unsupported_artifacts: verification.unsupported_artifacts
40755
41384
  };
40756
41385
  }
@@ -40917,7 +41546,7 @@ async function importExitBundle(opts) {
40917
41546
  state: stateResult,
40918
41547
  reputation: reputationResult,
40919
41548
  staged_artifacts: stagedArtifacts,
40920
- warnings: verification.warnings,
41549
+ warnings: [...verification.warnings, ...importWarnings],
40921
41550
  unsupported_artifacts: verification.unsupported_artifacts
40922
41551
  };
40923
41552
  }
@@ -41140,6 +41769,26 @@ ${policyErr.message}
41140
41769
  }
41141
41770
  throw policyErr;
41142
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
+ }
41143
41792
  const result = await exportExitBundle({
41144
41793
  bundleDir: outDir,
41145
41794
  storage: ctx.storage,
@@ -41151,7 +41800,8 @@ ${policyErr.message}
41151
41800
  config,
41152
41801
  stateStoragePath: ctx.stateStoragePath,
41153
41802
  stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
41154
- keySource: ctx.keySource
41803
+ keySource: ctx.keySource,
41804
+ ...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
41155
41805
  });
41156
41806
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
41157
41807
  else {
@@ -41229,6 +41879,11 @@ ${policyErr.message}
41229
41879
  write(err, "--conflict must be skip, overwrite, or version\n");
41230
41880
  return 2;
41231
41881
  }
41882
+ const didWebAllowedHosts = repeatedFlagValues(
41883
+ argv,
41884
+ "--did-web-allowed-host"
41885
+ );
41886
+ const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
41232
41887
  let result;
41233
41888
  try {
41234
41889
  result = await importExitBundle({
@@ -41244,7 +41899,9 @@ ${policyErr.message}
41244
41899
  conflictResolution: conflict,
41245
41900
  sourcePassphrase: flagValue(argv, "--source-passphrase"),
41246
41901
  sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
41247
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
41902
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
41903
+ ...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
41904
+ skipDidWebVerify
41248
41905
  });
41249
41906
  } catch (e) {
41250
41907
  if (e instanceof InvalidExitBundleError) {
@@ -41981,12 +42638,14 @@ ${err.message}
41981
42638
  fortressId: fortressIdForAggregator
41982
42639
  });
41983
42640
  const handoffEventBridge = new HandoffEventBridge();
42641
+ const workflowStateTracker = new WorkflowStateTracker();
41984
42642
  if (dashboard) {
41985
42643
  dashboard.setHandoffLog({
41986
42644
  handoffLog,
41987
42645
  eventBridge: handoffEventBridge,
41988
42646
  auditLog,
41989
- operatorId: aggregatorIdentityId
42647
+ operatorId: aggregatorIdentityId,
42648
+ workflowStateTracker
41990
42649
  });
41991
42650
  }
41992
42651
  const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);