@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/cli.cjs +2375 -201
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2375 -201
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1238 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +388 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +1238 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
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
|
|
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:
|
|
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:
|
|
24777
|
-
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
|
-
|
|
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);
|