@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/cli.cjs
CHANGED
|
@@ -17625,11 +17625,426 @@ var init_handoff_log = __esm({
|
|
|
17625
17625
|
}
|
|
17626
17626
|
};
|
|
17627
17627
|
COORDINATION_VIEW_AUDIT_OPS = {
|
|
17628
|
+
/** v1.3 Omega-1: operator opened the chronological handoff list. */
|
|
17628
17629
|
VIEW_OPENED: "operator_coordination_view_opened",
|
|
17629
|
-
|
|
17630
|
+
/** v1.3 Omega-1: operator drilled into a single handoff for detail. */
|
|
17631
|
+
ENTRY_DRILLED: "operator_handoff_entry_drilled",
|
|
17632
|
+
/**
|
|
17633
|
+
* v1.3 Omega-3: operator opened the Workflows sibling-view (list of
|
|
17634
|
+
* multi-handoff workflows grouped by `workflow-grouper`). Mirrors
|
|
17635
|
+
* VIEW_OPENED's shape so the dashboard activity feed can group both
|
|
17636
|
+
* as "operator coordination surfaces."
|
|
17637
|
+
*/
|
|
17638
|
+
WORKFLOW_VIEW_OPENED: "operator_workflow_view_opened",
|
|
17639
|
+
/**
|
|
17640
|
+
* v1.3 Omega-3: operator drilled into a single workflow for its
|
|
17641
|
+
* timeline + member-handoffs detail. Mirrors ENTRY_DRILLED's shape.
|
|
17642
|
+
*/
|
|
17643
|
+
WORKFLOW_DRILLED: "operator_workflow_drilled",
|
|
17644
|
+
/**
|
|
17645
|
+
* v1.3 Omega-3: server-side state transition observed on a
|
|
17646
|
+
* workflow (e.g., in_progress -> stalled). Emitted by the route
|
|
17647
|
+
* layer after the state tracker diffs against its prior snapshot.
|
|
17648
|
+
* Distinct from the operator-action events above: this records what
|
|
17649
|
+
* the workflow itself is doing, not what the operator clicked.
|
|
17650
|
+
*/
|
|
17651
|
+
WORKFLOW_STATE_CHANGED: "coordination_workflow_state_changed"
|
|
17652
|
+
};
|
|
17653
|
+
}
|
|
17654
|
+
});
|
|
17655
|
+
|
|
17656
|
+
// src/coordination/context-transfer-extractor.ts
|
|
17657
|
+
async function extractContextTransferBreakdown(detail, deps = {}) {
|
|
17658
|
+
const pathA = tryStructuredPath(detail);
|
|
17659
|
+
if (pathA) return pathA;
|
|
17660
|
+
const pathB = tryCompositionPath(detail);
|
|
17661
|
+
if (pathB) return pathB;
|
|
17662
|
+
const pathC = tryHeuristicPath(detail);
|
|
17663
|
+
if (pathC.confidence >= 0.5 || !deps.substrateSelector) {
|
|
17664
|
+
return pathC;
|
|
17665
|
+
}
|
|
17666
|
+
const assist = await tryLlmAssistPath(detail, deps.substrateSelector);
|
|
17667
|
+
return assist ?? pathC;
|
|
17668
|
+
}
|
|
17669
|
+
function tryStructuredPath(detail) {
|
|
17670
|
+
const details = sourceDetails(detail.source_audit_entry);
|
|
17671
|
+
if (!details) return null;
|
|
17672
|
+
const transferredRaw = details["transferred"];
|
|
17673
|
+
const withheldRaw = details["withheld"];
|
|
17674
|
+
if (transferredRaw === void 0 && withheldRaw === void 0) return null;
|
|
17675
|
+
const transferred = parseExplicitContextItems(transferredRaw);
|
|
17676
|
+
const withheld = parseExplicitContextItems(withheldRaw);
|
|
17677
|
+
return {
|
|
17678
|
+
handoff_entry_id: detail.entry.entry_id,
|
|
17679
|
+
transferred,
|
|
17680
|
+
withheld,
|
|
17681
|
+
source: "structured",
|
|
17682
|
+
confidence: 1
|
|
17683
|
+
};
|
|
17684
|
+
}
|
|
17685
|
+
function tryCompositionPath(detail) {
|
|
17686
|
+
const op = detail.source_audit_entry.operation;
|
|
17687
|
+
if (!op.startsWith("composition_completed")) return null;
|
|
17688
|
+
const details = sourceDetails(detail.source_audit_entry);
|
|
17689
|
+
if (!details) return null;
|
|
17690
|
+
const receiptRaw = details["receipt"];
|
|
17691
|
+
const sourceStateRaw = details["source_state_snapshot"];
|
|
17692
|
+
if (receiptRaw === void 0) return null;
|
|
17693
|
+
const transferred = parseExplicitContextItems(receiptRaw);
|
|
17694
|
+
const withheld = [];
|
|
17695
|
+
if (Array.isArray(sourceStateRaw)) {
|
|
17696
|
+
const transferredKeys = new Set(
|
|
17697
|
+
transferred.map((t) => `${t.category}:${t.summary}`)
|
|
17698
|
+
);
|
|
17699
|
+
for (const item of parseExplicitContextItems(sourceStateRaw)) {
|
|
17700
|
+
const key = `${item.category}:${item.summary}`;
|
|
17701
|
+
if (!transferredKeys.has(key)) withheld.push(item);
|
|
17702
|
+
}
|
|
17703
|
+
}
|
|
17704
|
+
return {
|
|
17705
|
+
handoff_entry_id: detail.entry.entry_id,
|
|
17706
|
+
transferred,
|
|
17707
|
+
withheld,
|
|
17708
|
+
source: "composition",
|
|
17709
|
+
confidence: 0.9
|
|
17710
|
+
};
|
|
17711
|
+
}
|
|
17712
|
+
function tryHeuristicPath(detail) {
|
|
17713
|
+
const entry = detail.entry;
|
|
17714
|
+
const audit = detail.source_audit_entry;
|
|
17715
|
+
const details = sourceDetails(audit);
|
|
17716
|
+
if (audit.operation === "cross_harness_approval_aggregated") {
|
|
17717
|
+
const ruleId = optString2(details, "policy_rule_id");
|
|
17718
|
+
if (ruleId) {
|
|
17719
|
+
const category = categoryFromPolicyRuleId(ruleId);
|
|
17720
|
+
const summary = `${ruleId} (${entry.source_agent_id} -> operator)`;
|
|
17721
|
+
return {
|
|
17722
|
+
handoff_entry_id: entry.entry_id,
|
|
17723
|
+
transferred: [
|
|
17724
|
+
{
|
|
17725
|
+
category,
|
|
17726
|
+
summary: truncate(summary, SUMMARY_MAX_CHARS),
|
|
17727
|
+
size_hint: "minimal"
|
|
17728
|
+
}
|
|
17729
|
+
],
|
|
17730
|
+
withheld: [],
|
|
17731
|
+
source: "heuristic",
|
|
17732
|
+
confidence: 0.5
|
|
17733
|
+
};
|
|
17734
|
+
}
|
|
17735
|
+
}
|
|
17736
|
+
if (audit.operation === "v1.1_local_handoff") {
|
|
17737
|
+
const reasonClass = optString2(details, "reason_class");
|
|
17738
|
+
const newStatus = optString2(details, "new_status");
|
|
17739
|
+
const previousStatus = optString2(details, "previous_status");
|
|
17740
|
+
const transferred = [];
|
|
17741
|
+
const withheld = [];
|
|
17742
|
+
if (newStatus === "denied" || newStatus === "failed") {
|
|
17743
|
+
withheld.push({
|
|
17744
|
+
category: "other",
|
|
17745
|
+
summary: truncate(
|
|
17746
|
+
`handoff ${newStatus}${reasonClass ? ` (${reasonClass})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
|
|
17747
|
+
SUMMARY_MAX_CHARS
|
|
17748
|
+
),
|
|
17749
|
+
size_hint: "minimal"
|
|
17750
|
+
});
|
|
17751
|
+
} else if (newStatus === "accepted" || newStatus === "completed") {
|
|
17752
|
+
transferred.push({
|
|
17753
|
+
category: "other",
|
|
17754
|
+
summary: truncate(
|
|
17755
|
+
`handoff ${newStatus}${previousStatus ? ` (from ${previousStatus})` : ""}: ${entry.source_agent_id} -> ${entry.target_agent_id}`,
|
|
17756
|
+
SUMMARY_MAX_CHARS
|
|
17757
|
+
),
|
|
17758
|
+
size_hint: "small"
|
|
17759
|
+
});
|
|
17760
|
+
} else {
|
|
17761
|
+
transferred.push({
|
|
17762
|
+
category: "other",
|
|
17763
|
+
summary: truncate(
|
|
17764
|
+
`handoff ${entry.source_agent_id} -> ${entry.target_agent_id}${newStatus ? ` (${newStatus})` : ""}`,
|
|
17765
|
+
SUMMARY_MAX_CHARS
|
|
17766
|
+
),
|
|
17767
|
+
size_hint: "minimal"
|
|
17768
|
+
});
|
|
17769
|
+
}
|
|
17770
|
+
return {
|
|
17771
|
+
handoff_entry_id: entry.entry_id,
|
|
17772
|
+
transferred,
|
|
17773
|
+
withheld,
|
|
17774
|
+
source: "heuristic",
|
|
17775
|
+
confidence: reasonClass || newStatus ? 0.5 : 0.3
|
|
17776
|
+
};
|
|
17777
|
+
}
|
|
17778
|
+
return {
|
|
17779
|
+
handoff_entry_id: entry.entry_id,
|
|
17780
|
+
transferred: [
|
|
17781
|
+
{
|
|
17782
|
+
category: "other",
|
|
17783
|
+
summary: truncate(
|
|
17784
|
+
`handoff ${entry.source_agent_id} -> ${entry.target_agent_id}`,
|
|
17785
|
+
SUMMARY_MAX_CHARS
|
|
17786
|
+
),
|
|
17787
|
+
size_hint: "minimal"
|
|
17788
|
+
}
|
|
17789
|
+
],
|
|
17790
|
+
withheld: [],
|
|
17791
|
+
source: "heuristic",
|
|
17792
|
+
confidence: 0.3
|
|
17793
|
+
};
|
|
17794
|
+
}
|
|
17795
|
+
async function tryLlmAssistPath(detail, selector) {
|
|
17796
|
+
const entry = detail.entry;
|
|
17797
|
+
const audit = detail.source_audit_entry;
|
|
17798
|
+
const probe = `event=${audit.operation} sender=${entry.source_agent_id} target=${entry.target_agent_id} summary=${entry.context_transfer_summary}`;
|
|
17799
|
+
try {
|
|
17800
|
+
const response = await selector.invokeClassify("sentinel-scoring", {
|
|
17801
|
+
kind: "classify",
|
|
17802
|
+
items: [probe],
|
|
17803
|
+
categories: [...CATEGORY_VALUES]
|
|
17804
|
+
});
|
|
17805
|
+
if (response.body.kind !== "classify") return null;
|
|
17806
|
+
const top = response.body.results[0];
|
|
17807
|
+
if (!top || !isCategory(top.category) || top.confidence < 0.4) {
|
|
17808
|
+
return null;
|
|
17809
|
+
}
|
|
17810
|
+
return {
|
|
17811
|
+
handoff_entry_id: entry.entry_id,
|
|
17812
|
+
transferred: [
|
|
17813
|
+
{
|
|
17814
|
+
category: top.category,
|
|
17815
|
+
summary: truncate(
|
|
17816
|
+
`LLM-classified handoff ${entry.source_agent_id} -> ${entry.target_agent_id}: ${top.category} (confidence ${top.confidence.toFixed(2)})`,
|
|
17817
|
+
SUMMARY_MAX_CHARS
|
|
17818
|
+
),
|
|
17819
|
+
size_hint: "minimal"
|
|
17820
|
+
}
|
|
17821
|
+
],
|
|
17822
|
+
withheld: [],
|
|
17823
|
+
source: "llm-assist",
|
|
17824
|
+
confidence: 0.6
|
|
17825
|
+
};
|
|
17826
|
+
} catch {
|
|
17827
|
+
return null;
|
|
17828
|
+
}
|
|
17829
|
+
}
|
|
17830
|
+
function sourceDetails(audit) {
|
|
17831
|
+
return audit.details;
|
|
17832
|
+
}
|
|
17833
|
+
function optString2(details, key) {
|
|
17834
|
+
if (!details) return null;
|
|
17835
|
+
const value = details[key];
|
|
17836
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
17837
|
+
return value;
|
|
17838
|
+
}
|
|
17839
|
+
function isCategory(value) {
|
|
17840
|
+
return CATEGORY_VALUES.includes(value);
|
|
17841
|
+
}
|
|
17842
|
+
function truncate(s, cap) {
|
|
17843
|
+
return s.length <= cap ? s : `${s.slice(0, cap - 3)}...`;
|
|
17844
|
+
}
|
|
17845
|
+
function parseExplicitContextItems(raw) {
|
|
17846
|
+
if (raw === null || raw === void 0) return [];
|
|
17847
|
+
if (Array.isArray(raw)) {
|
|
17848
|
+
const out = [];
|
|
17849
|
+
for (const entry of raw) {
|
|
17850
|
+
if (typeof entry === "string") {
|
|
17851
|
+
out.push({
|
|
17852
|
+
category: "other",
|
|
17853
|
+
summary: truncate(entry, SUMMARY_MAX_CHARS),
|
|
17854
|
+
size_hint: "minimal"
|
|
17855
|
+
});
|
|
17856
|
+
continue;
|
|
17857
|
+
}
|
|
17858
|
+
if (entry && typeof entry === "object") {
|
|
17859
|
+
const obj = entry;
|
|
17860
|
+
const category = isCategoryValue(obj["category"]) ? obj["category"] : "other";
|
|
17861
|
+
const summary = typeof obj["summary"] === "string" ? truncate(obj["summary"], SUMMARY_MAX_CHARS) : "(unspecified)";
|
|
17862
|
+
const sizeHint = isSizeHintValue(obj["size_hint"]) ? obj["size_hint"] : "minimal";
|
|
17863
|
+
out.push({ category, summary, size_hint: sizeHint });
|
|
17864
|
+
}
|
|
17865
|
+
}
|
|
17866
|
+
return out;
|
|
17867
|
+
}
|
|
17868
|
+
if (typeof raw === "object" && raw !== null) {
|
|
17869
|
+
const out = [];
|
|
17870
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
17871
|
+
const category = isCategoryValue(k) ? k : "other";
|
|
17872
|
+
if (Array.isArray(v)) {
|
|
17873
|
+
for (const item of v) {
|
|
17874
|
+
if (typeof item === "string") {
|
|
17875
|
+
out.push({
|
|
17876
|
+
category,
|
|
17877
|
+
summary: truncate(item, SUMMARY_MAX_CHARS),
|
|
17878
|
+
size_hint: "minimal"
|
|
17879
|
+
});
|
|
17880
|
+
}
|
|
17881
|
+
}
|
|
17882
|
+
}
|
|
17883
|
+
}
|
|
17884
|
+
return out;
|
|
17885
|
+
}
|
|
17886
|
+
return [];
|
|
17887
|
+
}
|
|
17888
|
+
function isCategoryValue(v) {
|
|
17889
|
+
return typeof v === "string" && CATEGORY_VALUES.includes(v);
|
|
17890
|
+
}
|
|
17891
|
+
function isSizeHintValue(v) {
|
|
17892
|
+
return typeof v === "string" && (v === "minimal" || v === "small" || v === "medium" || v === "large");
|
|
17893
|
+
}
|
|
17894
|
+
function categoryFromPolicyRuleId(ruleId) {
|
|
17895
|
+
const lower = ruleId.toLowerCase();
|
|
17896
|
+
if (lower.includes("credential") || lower.includes("broker_secret")) {
|
|
17897
|
+
return "credentials";
|
|
17898
|
+
}
|
|
17899
|
+
if (lower.includes("memory") || lower.includes("state_read")) {
|
|
17900
|
+
return "memory";
|
|
17901
|
+
}
|
|
17902
|
+
if (lower.includes("plan")) {
|
|
17903
|
+
return "plans";
|
|
17904
|
+
}
|
|
17905
|
+
if (lower.includes("export") || lower.includes("output")) {
|
|
17906
|
+
return "outputs";
|
|
17907
|
+
}
|
|
17908
|
+
if (lower.includes("audit")) {
|
|
17909
|
+
return "audit-refs";
|
|
17910
|
+
}
|
|
17911
|
+
return "other";
|
|
17912
|
+
}
|
|
17913
|
+
var SUMMARY_MAX_CHARS, CATEGORY_VALUES, CONTEXT_TRANSFER_AUDIT_OPS;
|
|
17914
|
+
var init_context_transfer_extractor = __esm({
|
|
17915
|
+
"src/coordination/context-transfer-extractor.ts"() {
|
|
17916
|
+
SUMMARY_MAX_CHARS = 240;
|
|
17917
|
+
CATEGORY_VALUES = [
|
|
17918
|
+
"memory",
|
|
17919
|
+
"credentials",
|
|
17920
|
+
"plans",
|
|
17921
|
+
"outputs",
|
|
17922
|
+
"audit-refs",
|
|
17923
|
+
"other"
|
|
17924
|
+
];
|
|
17925
|
+
CONTEXT_TRANSFER_AUDIT_OPS = {
|
|
17926
|
+
DECODED: "operator_handoff_context_transfer_decoded"
|
|
17630
17927
|
};
|
|
17631
17928
|
}
|
|
17632
17929
|
});
|
|
17930
|
+
function groupHandoffsIntoWorkflows(handoffs, opts) {
|
|
17931
|
+
if (handoffs.length === 0) return [];
|
|
17932
|
+
const now = opts?.now ?? /* @__PURE__ */ new Date();
|
|
17933
|
+
const linkedGroups = /* @__PURE__ */ new Map();
|
|
17934
|
+
const unlinked = [];
|
|
17935
|
+
for (const h of handoffs) {
|
|
17936
|
+
if (h.workflow_link !== null && h.workflow_link.length > 0) {
|
|
17937
|
+
let bucket = linkedGroups.get(h.workflow_link);
|
|
17938
|
+
if (!bucket) {
|
|
17939
|
+
bucket = [];
|
|
17940
|
+
linkedGroups.set(h.workflow_link, bucket);
|
|
17941
|
+
}
|
|
17942
|
+
bucket.push(h);
|
|
17943
|
+
} else {
|
|
17944
|
+
unlinked.push(h);
|
|
17945
|
+
}
|
|
17946
|
+
}
|
|
17947
|
+
const sortedUnlinked = [...unlinked].sort(
|
|
17948
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
17949
|
+
);
|
|
17950
|
+
const heuristicChains = [];
|
|
17951
|
+
for (const h of sortedUnlinked) {
|
|
17952
|
+
const joinedIdx = findExtendableChain(heuristicChains, h);
|
|
17953
|
+
if (joinedIdx !== null) {
|
|
17954
|
+
heuristicChains[joinedIdx].push(h);
|
|
17955
|
+
} else {
|
|
17956
|
+
heuristicChains.push([h]);
|
|
17957
|
+
}
|
|
17958
|
+
}
|
|
17959
|
+
const workflows = [];
|
|
17960
|
+
for (const members of linkedGroups.values()) {
|
|
17961
|
+
workflows.push(materialize(members, now));
|
|
17962
|
+
}
|
|
17963
|
+
for (const members of heuristicChains) {
|
|
17964
|
+
workflows.push(materialize(members, now));
|
|
17965
|
+
}
|
|
17966
|
+
workflows.sort(
|
|
17967
|
+
(a, b) => a.last_activity_at < b.last_activity_at ? 1 : -1
|
|
17968
|
+
);
|
|
17969
|
+
return workflows;
|
|
17970
|
+
}
|
|
17971
|
+
function determineWorkflowState(members, now) {
|
|
17972
|
+
if (members.length === 0) return "unknown";
|
|
17973
|
+
const sorted = [...members].sort(
|
|
17974
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
17975
|
+
);
|
|
17976
|
+
const last = sorted[sorted.length - 1];
|
|
17977
|
+
const root = sorted[0];
|
|
17978
|
+
const lastMs = Date.parse(last.observed_at);
|
|
17979
|
+
if (!Number.isFinite(lastMs)) return "unknown";
|
|
17980
|
+
if (last.target_agent_id === OPERATOR_PSEUDO_AGENT) {
|
|
17981
|
+
return "completed";
|
|
17982
|
+
}
|
|
17983
|
+
if (sorted.length > CYCLE_COMPLETION_MIN_HOPS && last.target_agent_id === root.source_agent_id) {
|
|
17984
|
+
return "completed";
|
|
17985
|
+
}
|
|
17986
|
+
const ageMs = now.getTime() - lastMs;
|
|
17987
|
+
if (ageMs > STALL_THRESHOLD_MS) {
|
|
17988
|
+
return "stalled";
|
|
17989
|
+
}
|
|
17990
|
+
return "in_progress";
|
|
17991
|
+
}
|
|
17992
|
+
function workflowIdFromRoot(rootEntryId) {
|
|
17993
|
+
return crypto.createHash("sha256").update(`workflow:${rootEntryId}`).digest("hex").slice(0, 32);
|
|
17994
|
+
}
|
|
17995
|
+
function findExtendableChain(chains, h) {
|
|
17996
|
+
const hMs = Date.parse(h.observed_at);
|
|
17997
|
+
if (!Number.isFinite(hMs)) return null;
|
|
17998
|
+
let bestIdx = null;
|
|
17999
|
+
let bestGapMs = Number.POSITIVE_INFINITY;
|
|
18000
|
+
for (let i = 0; i < chains.length; i += 1) {
|
|
18001
|
+
const chain = chains[i];
|
|
18002
|
+
const last = chain[chain.length - 1];
|
|
18003
|
+
const lastMs = Date.parse(last.observed_at);
|
|
18004
|
+
if (!Number.isFinite(lastMs)) continue;
|
|
18005
|
+
const gapMs = Math.abs(hMs - lastMs);
|
|
18006
|
+
if (gapMs > HEURISTIC_WINDOW_MS) continue;
|
|
18007
|
+
if (!sharesAgent(last, h)) continue;
|
|
18008
|
+
if (gapMs < bestGapMs) {
|
|
18009
|
+
bestGapMs = gapMs;
|
|
18010
|
+
bestIdx = i;
|
|
18011
|
+
}
|
|
18012
|
+
}
|
|
18013
|
+
return bestIdx;
|
|
18014
|
+
}
|
|
18015
|
+
function sharesAgent(a, b) {
|
|
18016
|
+
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;
|
|
18017
|
+
}
|
|
18018
|
+
function materialize(members, now) {
|
|
18019
|
+
const sorted = [...members].sort(
|
|
18020
|
+
(a, b) => a.observed_at < b.observed_at ? -1 : 1
|
|
18021
|
+
);
|
|
18022
|
+
const root = sorted[0];
|
|
18023
|
+
const last = sorted[sorted.length - 1];
|
|
18024
|
+
const involved = /* @__PURE__ */ new Set();
|
|
18025
|
+
for (const h of sorted) {
|
|
18026
|
+
if (h.source_agent_id) involved.add(h.source_agent_id);
|
|
18027
|
+
if (h.target_agent_id) involved.add(h.target_agent_id);
|
|
18028
|
+
}
|
|
18029
|
+
return {
|
|
18030
|
+
workflow_id: workflowIdFromRoot(root.entry_id),
|
|
18031
|
+
root_handoff: root,
|
|
18032
|
+
member_handoffs: sorted,
|
|
18033
|
+
state: determineWorkflowState(sorted, now),
|
|
18034
|
+
started_at: root.observed_at,
|
|
18035
|
+
last_activity_at: last.observed_at,
|
|
18036
|
+
involved_agents: [...involved].sort()
|
|
18037
|
+
};
|
|
18038
|
+
}
|
|
18039
|
+
var HEURISTIC_WINDOW_MS, STALL_THRESHOLD_MS, CYCLE_COMPLETION_MIN_HOPS;
|
|
18040
|
+
var init_workflow_grouper = __esm({
|
|
18041
|
+
"src/coordination/workflow-grouper.ts"() {
|
|
18042
|
+
init_handoff_log();
|
|
18043
|
+
HEURISTIC_WINDOW_MS = 5 * 60 * 1e3;
|
|
18044
|
+
STALL_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
|
|
18045
|
+
CYCLE_COMPLETION_MIN_HOPS = 2;
|
|
18046
|
+
}
|
|
18047
|
+
});
|
|
17633
18048
|
|
|
17634
18049
|
// src/coordination/handoff-routes.ts
|
|
17635
18050
|
function writeJSON6(res, status, payload) {
|
|
@@ -17653,6 +18068,101 @@ function matchEntryRoute2(path) {
|
|
|
17653
18068
|
if (rest.includes("/")) return null;
|
|
17654
18069
|
return { entryId: decodeURIComponent(rest) };
|
|
17655
18070
|
}
|
|
18071
|
+
function matchWorkflowRoute(path) {
|
|
18072
|
+
const prefix = `${COORDINATION_WORKFLOWS_PREFIX}/`;
|
|
18073
|
+
if (!path.startsWith(prefix)) return null;
|
|
18074
|
+
const rest = path.slice(prefix.length);
|
|
18075
|
+
if (rest.length === 0 || rest === "stream") return null;
|
|
18076
|
+
if (rest.includes("/")) return null;
|
|
18077
|
+
return { workflowId: decodeURIComponent(rest) };
|
|
18078
|
+
}
|
|
18079
|
+
async function computeWorkflowsAndTrackTransitions(deps) {
|
|
18080
|
+
const handoffs = await deps.handoffLog.query({ limit: 500 });
|
|
18081
|
+
const workflows = groupHandoffsIntoWorkflows(handoffs, {
|
|
18082
|
+
...deps.now !== void 0 ? { now: deps.now() } : {}
|
|
18083
|
+
});
|
|
18084
|
+
const transitions = deps.workflowStateTracker ? deps.workflowStateTracker.observe(workflows) : [];
|
|
18085
|
+
for (const change of transitions) {
|
|
18086
|
+
deps.auditLog.append(
|
|
18087
|
+
"l2",
|
|
18088
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_STATE_CHANGED,
|
|
18089
|
+
deps.operatorId,
|
|
18090
|
+
{
|
|
18091
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18092
|
+
workflow_id: change.workflow_id,
|
|
18093
|
+
previous_state: change.previous_state,
|
|
18094
|
+
new_state: change.new_state
|
|
18095
|
+
}
|
|
18096
|
+
);
|
|
18097
|
+
}
|
|
18098
|
+
return { workflows, transitions };
|
|
18099
|
+
}
|
|
18100
|
+
function filterWorkflowList(workflows, opts) {
|
|
18101
|
+
let filtered = workflows;
|
|
18102
|
+
if (opts.state) {
|
|
18103
|
+
filtered = filtered.filter((w) => w.state === opts.state);
|
|
18104
|
+
}
|
|
18105
|
+
if (opts.agentId) {
|
|
18106
|
+
filtered = filtered.filter((w) => w.involved_agents.includes(opts.agentId));
|
|
18107
|
+
}
|
|
18108
|
+
if (opts.since) {
|
|
18109
|
+
filtered = filtered.filter((w) => w.last_activity_at >= opts.since);
|
|
18110
|
+
}
|
|
18111
|
+
return filtered.slice(0, opts.limit);
|
|
18112
|
+
}
|
|
18113
|
+
function isWorkflowState(value) {
|
|
18114
|
+
return value === "in_progress" || value === "completed" || value === "stalled" || value === "unknown";
|
|
18115
|
+
}
|
|
18116
|
+
async function handleWorkflowStream(deps, res) {
|
|
18117
|
+
res.writeHead(200, {
|
|
18118
|
+
"Content-Type": "text/event-stream",
|
|
18119
|
+
"Cache-Control": "no-cache, no-transform",
|
|
18120
|
+
Connection: "keep-alive",
|
|
18121
|
+
"X-Accel-Buffering": "no"
|
|
18122
|
+
});
|
|
18123
|
+
const initial = await computeWorkflowsAndTrackTransitions(deps);
|
|
18124
|
+
res.write(
|
|
18125
|
+
`event: workflow_snapshot
|
|
18126
|
+
data: ${JSON.stringify({ workflows: initial.workflows })}
|
|
18127
|
+
|
|
18128
|
+
`
|
|
18129
|
+
);
|
|
18130
|
+
if (initial.transitions.length > 0) {
|
|
18131
|
+
res.write(
|
|
18132
|
+
`event: workflow_state_changed
|
|
18133
|
+
data: ${JSON.stringify({ transitions: initial.transitions })}
|
|
18134
|
+
|
|
18135
|
+
`
|
|
18136
|
+
);
|
|
18137
|
+
}
|
|
18138
|
+
const unsubscribe = deps.events.subscribe(() => {
|
|
18139
|
+
void (async () => {
|
|
18140
|
+
try {
|
|
18141
|
+
const tick = await computeWorkflowsAndTrackTransitions(deps);
|
|
18142
|
+
res.write(
|
|
18143
|
+
`event: workflow_snapshot
|
|
18144
|
+
data: ${JSON.stringify({ workflows: tick.workflows })}
|
|
18145
|
+
|
|
18146
|
+
`
|
|
18147
|
+
);
|
|
18148
|
+
if (tick.transitions.length > 0) {
|
|
18149
|
+
res.write(
|
|
18150
|
+
`event: workflow_state_changed
|
|
18151
|
+
data: ${JSON.stringify({ transitions: tick.transitions })}
|
|
18152
|
+
|
|
18153
|
+
`
|
|
18154
|
+
);
|
|
18155
|
+
}
|
|
18156
|
+
} catch {
|
|
18157
|
+
}
|
|
18158
|
+
})();
|
|
18159
|
+
});
|
|
18160
|
+
const cleanup = () => {
|
|
18161
|
+
unsubscribe();
|
|
18162
|
+
};
|
|
18163
|
+
res.on("close", cleanup);
|
|
18164
|
+
res.on("error", cleanup);
|
|
18165
|
+
}
|
|
17656
18166
|
async function handleStream3(deps, res) {
|
|
17657
18167
|
res.writeHead(200, {
|
|
17658
18168
|
"Content-Type": "text/event-stream",
|
|
@@ -17736,6 +18246,67 @@ async function handleCoordinationRoute(deps, req, res) {
|
|
|
17736
18246
|
writeJSON6(res, 200, { ok: true, data: { entries } });
|
|
17737
18247
|
return true;
|
|
17738
18248
|
}
|
|
18249
|
+
if (method === "GET" && path === `${COORDINATION_WORKFLOWS_PREFIX}/stream`) {
|
|
18250
|
+
await handleWorkflowStream(deps, res);
|
|
18251
|
+
return true;
|
|
18252
|
+
}
|
|
18253
|
+
if (method === "GET" && path === COORDINATION_WORKFLOWS_PREFIX) {
|
|
18254
|
+
const limit = parseLimit4(
|
|
18255
|
+
url.searchParams.get("limit"),
|
|
18256
|
+
COORDINATION_LIST_DEFAULT_LIMIT,
|
|
18257
|
+
COORDINATION_LIST_MAX_LIMIT
|
|
18258
|
+
);
|
|
18259
|
+
const rawState = url.searchParams.get("state");
|
|
18260
|
+
const state = rawState && isWorkflowState(rawState) ? rawState : void 0;
|
|
18261
|
+
const since = url.searchParams.get("since") ?? void 0;
|
|
18262
|
+
const agentId = url.searchParams.get("agent_id") ?? void 0;
|
|
18263
|
+
const computed = await computeWorkflowsAndTrackTransitions(deps);
|
|
18264
|
+
const filtered = filterWorkflowList(computed.workflows, {
|
|
18265
|
+
...state !== void 0 ? { state } : {},
|
|
18266
|
+
...agentId !== void 0 ? { agentId } : {},
|
|
18267
|
+
...since !== void 0 ? { since } : {},
|
|
18268
|
+
limit
|
|
18269
|
+
});
|
|
18270
|
+
deps.auditLog.append(
|
|
18271
|
+
"l2",
|
|
18272
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_VIEW_OPENED,
|
|
18273
|
+
deps.operatorId,
|
|
18274
|
+
{
|
|
18275
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18276
|
+
result_count: filtered.length,
|
|
18277
|
+
...state !== void 0 ? { state } : {},
|
|
18278
|
+
...agentId !== void 0 ? { agent_id: agentId } : {},
|
|
18279
|
+
...since !== void 0 ? { since } : {}
|
|
18280
|
+
}
|
|
18281
|
+
);
|
|
18282
|
+
writeJSON6(res, 200, { ok: true, data: { workflows: filtered } });
|
|
18283
|
+
return true;
|
|
18284
|
+
}
|
|
18285
|
+
const workflowMatch = matchWorkflowRoute(path);
|
|
18286
|
+
if (method === "GET" && workflowMatch) {
|
|
18287
|
+
const computed = await computeWorkflowsAndTrackTransitions(deps);
|
|
18288
|
+
const wf = computed.workflows.find(
|
|
18289
|
+
(w) => w.workflow_id === workflowMatch.workflowId
|
|
18290
|
+
);
|
|
18291
|
+
if (!wf) {
|
|
18292
|
+
writeJSON6(res, 404, { ok: false, error: "not_found" });
|
|
18293
|
+
return true;
|
|
18294
|
+
}
|
|
18295
|
+
deps.auditLog.append(
|
|
18296
|
+
"l2",
|
|
18297
|
+
COORDINATION_VIEW_AUDIT_OPS.WORKFLOW_DRILLED,
|
|
18298
|
+
deps.operatorId,
|
|
18299
|
+
{
|
|
18300
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18301
|
+
workflow_id: wf.workflow_id,
|
|
18302
|
+
state: wf.state,
|
|
18303
|
+
member_count: wf.member_handoffs.length,
|
|
18304
|
+
involved_agent_count: wf.involved_agents.length
|
|
18305
|
+
}
|
|
18306
|
+
);
|
|
18307
|
+
writeJSON6(res, 200, { ok: true, data: { workflow: wf } });
|
|
18308
|
+
return true;
|
|
18309
|
+
}
|
|
17739
18310
|
const entryMatch = matchEntryRoute2(path);
|
|
17740
18311
|
if (method === "GET" && entryMatch) {
|
|
17741
18312
|
const detail = await deps.handoffLog.getEntry(entryMatch.entryId);
|
|
@@ -17755,7 +18326,29 @@ async function handleCoordinationRoute(deps, req, res) {
|
|
|
17755
18326
|
target_agent_id: detail.entry.target_agent_id
|
|
17756
18327
|
}
|
|
17757
18328
|
);
|
|
17758
|
-
|
|
18329
|
+
let breakdown = null;
|
|
18330
|
+
try {
|
|
18331
|
+
breakdown = await extractContextTransferBreakdown(
|
|
18332
|
+
detail,
|
|
18333
|
+
deps.contextTransfer ?? {}
|
|
18334
|
+
);
|
|
18335
|
+
deps.auditLog.append(
|
|
18336
|
+
"l2",
|
|
18337
|
+
CONTEXT_TRANSFER_AUDIT_OPS.DECODED,
|
|
18338
|
+
deps.operatorId,
|
|
18339
|
+
{
|
|
18340
|
+
fortress_id: deps.handoffLog.getFortressId(),
|
|
18341
|
+
entry_id: detail.entry.entry_id,
|
|
18342
|
+
extractor_path: breakdown.source,
|
|
18343
|
+
confidence: breakdown.confidence,
|
|
18344
|
+
transferred_count: breakdown.transferred.length,
|
|
18345
|
+
withheld_count: breakdown.withheld.length
|
|
18346
|
+
}
|
|
18347
|
+
);
|
|
18348
|
+
} catch {
|
|
18349
|
+
}
|
|
18350
|
+
const responseData = breakdown !== null ? { ...detail, context_transfer_breakdown: breakdown } : detail;
|
|
18351
|
+
writeJSON6(res, 200, { ok: true, data: responseData });
|
|
17759
18352
|
return true;
|
|
17760
18353
|
}
|
|
17761
18354
|
writeJSON6(res, 404, { ok: false, error: "not_found", path });
|
|
@@ -17766,13 +18359,16 @@ async function handleCoordinationRoute(deps, req, res) {
|
|
|
17766
18359
|
return true;
|
|
17767
18360
|
}
|
|
17768
18361
|
}
|
|
17769
|
-
var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
|
|
18362
|
+
var COORDINATION_API_PREFIX, COORDINATION_HANDOFFS_PREFIX, COORDINATION_WORKFLOWS_PREFIX, COORDINATION_LIST_DEFAULT_LIMIT, COORDINATION_LIST_MAX_LIMIT, HandoffEventBridge;
|
|
17770
18363
|
var init_handoff_routes = __esm({
|
|
17771
18364
|
"src/coordination/handoff-routes.ts"() {
|
|
17772
18365
|
init_auth_middleware();
|
|
17773
18366
|
init_handoff_log();
|
|
18367
|
+
init_context_transfer_extractor();
|
|
18368
|
+
init_workflow_grouper();
|
|
17774
18369
|
COORDINATION_API_PREFIX = "/api/coordination";
|
|
17775
18370
|
COORDINATION_HANDOFFS_PREFIX = "/api/coordination/handoffs";
|
|
18371
|
+
COORDINATION_WORKFLOWS_PREFIX = "/api/coordination/workflows";
|
|
17776
18372
|
COORDINATION_LIST_DEFAULT_LIMIT = 50;
|
|
17777
18373
|
COORDINATION_LIST_MAX_LIMIT = 500;
|
|
17778
18374
|
HandoffEventBridge = class {
|
|
@@ -17891,6 +18487,8 @@ var init_dashboard = __esm({
|
|
|
17891
18487
|
*/
|
|
17892
18488
|
handoffLog = null;
|
|
17893
18489
|
handoffEventBridge = null;
|
|
18490
|
+
handoffContextTransfer = null;
|
|
18491
|
+
workflowStateTracker = null;
|
|
17894
18492
|
handoffAuditLog = null;
|
|
17895
18493
|
handoffOperatorId = null;
|
|
17896
18494
|
constructor(config) {
|
|
@@ -17971,6 +18569,8 @@ var init_dashboard = __esm({
|
|
|
17971
18569
|
this.handoffEventBridge = opts.eventBridge ?? null;
|
|
17972
18570
|
this.handoffAuditLog = opts.auditLog ?? null;
|
|
17973
18571
|
this.handoffOperatorId = opts.operatorId ?? null;
|
|
18572
|
+
this.handoffContextTransfer = opts.contextTransfer ?? null;
|
|
18573
|
+
this.workflowStateTracker = opts.workflowStateTracker ?? null;
|
|
17974
18574
|
}
|
|
17975
18575
|
/**
|
|
17976
18576
|
* v1.3 WP-V1.3-10 dispatch entry point. Called from `handleRequest`
|
|
@@ -18028,7 +18628,9 @@ var init_dashboard = __esm({
|
|
|
18028
18628
|
handoffLog: this.handoffLog,
|
|
18029
18629
|
auditLog: this.handoffAuditLog,
|
|
18030
18630
|
operatorId: this.handoffOperatorId ?? this.identityManager?.getPrimaryIdentityId() ?? "operator_dashboard",
|
|
18031
|
-
events: this.handoffEventBridge
|
|
18631
|
+
events: this.handoffEventBridge,
|
|
18632
|
+
...this.handoffContextTransfer !== null ? { contextTransfer: this.handoffContextTransfer } : {},
|
|
18633
|
+
...this.workflowStateTracker !== null ? { workflowStateTracker: this.workflowStateTracker } : {}
|
|
18032
18634
|
},
|
|
18033
18635
|
req,
|
|
18034
18636
|
res
|
|
@@ -22312,46 +22914,346 @@ var init_sentinel_dispatcher = __esm({
|
|
|
22312
22914
|
}
|
|
22313
22915
|
});
|
|
22314
22916
|
|
|
22315
|
-
// src/anomaly-detection/
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
AnomalyPipelineDispatcher = class {
|
|
22334
|
-
findingStore;
|
|
22335
|
-
auditLog;
|
|
22917
|
+
// src/anomaly-detection/classifier-state-store.ts
|
|
22918
|
+
function stateKey(classifierId, agentId) {
|
|
22919
|
+
return `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.${agentId}`;
|
|
22920
|
+
}
|
|
22921
|
+
function aadFor(classifierId, agentId) {
|
|
22922
|
+
return `${classifierId}|${agentId}`;
|
|
22923
|
+
}
|
|
22924
|
+
var ANOMALY_CLASSIFIER_STATE_NAMESPACE, ANOMALY_CLASSIFIER_STATE_KEY_PREFIX, HKDF_INFO3, MAX_STATE_BYTES, ClassifierStateStore;
|
|
22925
|
+
var init_classifier_state_store = __esm({
|
|
22926
|
+
"src/anomaly-detection/classifier-state-store.ts"() {
|
|
22927
|
+
init_encryption();
|
|
22928
|
+
init_key_derivation();
|
|
22929
|
+
init_encoding();
|
|
22930
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE = "_anomaly_classifier_state";
|
|
22931
|
+
ANOMALY_CLASSIFIER_STATE_KEY_PREFIX = "state.";
|
|
22932
|
+
HKDF_INFO3 = "l2-anomaly-classifier-state-v1";
|
|
22933
|
+
MAX_STATE_BYTES = 256 * 1024;
|
|
22934
|
+
ClassifierStateStore = class {
|
|
22336
22935
|
storage;
|
|
22337
|
-
|
|
22936
|
+
encryptionKey;
|
|
22338
22937
|
fortressId;
|
|
22339
|
-
identityId;
|
|
22340
22938
|
now;
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
|
|
22344
|
-
|
|
22345
|
-
|
|
22346
|
-
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22939
|
+
constructor(opts) {
|
|
22940
|
+
this.storage = opts.storage;
|
|
22941
|
+
this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO3);
|
|
22942
|
+
this.fortressId = opts.fortressId;
|
|
22943
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
22944
|
+
}
|
|
22945
|
+
async saveState(classifierId, agentId, state) {
|
|
22946
|
+
const persisted = {
|
|
22947
|
+
version: 1,
|
|
22948
|
+
classifier_id: classifierId,
|
|
22949
|
+
agent_id: agentId,
|
|
22950
|
+
fortress_id: this.fortressId,
|
|
22951
|
+
saved_at: this.now().toISOString(),
|
|
22952
|
+
state
|
|
22953
|
+
};
|
|
22954
|
+
const aadString = aadFor(classifierId, agentId);
|
|
22955
|
+
const aad = stringToBytes(aadString);
|
|
22956
|
+
const plaintext = stringToBytes(JSON.stringify(persisted));
|
|
22957
|
+
const envelope = encrypt(plaintext, this.encryptionKey, aad);
|
|
22958
|
+
await this.storage.write(
|
|
22959
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22960
|
+
stateKey(classifierId, agentId),
|
|
22961
|
+
stringToBytes(JSON.stringify(envelope))
|
|
22962
|
+
);
|
|
22963
|
+
}
|
|
22964
|
+
async loadState(classifierId, agentId) {
|
|
22965
|
+
const key = stateKey(classifierId, agentId);
|
|
22966
|
+
let raw;
|
|
22967
|
+
try {
|
|
22968
|
+
raw = await this.storage.read(
|
|
22969
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22970
|
+
key
|
|
22971
|
+
);
|
|
22972
|
+
} catch {
|
|
22973
|
+
return null;
|
|
22974
|
+
}
|
|
22975
|
+
if (!raw) return null;
|
|
22976
|
+
if (raw.length > MAX_STATE_BYTES) return null;
|
|
22977
|
+
try {
|
|
22978
|
+
const aad = stringToBytes(aadFor(classifierId, agentId));
|
|
22979
|
+
const envelope = JSON.parse(bytesToString(raw));
|
|
22980
|
+
const plaintext = decrypt(envelope, this.encryptionKey, aad);
|
|
22981
|
+
const persisted = JSON.parse(
|
|
22982
|
+
bytesToString(plaintext)
|
|
22983
|
+
);
|
|
22984
|
+
if (persisted.version !== 1) return null;
|
|
22985
|
+
if (persisted.classifier_id !== classifierId) return null;
|
|
22986
|
+
if (persisted.agent_id !== agentId) return null;
|
|
22987
|
+
if (persisted.fortress_id !== this.fortressId) return null;
|
|
22988
|
+
return persisted.state;
|
|
22989
|
+
} catch {
|
|
22990
|
+
return null;
|
|
22991
|
+
}
|
|
22992
|
+
}
|
|
22993
|
+
/** Delete a single classifier-agent state record. */
|
|
22994
|
+
async deleteState(classifierId, agentId) {
|
|
22995
|
+
const key = stateKey(classifierId, agentId);
|
|
22996
|
+
const existed = await this.storage.exists(
|
|
22997
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
22998
|
+
key
|
|
22999
|
+
);
|
|
23000
|
+
if (!existed) return false;
|
|
23001
|
+
try {
|
|
23002
|
+
await this.storage.delete(ANOMALY_CLASSIFIER_STATE_NAMESPACE, key);
|
|
23003
|
+
} catch {
|
|
23004
|
+
return false;
|
|
23005
|
+
}
|
|
23006
|
+
return true;
|
|
23007
|
+
}
|
|
23008
|
+
/**
|
|
23009
|
+
* List the (classifier_id, agent_id) tuples currently persisted.
|
|
23010
|
+
* Returns the agent ids for one classifier when classifierId is
|
|
23011
|
+
* given.
|
|
23012
|
+
*/
|
|
23013
|
+
async listAgents(classifierId) {
|
|
23014
|
+
const metas = await this.storage.list(
|
|
23015
|
+
ANOMALY_CLASSIFIER_STATE_NAMESPACE,
|
|
23016
|
+
ANOMALY_CLASSIFIER_STATE_KEY_PREFIX
|
|
23017
|
+
);
|
|
23018
|
+
const prefix = `${ANOMALY_CLASSIFIER_STATE_KEY_PREFIX}${classifierId}.`;
|
|
23019
|
+
const out = [];
|
|
23020
|
+
for (const meta of metas) {
|
|
23021
|
+
if (!meta.key.startsWith(prefix)) continue;
|
|
23022
|
+
out.push(meta.key.slice(prefix.length));
|
|
23023
|
+
}
|
|
23024
|
+
return out;
|
|
23025
|
+
}
|
|
23026
|
+
};
|
|
23027
|
+
}
|
|
23028
|
+
});
|
|
23029
|
+
|
|
23030
|
+
// src/anomaly-detection/classifiers/cusum.ts
|
|
23031
|
+
var CUSUM_CLASSIFIER_ID;
|
|
23032
|
+
var init_cusum = __esm({
|
|
23033
|
+
"src/anomaly-detection/classifiers/cusum.ts"() {
|
|
23034
|
+
init_classifier_state_store();
|
|
23035
|
+
CUSUM_CLASSIFIER_ID = "cusum";
|
|
23036
|
+
}
|
|
23037
|
+
});
|
|
23038
|
+
|
|
23039
|
+
// src/anomaly-detection/classifiers/psi.ts
|
|
23040
|
+
var PSI_CLASSIFIER_ID;
|
|
23041
|
+
var init_psi = __esm({
|
|
23042
|
+
"src/anomaly-detection/classifiers/psi.ts"() {
|
|
23043
|
+
init_classifier_state_store();
|
|
23044
|
+
PSI_CLASSIFIER_ID = "psi";
|
|
23045
|
+
}
|
|
23046
|
+
});
|
|
23047
|
+
|
|
23048
|
+
// src/anomaly-detection/types.ts
|
|
23049
|
+
function severityFromAnomalyScore(score) {
|
|
23050
|
+
if (!Number.isFinite(score)) return null;
|
|
23051
|
+
if (score < 1) return null;
|
|
23052
|
+
if (score < 3) return "info";
|
|
23053
|
+
if (score < 6) return "warn";
|
|
23054
|
+
return "alert";
|
|
23055
|
+
}
|
|
23056
|
+
function buildAnomalyFinding(detector, classifier, vector, prediction, severity) {
|
|
23057
|
+
const summary = formatAnomalySummary(
|
|
23058
|
+
detector,
|
|
23059
|
+
classifier,
|
|
23060
|
+
vector,
|
|
23061
|
+
prediction,
|
|
23062
|
+
severity
|
|
23063
|
+
);
|
|
23064
|
+
return {
|
|
23065
|
+
finding_id: "",
|
|
23066
|
+
sentinel_id: `${ANOMALY_SENTINEL_ID_PREFIX}${detector.detectorId}`,
|
|
23067
|
+
severity,
|
|
23068
|
+
summary,
|
|
23069
|
+
details: {
|
|
23070
|
+
detector_id: detector.detectorId,
|
|
23071
|
+
classifier_id: classifier.classifierId,
|
|
23072
|
+
anomaly_score: prediction.anomaly_score,
|
|
23073
|
+
window_label: vector.window_label,
|
|
23074
|
+
observed_features: vector.features,
|
|
23075
|
+
feature_contributions: prediction.feature_contributions,
|
|
23076
|
+
explanation: prediction.explanation
|
|
23077
|
+
},
|
|
23078
|
+
observed_at: vector.observed_at,
|
|
23079
|
+
agent_id: vector.agent_id,
|
|
23080
|
+
evidence_audit_ids: [],
|
|
23081
|
+
fortress_id: ""
|
|
23082
|
+
};
|
|
23083
|
+
}
|
|
23084
|
+
function formatAnomalySummary(detector, classifier, vector, prediction, severity) {
|
|
23085
|
+
const top = prediction.explanation.slice(0, 3).join("; ");
|
|
23086
|
+
return `${detector.detectorId}/${classifier.classifierId} ${severity}: agent ${vector.agent_id} drifted ${prediction.anomaly_score.toFixed(2)} sigma from baseline. Top contributors: ${top || "(none)"}.`;
|
|
23087
|
+
}
|
|
23088
|
+
var AnomalyDetector, ANOMALY_SENTINEL_ID_PREFIX;
|
|
23089
|
+
var init_types4 = __esm({
|
|
23090
|
+
"src/anomaly-detection/types.ts"() {
|
|
23091
|
+
AnomalyDetector = class {
|
|
23092
|
+
/**
|
|
23093
|
+
* Additional classifiers attached post-construction. Keyed by
|
|
23094
|
+
* classifierId so subscribe/unsubscribe is idempotent. Primary
|
|
23095
|
+
* `classifier` is NOT stored here.
|
|
23096
|
+
*/
|
|
23097
|
+
additionalClassifiers = /* @__PURE__ */ new Map();
|
|
23098
|
+
/**
|
|
23099
|
+
* Attach an additional classifier. Idempotent: a second call with
|
|
23100
|
+
* the same classifierId returns false. The primary classifier
|
|
23101
|
+
* cannot be re-attached as additional (returns false). The
|
|
23102
|
+
* dispatcher emits ANOMALY_CLASSIFIER_SUBSCRIBED on success.
|
|
23103
|
+
*/
|
|
23104
|
+
addClassifier(classifier) {
|
|
23105
|
+
if (classifier.classifierId === this.classifier.classifierId) return false;
|
|
23106
|
+
if (this.additionalClassifiers.has(classifier.classifierId)) return false;
|
|
23107
|
+
this.additionalClassifiers.set(classifier.classifierId, classifier);
|
|
23108
|
+
return true;
|
|
23109
|
+
}
|
|
23110
|
+
/**
|
|
23111
|
+
* Detach an additional classifier by id. Cannot remove the primary
|
|
23112
|
+
* (returns false). Returns true when an existing additional
|
|
23113
|
+
* classifier was removed. The dispatcher emits
|
|
23114
|
+
* ANOMALY_CLASSIFIER_UNSUBSCRIBED on success.
|
|
23115
|
+
*/
|
|
23116
|
+
removeClassifier(classifierId) {
|
|
23117
|
+
if (classifierId === this.classifier.classifierId) return false;
|
|
23118
|
+
return this.additionalClassifiers.delete(classifierId);
|
|
23119
|
+
}
|
|
23120
|
+
/** List every classifier id attached: primary first, then additionals. */
|
|
23121
|
+
listClassifierIds() {
|
|
23122
|
+
return [
|
|
23123
|
+
this.classifier.classifierId,
|
|
23124
|
+
...this.additionalClassifiers.keys()
|
|
23125
|
+
];
|
|
23126
|
+
}
|
|
23127
|
+
/**
|
|
23128
|
+
* Return every attached classifier: primary first, then additionals
|
|
23129
|
+
* in insertion order. Used by evaluate() and the dispatcher's train
|
|
23130
|
+
* + audit emission.
|
|
23131
|
+
*/
|
|
23132
|
+
getAllClassifiers() {
|
|
23133
|
+
return [this.classifier, ...this.additionalClassifiers.values()];
|
|
23134
|
+
}
|
|
23135
|
+
/**
|
|
23136
|
+
* Bind the detector to a fortress context. Default stores it on
|
|
23137
|
+
* `this`; subclasses with priming logic override.
|
|
23138
|
+
*/
|
|
23139
|
+
async subscribe(context) {
|
|
23140
|
+
this.context = context;
|
|
23141
|
+
}
|
|
23142
|
+
async unsubscribe() {
|
|
23143
|
+
this.context = void 0;
|
|
23144
|
+
this.additionalClassifiers.clear();
|
|
23145
|
+
}
|
|
23146
|
+
/**
|
|
23147
|
+
* One evaluation pass. Default impl: extract -> for each classifier
|
|
23148
|
+
* attached, predict (drift against that classifier's prior
|
|
23149
|
+
* baseline) -> observe (only when the prediction is in-baseline,
|
|
23150
|
+
* so outliers do not contaminate the rolling baseline and pull
|
|
23151
|
+
* future predictions toward themselves) -> emit findings above
|
|
23152
|
+
* threshold. Multi-classifier evaluation is per-classifier: each
|
|
23153
|
+
* decides independently whether to absorb or emit. Subclasses with
|
|
23154
|
+
* custom routing override.
|
|
23155
|
+
*
|
|
23156
|
+
* Predict-then-observe (with conditional observe) is the standard
|
|
23157
|
+
* online anomaly-detection pattern. Chi-1 spawn prompt called for
|
|
23158
|
+
* observe-then-predict; CTO call: changed to predict-then-observe
|
|
23159
|
+
* because observe-then-predict measures the sample against itself
|
|
23160
|
+
* after one-sample contamination, which is structurally incorrect
|
|
23161
|
+
* for drift detection. Chi-2 preserves that invariant on a per-
|
|
23162
|
+
* classifier basis (each classifier's observe is conditional on its
|
|
23163
|
+
* own predict result).
|
|
23164
|
+
*/
|
|
23165
|
+
async evaluate() {
|
|
23166
|
+
const ctx = this.requireContext();
|
|
23167
|
+
const vectors = await this.featureExtract(ctx);
|
|
23168
|
+
const findings = [];
|
|
23169
|
+
const classifiers = this.getAllClassifiers();
|
|
23170
|
+
for (const vector of vectors) {
|
|
23171
|
+
for (const classifier of classifiers) {
|
|
23172
|
+
const prediction = await classifier.predict(vector);
|
|
23173
|
+
if (!prediction.baseline_ready) {
|
|
23174
|
+
await classifier.observe(vector);
|
|
23175
|
+
continue;
|
|
23176
|
+
}
|
|
23177
|
+
const severity = severityFromAnomalyScore(prediction.anomaly_score);
|
|
23178
|
+
if (severity === null) {
|
|
23179
|
+
await classifier.observe(vector);
|
|
23180
|
+
continue;
|
|
23181
|
+
}
|
|
23182
|
+
findings.push(
|
|
23183
|
+
buildAnomalyFinding(this, classifier, vector, prediction, severity)
|
|
23184
|
+
);
|
|
23185
|
+
}
|
|
23186
|
+
}
|
|
23187
|
+
return findings;
|
|
23188
|
+
}
|
|
23189
|
+
context;
|
|
23190
|
+
requireContext() {
|
|
23191
|
+
if (!this.context) {
|
|
23192
|
+
throw new Error(
|
|
23193
|
+
`anomaly-detector ${this.detectorId}: evaluate() called before subscribe()`
|
|
23194
|
+
);
|
|
23195
|
+
}
|
|
23196
|
+
return this.context;
|
|
23197
|
+
}
|
|
23198
|
+
};
|
|
23199
|
+
ANOMALY_SENTINEL_ID_PREFIX = "anomaly:";
|
|
23200
|
+
}
|
|
23201
|
+
});
|
|
23202
|
+
function classifierSpecificAuditOp(classifierId) {
|
|
23203
|
+
if (classifierId === null) return null;
|
|
23204
|
+
if (classifierId === CUSUM_CLASSIFIER_ID) {
|
|
23205
|
+
return ANOMALY_AUDIT_OPS.CUSUM_DRIFT_DETECTED;
|
|
23206
|
+
}
|
|
23207
|
+
if (classifierId === PSI_CLASSIFIER_ID) {
|
|
23208
|
+
return ANOMALY_AUDIT_OPS.PSI_DISTRIBUTION_SHIFT_DETECTED;
|
|
23209
|
+
}
|
|
23210
|
+
return null;
|
|
23211
|
+
}
|
|
23212
|
+
var ANOMALY_AUDIT_OPS, DEFAULT_TICK_INTERVAL_MS2, AnomalyPipelineDispatcher;
|
|
23213
|
+
var init_anomaly_pipeline = __esm({
|
|
23214
|
+
"src/anomaly-detection/anomaly-pipeline.ts"() {
|
|
23215
|
+
init_cusum();
|
|
23216
|
+
init_psi();
|
|
23217
|
+
init_types4();
|
|
23218
|
+
ANOMALY_AUDIT_OPS = {
|
|
23219
|
+
DETECTOR_REGISTERED: "anomaly_detector_registered",
|
|
23220
|
+
DETECTOR_UNREGISTERED: "anomaly_detector_unregistered",
|
|
23221
|
+
FINDING_EMITTED: "anomaly_finding_emitted",
|
|
23222
|
+
EVALUATION_FAILED: "anomaly_evaluation_failed",
|
|
23223
|
+
TRAINING_COMPLETED: "anomaly_training_completed",
|
|
23224
|
+
TRAINING_FAILED: "anomaly_training_failed",
|
|
23225
|
+
/** Chi-2: a classifier was attached to an existing detector. */
|
|
23226
|
+
CLASSIFIER_SUBSCRIBED: "anomaly_classifier_subscribed",
|
|
23227
|
+
/** Chi-2: a classifier was detached from an existing detector. */
|
|
23228
|
+
CLASSIFIER_UNSUBSCRIBED: "anomaly_classifier_unsubscribed",
|
|
23229
|
+
/** Chi-2: CUSUM-flagged mean-shift drift on a per-agent feature. */
|
|
23230
|
+
CUSUM_DRIFT_DETECTED: "anomaly_cusum_drift_detected",
|
|
23231
|
+
/** Chi-2: PSI-flagged distribution shift on a per-agent feature. */
|
|
23232
|
+
PSI_DISTRIBUTION_SHIFT_DETECTED: "anomaly_psi_distribution_shift_detected"
|
|
23233
|
+
};
|
|
23234
|
+
DEFAULT_TICK_INTERVAL_MS2 = 6e4;
|
|
23235
|
+
AnomalyPipelineDispatcher = class {
|
|
23236
|
+
findingStore;
|
|
23237
|
+
auditLog;
|
|
23238
|
+
storage;
|
|
23239
|
+
masterKey;
|
|
23240
|
+
fortressId;
|
|
23241
|
+
identityId;
|
|
23242
|
+
now;
|
|
23243
|
+
tickIntervalMs;
|
|
23244
|
+
detectors = /* @__PURE__ */ new Map();
|
|
23245
|
+
listeners = /* @__PURE__ */ new Set();
|
|
23246
|
+
tickTimer = null;
|
|
23247
|
+
tickInFlight = false;
|
|
23248
|
+
constructor(deps) {
|
|
23249
|
+
this.findingStore = deps.findingStore;
|
|
23250
|
+
this.auditLog = deps.auditLog;
|
|
23251
|
+
this.storage = deps.storage;
|
|
23252
|
+
this.masterKey = deps.masterKey;
|
|
23253
|
+
this.fortressId = deps.fortressId;
|
|
23254
|
+
this.identityId = deps.identityId;
|
|
23255
|
+
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
23256
|
+
this.tickIntervalMs = deps.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS2;
|
|
22355
23257
|
}
|
|
22356
23258
|
onEvent(listener) {
|
|
22357
23259
|
this.listeners.add(listener);
|
|
@@ -22418,34 +23320,37 @@ var init_anomaly_pipeline = __esm({
|
|
|
22418
23320
|
const stamped = await this.routeFinding(detectorId, raw);
|
|
22419
23321
|
findings.push(stamped);
|
|
22420
23322
|
}
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
|
|
22440
|
-
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
23323
|
+
for (const classifier of detector.getAllClassifiers()) {
|
|
23324
|
+
try {
|
|
23325
|
+
const trainingResult = await classifier.train();
|
|
23326
|
+
this.auditLog.append(
|
|
23327
|
+
"l2",
|
|
23328
|
+
ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
|
|
23329
|
+
this.identityId,
|
|
23330
|
+
{
|
|
23331
|
+
detector_id: detectorId,
|
|
23332
|
+
classifier_id: classifier.classifierId,
|
|
23333
|
+
trained_at: trainingResult.trained_at,
|
|
23334
|
+
sample_count: trainingResult.sample_count,
|
|
23335
|
+
agent_count: trainingResult.agent_count,
|
|
23336
|
+
fortress_id: this.fortressId
|
|
23337
|
+
}
|
|
23338
|
+
);
|
|
23339
|
+
} catch (trainErr) {
|
|
23340
|
+
const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
|
|
23341
|
+
this.auditLog.append(
|
|
23342
|
+
"l2",
|
|
23343
|
+
ANOMALY_AUDIT_OPS.TRAINING_FAILED,
|
|
23344
|
+
this.identityId,
|
|
23345
|
+
{
|
|
23346
|
+
detector_id: detectorId,
|
|
23347
|
+
classifier_id: classifier.classifierId,
|
|
23348
|
+
error_message: message,
|
|
23349
|
+
fortress_id: this.fortressId
|
|
23350
|
+
},
|
|
23351
|
+
"failure"
|
|
23352
|
+
);
|
|
23353
|
+
}
|
|
22449
23354
|
}
|
|
22450
23355
|
} catch (err) {
|
|
22451
23356
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -22508,6 +23413,7 @@ var init_anomaly_pipeline = __esm({
|
|
|
22508
23413
|
observed_at: raw.observed_at || this.now().toISOString()
|
|
22509
23414
|
};
|
|
22510
23415
|
await this.findingStore.saveFinding(stamped);
|
|
23416
|
+
const classifierId = stamped.details["classifier_id"] ?? null;
|
|
22511
23417
|
this.auditLog.append(
|
|
22512
23418
|
"l2",
|
|
22513
23419
|
ANOMALY_AUDIT_OPS.FINDING_EMITTED,
|
|
@@ -22517,13 +23423,79 @@ var init_anomaly_pipeline = __esm({
|
|
|
22517
23423
|
finding_id: stamped.finding_id,
|
|
22518
23424
|
severity: stamped.severity,
|
|
22519
23425
|
anomaly_score: stamped.details["anomaly_score"] ?? null,
|
|
23426
|
+
...classifierId !== null ? { classifier_id: classifierId } : {},
|
|
22520
23427
|
...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
|
|
22521
23428
|
fortress_id: this.fortressId
|
|
22522
23429
|
}
|
|
22523
23430
|
);
|
|
23431
|
+
const specificOp = classifierSpecificAuditOp(classifierId);
|
|
23432
|
+
if (specificOp !== null) {
|
|
23433
|
+
this.auditLog.append("l2", specificOp, this.identityId, {
|
|
23434
|
+
detector_id: detectorId,
|
|
23435
|
+
finding_id: stamped.finding_id,
|
|
23436
|
+
severity: stamped.severity,
|
|
23437
|
+
anomaly_score: stamped.details["anomaly_score"] ?? null,
|
|
23438
|
+
...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
|
|
23439
|
+
fortress_id: this.fortressId
|
|
23440
|
+
});
|
|
23441
|
+
}
|
|
22524
23442
|
this.emit({ type: "finding", finding: stamped });
|
|
22525
23443
|
return stamped;
|
|
22526
23444
|
}
|
|
23445
|
+
/**
|
|
23446
|
+
* Chi-2: attach an additional classifier to an already-registered
|
|
23447
|
+
* detector. Emits ANOMALY_CLASSIFIER_SUBSCRIBED on success. The
|
|
23448
|
+
* factory is called with the fortress AnomalyContext so the
|
|
23449
|
+
* classifier can build its own state-store binding. Idempotent: a
|
|
23450
|
+
* second call with the same classifierId returns false.
|
|
23451
|
+
*/
|
|
23452
|
+
async addClassifierToDetector(detectorId, factory) {
|
|
23453
|
+
const detector = this.detectors.get(detectorId);
|
|
23454
|
+
if (!detector) return false;
|
|
23455
|
+
const context = {
|
|
23456
|
+
fortressId: this.fortressId,
|
|
23457
|
+
auditLog: this.auditLog,
|
|
23458
|
+
storage: this.storage,
|
|
23459
|
+
masterKey: this.masterKey,
|
|
23460
|
+
now: this.now
|
|
23461
|
+
};
|
|
23462
|
+
const classifier = factory(context);
|
|
23463
|
+
const added = detector.addClassifier(classifier);
|
|
23464
|
+
if (!added) return false;
|
|
23465
|
+
this.auditLog.append(
|
|
23466
|
+
"l2",
|
|
23467
|
+
ANOMALY_AUDIT_OPS.CLASSIFIER_SUBSCRIBED,
|
|
23468
|
+
this.identityId,
|
|
23469
|
+
{
|
|
23470
|
+
detector_id: detectorId,
|
|
23471
|
+
classifier_id: classifier.classifierId,
|
|
23472
|
+
fortress_id: this.fortressId
|
|
23473
|
+
}
|
|
23474
|
+
);
|
|
23475
|
+
return true;
|
|
23476
|
+
}
|
|
23477
|
+
/**
|
|
23478
|
+
* Chi-2: detach an additional classifier from an already-registered
|
|
23479
|
+
* detector. Emits ANOMALY_CLASSIFIER_UNSUBSCRIBED on success. The
|
|
23480
|
+
* primary classifier cannot be detached (returns false).
|
|
23481
|
+
*/
|
|
23482
|
+
async removeClassifierFromDetector(detectorId, classifierId) {
|
|
23483
|
+
const detector = this.detectors.get(detectorId);
|
|
23484
|
+
if (!detector) return false;
|
|
23485
|
+
const removed = detector.removeClassifier(classifierId);
|
|
23486
|
+
if (!removed) return false;
|
|
23487
|
+
this.auditLog.append(
|
|
23488
|
+
"l2",
|
|
23489
|
+
ANOMALY_AUDIT_OPS.CLASSIFIER_UNSUBSCRIBED,
|
|
23490
|
+
this.identityId,
|
|
23491
|
+
{
|
|
23492
|
+
detector_id: detectorId,
|
|
23493
|
+
classifier_id: classifierId,
|
|
23494
|
+
fortress_id: this.fortressId
|
|
23495
|
+
}
|
|
23496
|
+
);
|
|
23497
|
+
return true;
|
|
23498
|
+
}
|
|
22527
23499
|
emit(event) {
|
|
22528
23500
|
for (const listener of this.listeners) {
|
|
22529
23501
|
try {
|
|
@@ -22536,6 +23508,75 @@ var init_anomaly_pipeline = __esm({
|
|
|
22536
23508
|
}
|
|
22537
23509
|
});
|
|
22538
23510
|
|
|
23511
|
+
// src/coordination/workflow-state-tracker.ts
|
|
23512
|
+
var WorkflowStateTracker;
|
|
23513
|
+
var init_workflow_state_tracker = __esm({
|
|
23514
|
+
"src/coordination/workflow-state-tracker.ts"() {
|
|
23515
|
+
WorkflowStateTracker = class {
|
|
23516
|
+
states = /* @__PURE__ */ new Map();
|
|
23517
|
+
now;
|
|
23518
|
+
constructor(opts) {
|
|
23519
|
+
this.now = opts?.now ?? (() => /* @__PURE__ */ new Date());
|
|
23520
|
+
}
|
|
23521
|
+
/**
|
|
23522
|
+
* Diff the supplied workflow list against the last-observed states.
|
|
23523
|
+
* Returns the set of transitions detected this call; the tracker
|
|
23524
|
+
* mutates its internal map to reflect the new states.
|
|
23525
|
+
*
|
|
23526
|
+
* Transitions emitted:
|
|
23527
|
+
* - First observation of a workflow (`previous_state` is the
|
|
23528
|
+
* sentinel `unobserved`). Lets the route handler audit-emit
|
|
23529
|
+
* the initial state so the operator sees workflows as they
|
|
23530
|
+
* surface, not only when they change.
|
|
23531
|
+
* - Subsequent observation where `previous_state !== new_state`.
|
|
23532
|
+
*/
|
|
23533
|
+
observe(workflows) {
|
|
23534
|
+
const out = [];
|
|
23535
|
+
const observedAt = this.now().toISOString();
|
|
23536
|
+
for (const wf of workflows) {
|
|
23537
|
+
const prior = this.states.get(wf.workflow_id);
|
|
23538
|
+
if (prior === void 0) {
|
|
23539
|
+
out.push({
|
|
23540
|
+
workflow_id: wf.workflow_id,
|
|
23541
|
+
previous_state: "unobserved",
|
|
23542
|
+
new_state: wf.state,
|
|
23543
|
+
observed_at: observedAt
|
|
23544
|
+
});
|
|
23545
|
+
this.states.set(wf.workflow_id, wf.state);
|
|
23546
|
+
continue;
|
|
23547
|
+
}
|
|
23548
|
+
if (prior !== wf.state) {
|
|
23549
|
+
out.push({
|
|
23550
|
+
workflow_id: wf.workflow_id,
|
|
23551
|
+
previous_state: prior,
|
|
23552
|
+
new_state: wf.state,
|
|
23553
|
+
observed_at: observedAt
|
|
23554
|
+
});
|
|
23555
|
+
this.states.set(wf.workflow_id, wf.state);
|
|
23556
|
+
}
|
|
23557
|
+
}
|
|
23558
|
+
return out;
|
|
23559
|
+
}
|
|
23560
|
+
/**
|
|
23561
|
+
* Drop a workflow's recorded state. Surfaced for tests + future
|
|
23562
|
+
* "operator dismissed this workflow" affordance; not currently
|
|
23563
|
+
* called by the production wiring.
|
|
23564
|
+
*/
|
|
23565
|
+
forget(workflowId) {
|
|
23566
|
+
this.states.delete(workflowId);
|
|
23567
|
+
}
|
|
23568
|
+
/** Reset the tracker. Tests use this between runs. */
|
|
23569
|
+
reset() {
|
|
23570
|
+
this.states.clear();
|
|
23571
|
+
}
|
|
23572
|
+
/** Read-only view of the current snapshot. Useful for diagnostics. */
|
|
23573
|
+
snapshot() {
|
|
23574
|
+
return new Map(this.states);
|
|
23575
|
+
}
|
|
23576
|
+
};
|
|
23577
|
+
}
|
|
23578
|
+
});
|
|
23579
|
+
|
|
22539
23580
|
// src/sentinel/sentinel.ts
|
|
22540
23581
|
var Sentinel;
|
|
22541
23582
|
var init_sentinel = __esm({
|
|
@@ -38247,7 +39288,7 @@ ${runningLines.join("\n")}`;
|
|
|
38247
39288
|
function chatStorageKey(surface, threadKey) {
|
|
38248
39289
|
return `${surface}.${threadKey}`;
|
|
38249
39290
|
}
|
|
38250
|
-
var OPERATOR_CHAT_NAMESPACE,
|
|
39291
|
+
var OPERATOR_CHAT_NAMESPACE, HKDF_INFO4, OperatorChatStore;
|
|
38251
39292
|
var init_operator_chat_store = __esm({
|
|
38252
39293
|
"src/chat/operator-chat-store.ts"() {
|
|
38253
39294
|
init_encryption();
|
|
@@ -38255,13 +39296,13 @@ var init_operator_chat_store = __esm({
|
|
|
38255
39296
|
init_encoding();
|
|
38256
39297
|
init_operator_chat_types();
|
|
38257
39298
|
OPERATOR_CHAT_NAMESPACE = "_chat";
|
|
38258
|
-
|
|
39299
|
+
HKDF_INFO4 = "operator-chat-store-v1";
|
|
38259
39300
|
OperatorChatStore = class {
|
|
38260
39301
|
storage;
|
|
38261
39302
|
encryptionKey;
|
|
38262
39303
|
constructor(storage, masterKey) {
|
|
38263
39304
|
this.storage = storage;
|
|
38264
|
-
this.encryptionKey = derivePurposeKey(masterKey,
|
|
39305
|
+
this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO4);
|
|
38265
39306
|
}
|
|
38266
39307
|
/**
|
|
38267
39308
|
* Load a thread. Returns null if no record exists or if the on-disk
|
|
@@ -38357,7 +39398,7 @@ function lastTurnId(bundle) {
|
|
|
38357
39398
|
}
|
|
38358
39399
|
return max;
|
|
38359
39400
|
}
|
|
38360
|
-
var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX,
|
|
39401
|
+
var CONCIERGE_MEMORY_NAMESPACE, CONCIERGE_MEMORY_KEY_PREFIX, HKDF_INFO5, DEFAULT_CONCIERGE_RETENTION_DAYS, MAX_BUNDLE_BYTES3, ConciergeMemoryStore;
|
|
38361
39402
|
var init_concierge_memory_store = __esm({
|
|
38362
39403
|
"src/chat/concierge-memory-store.ts"() {
|
|
38363
39404
|
init_encryption();
|
|
@@ -38365,7 +39406,7 @@ var init_concierge_memory_store = __esm({
|
|
|
38365
39406
|
init_encoding();
|
|
38366
39407
|
CONCIERGE_MEMORY_NAMESPACE = "_chat";
|
|
38367
39408
|
CONCIERGE_MEMORY_KEY_PREFIX = "concierge_memory.";
|
|
38368
|
-
|
|
39409
|
+
HKDF_INFO5 = "concierge-memory-store-v1";
|
|
38369
39410
|
DEFAULT_CONCIERGE_RETENTION_DAYS = 30;
|
|
38370
39411
|
MAX_BUNDLE_BYTES3 = 4 * 1024 * 1024;
|
|
38371
39412
|
ConciergeMemoryStore = class {
|
|
@@ -38376,7 +39417,7 @@ var init_concierge_memory_store = __esm({
|
|
|
38376
39417
|
locks;
|
|
38377
39418
|
constructor(opts) {
|
|
38378
39419
|
this.storage = opts.storage;
|
|
38379
|
-
this.encryptionKey = derivePurposeKey(opts.masterKey,
|
|
39420
|
+
this.encryptionKey = derivePurposeKey(opts.masterKey, HKDF_INFO5);
|
|
38380
39421
|
this.fortressId = opts.fortressId;
|
|
38381
39422
|
this.retentionDays = opts.retentionDays !== void 0 && opts.retentionDays > 0 ? opts.retentionDays : DEFAULT_CONCIERGE_RETENTION_DAYS;
|
|
38382
39423
|
this.locks = /* @__PURE__ */ new Map();
|
|
@@ -39025,7 +40066,7 @@ var init_defaults = __esm({
|
|
|
39025
40066
|
});
|
|
39026
40067
|
|
|
39027
40068
|
// src/intelligence/policy-store.ts
|
|
39028
|
-
var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY,
|
|
40069
|
+
var INTELLIGENCE_NAMESPACE, SUBSTRATE_CONFIG_KEY, HKDF_INFO6, IntelligenceConfigStore;
|
|
39029
40070
|
var init_policy_store = __esm({
|
|
39030
40071
|
"src/intelligence/policy-store.ts"() {
|
|
39031
40072
|
init_encryption();
|
|
@@ -39034,13 +40075,13 @@ var init_policy_store = __esm({
|
|
|
39034
40075
|
init_defaults();
|
|
39035
40076
|
INTELLIGENCE_NAMESPACE = "_intelligence";
|
|
39036
40077
|
SUBSTRATE_CONFIG_KEY = "substrate-config";
|
|
39037
|
-
|
|
40078
|
+
HKDF_INFO6 = "intelligence-substrate-config";
|
|
39038
40079
|
IntelligenceConfigStore = class {
|
|
39039
40080
|
storage;
|
|
39040
40081
|
encryptionKey;
|
|
39041
40082
|
constructor(storage, masterKey) {
|
|
39042
40083
|
this.storage = storage;
|
|
39043
|
-
this.encryptionKey = derivePurposeKey(masterKey,
|
|
40084
|
+
this.encryptionKey = derivePurposeKey(masterKey, HKDF_INFO6);
|
|
39044
40085
|
}
|
|
39045
40086
|
/**
|
|
39046
40087
|
* Load the operator's substrate config from disk. Returns the config
|
|
@@ -39833,17 +40874,152 @@ ${redactedItems.map((r) => `- ${r.redacted}`).join("\n")}`,
|
|
|
39833
40874
|
}
|
|
39834
40875
|
});
|
|
39835
40876
|
|
|
39836
|
-
// src/
|
|
39837
|
-
function
|
|
39838
|
-
|
|
39839
|
-
|
|
40877
|
+
// src/query-anonymity/header-strip.ts
|
|
40878
|
+
function stripHeaders(headers) {
|
|
40879
|
+
const stripped = {};
|
|
40880
|
+
const removed = [];
|
|
40881
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
40882
|
+
const lower = name.toLowerCase();
|
|
40883
|
+
if (REQUIRED_HEADER_SET.has(lower)) {
|
|
40884
|
+
stripped[name] = value;
|
|
40885
|
+
continue;
|
|
40886
|
+
}
|
|
40887
|
+
const reason = STRIP_REASON_BY_NAME.get(lower);
|
|
40888
|
+
if (reason !== void 0) {
|
|
40889
|
+
removed.push({ name, reason });
|
|
40890
|
+
continue;
|
|
40891
|
+
}
|
|
40892
|
+
stripped[name] = value;
|
|
40893
|
+
}
|
|
40894
|
+
return { stripped, removed };
|
|
39840
40895
|
}
|
|
39841
|
-
function
|
|
39842
|
-
|
|
39843
|
-
"
|
|
39844
|
-
|
|
39845
|
-
|
|
39846
|
-
"
|
|
40896
|
+
function defeatUndiciDefaultsInto(headers) {
|
|
40897
|
+
if (headers["user-agent"] === void 0 && headers["User-Agent"] === void 0) {
|
|
40898
|
+
headers["User-Agent"] = "";
|
|
40899
|
+
}
|
|
40900
|
+
if (headers["accept-language"] === void 0 && headers["Accept-Language"] === void 0) {
|
|
40901
|
+
headers["Accept-Language"] = "";
|
|
40902
|
+
}
|
|
40903
|
+
return headers;
|
|
40904
|
+
}
|
|
40905
|
+
function createAnonymizedFetch(baseFetch, onAudit) {
|
|
40906
|
+
const wrapped = async (input, init) => {
|
|
40907
|
+
const headers = normalizeHeadersInit(init?.headers);
|
|
40908
|
+
const result = stripHeaders(headers);
|
|
40909
|
+
defeatUndiciDefaultsInto(result.stripped);
|
|
40910
|
+
const preservedRequired = Object.keys(result.stripped).filter(
|
|
40911
|
+
(k) => REQUIRED_HEADER_SET.has(k.toLowerCase())
|
|
40912
|
+
);
|
|
40913
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
40914
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
40915
|
+
if (onAudit) {
|
|
40916
|
+
onAudit({
|
|
40917
|
+
url,
|
|
40918
|
+
method,
|
|
40919
|
+
stripped_count: result.removed.length,
|
|
40920
|
+
removed: result.removed,
|
|
40921
|
+
required_preserved: preservedRequired
|
|
40922
|
+
});
|
|
40923
|
+
}
|
|
40924
|
+
return baseFetch(input, { ...init, headers: result.stripped });
|
|
40925
|
+
};
|
|
40926
|
+
return wrapped;
|
|
40927
|
+
}
|
|
40928
|
+
function normalizeHeadersInit(raw) {
|
|
40929
|
+
if (raw === void 0) return {};
|
|
40930
|
+
if (typeof Headers !== "undefined" && raw instanceof Headers) {
|
|
40931
|
+
const out = {};
|
|
40932
|
+
raw.forEach((value, key) => {
|
|
40933
|
+
out[key] = value;
|
|
40934
|
+
});
|
|
40935
|
+
return out;
|
|
40936
|
+
}
|
|
40937
|
+
if (Array.isArray(raw)) {
|
|
40938
|
+
const out = {};
|
|
40939
|
+
for (const [k, v] of raw) {
|
|
40940
|
+
if (k !== void 0 && v !== void 0) out[k] = v;
|
|
40941
|
+
}
|
|
40942
|
+
return out;
|
|
40943
|
+
}
|
|
40944
|
+
return { ...raw };
|
|
40945
|
+
}
|
|
40946
|
+
var QUERY_ANONYMITY_AUDIT_OPS, CANONICAL_STRIP_LIST, REQUIRED_HEADERS, REQUIRED_HEADER_SET, STRIP_REASON_BY_NAME;
|
|
40947
|
+
var init_header_strip = __esm({
|
|
40948
|
+
"src/query-anonymity/header-strip.ts"() {
|
|
40949
|
+
QUERY_ANONYMITY_AUDIT_OPS = {
|
|
40950
|
+
HEADERS_STRIPPED: "query_anonymity_headers_stripped"
|
|
40951
|
+
};
|
|
40952
|
+
CANONICAL_STRIP_LIST = [
|
|
40953
|
+
// Browser / runtime fingerprinting.
|
|
40954
|
+
{ name: "user-agent", reason: "user-agent" },
|
|
40955
|
+
{ name: "sec-ch-ua", reason: "fingerprintable-extension" },
|
|
40956
|
+
{ name: "sec-ch-ua-mobile", reason: "fingerprintable-extension" },
|
|
40957
|
+
{ name: "sec-ch-ua-platform", reason: "fingerprintable-extension" },
|
|
40958
|
+
{ name: "sec-ch-ua-platform-version", reason: "fingerprintable-extension" },
|
|
40959
|
+
{ name: "sec-ch-ua-arch", reason: "fingerprintable-extension" },
|
|
40960
|
+
{ name: "sec-ch-ua-bitness", reason: "fingerprintable-extension" },
|
|
40961
|
+
{ name: "sec-ch-ua-model", reason: "fingerprintable-extension" },
|
|
40962
|
+
{ name: "sec-ch-ua-full-version-list", reason: "fingerprintable-extension" },
|
|
40963
|
+
// Locale fingerprint.
|
|
40964
|
+
{ name: "accept-language", reason: "locale-fingerprint" },
|
|
40965
|
+
// Request-origin leak.
|
|
40966
|
+
{ name: "referer", reason: "leaking-network-info" },
|
|
40967
|
+
{ name: "referrer-policy", reason: "leaking-network-info" },
|
|
40968
|
+
{ name: "origin", reason: "leaking-network-info" },
|
|
40969
|
+
// Forwarded-by / IP-derived network info.
|
|
40970
|
+
{ name: "via", reason: "leaking-network-info" },
|
|
40971
|
+
{ name: "forwarded", reason: "leaking-network-info" },
|
|
40972
|
+
{ name: "x-forwarded-for", reason: "leaking-network-info" },
|
|
40973
|
+
{ name: "x-real-ip", reason: "leaking-network-info" },
|
|
40974
|
+
{ name: "x-client-ip", reason: "leaking-network-info" },
|
|
40975
|
+
// DNT / GPC are technically anti-tracking signals but they
|
|
40976
|
+
// themselves form a fingerprint (operators who set DNT=1 are a
|
|
40977
|
+
// smaller subset). Strip to keep the substrate ignorant of
|
|
40978
|
+
// operator preferences.
|
|
40979
|
+
{ name: "dnt", reason: "unnecessary-metadata" },
|
|
40980
|
+
{ name: "sec-gpc", reason: "unnecessary-metadata" }
|
|
40981
|
+
];
|
|
40982
|
+
REQUIRED_HEADERS = [
|
|
40983
|
+
"authorization",
|
|
40984
|
+
"content-type",
|
|
40985
|
+
"content-length",
|
|
40986
|
+
"host",
|
|
40987
|
+
"accept",
|
|
40988
|
+
"x-api-key",
|
|
40989
|
+
// Anthropic API auth
|
|
40990
|
+
"anthropic-version",
|
|
40991
|
+
// Anthropic API contract version
|
|
40992
|
+
"anthropic-beta",
|
|
40993
|
+
// optional Anthropic beta opt-in
|
|
40994
|
+
"openai-organization",
|
|
40995
|
+
// optional OpenAI org id
|
|
40996
|
+
"x-stainless-package-version",
|
|
40997
|
+
// allowed for Anthropic + OpenAI SDK contract compat
|
|
40998
|
+
"x-goog-api-key",
|
|
40999
|
+
// Google AI Studio
|
|
41000
|
+
"x-goog-user-project"
|
|
41001
|
+
// Google AI Studio
|
|
41002
|
+
];
|
|
41003
|
+
REQUIRED_HEADER_SET = new Set(
|
|
41004
|
+
REQUIRED_HEADERS.map((h) => h.toLowerCase())
|
|
41005
|
+
);
|
|
41006
|
+
STRIP_REASON_BY_NAME = new Map(
|
|
41007
|
+
CANONICAL_STRIP_LIST.map((h) => [h.name.toLowerCase(), h.reason])
|
|
41008
|
+
);
|
|
41009
|
+
}
|
|
41010
|
+
});
|
|
41011
|
+
|
|
41012
|
+
// src/intelligence/substrates/hybrid/per-surface-router.ts
|
|
41013
|
+
function resolveHybridChoice(rules, surface) {
|
|
41014
|
+
if (!rules) return null;
|
|
41015
|
+
return rules.perSurface[surface] ?? null;
|
|
41016
|
+
}
|
|
41017
|
+
function validateHybridRules(rules) {
|
|
41018
|
+
const surfaces = [
|
|
41019
|
+
"concierge",
|
|
41020
|
+
"direct-agent-gate-advisor",
|
|
41021
|
+
"sentinel-scoring",
|
|
41022
|
+
"gate-explanation",
|
|
39847
41023
|
"privacy-filter-tier-2",
|
|
39848
41024
|
"template-suggestion"
|
|
39849
41025
|
];
|
|
@@ -39954,6 +41130,7 @@ var init_selector = __esm({
|
|
|
39954
41130
|
init_local();
|
|
39955
41131
|
init_venice();
|
|
39956
41132
|
init_frontier();
|
|
41133
|
+
init_header_strip();
|
|
39957
41134
|
init_per_surface_router();
|
|
39958
41135
|
DISABLED_CAPABILITY = {
|
|
39959
41136
|
summarize: false,
|
|
@@ -39988,7 +41165,21 @@ var init_selector = __esm({
|
|
|
39988
41165
|
this.auditLog = cfg.auditLog;
|
|
39989
41166
|
this.identityId = cfg.identityId;
|
|
39990
41167
|
this.redactor = cfg.redactor ?? IDENTITY_REDACTOR;
|
|
39991
|
-
|
|
41168
|
+
const baseFetch = cfg.fetchImpl ?? globalThis.fetch;
|
|
41169
|
+
this.fetchImpl = createAnonymizedFetch(baseFetch, (event) => {
|
|
41170
|
+
this.auditLog.append(
|
|
41171
|
+
"l2",
|
|
41172
|
+
QUERY_ANONYMITY_AUDIT_OPS.HEADERS_STRIPPED,
|
|
41173
|
+
this.identityId,
|
|
41174
|
+
{
|
|
41175
|
+
url: event.url,
|
|
41176
|
+
method: event.method,
|
|
41177
|
+
stripped_count: event.stripped_count,
|
|
41178
|
+
removed: event.removed,
|
|
41179
|
+
required_preserved: event.required_preserved
|
|
41180
|
+
}
|
|
41181
|
+
);
|
|
41182
|
+
});
|
|
39992
41183
|
this.config = buildDefaultConfig();
|
|
39993
41184
|
}
|
|
39994
41185
|
/**
|
|
@@ -40705,6 +41896,247 @@ var init_constants5 = __esm({
|
|
|
40705
41896
|
];
|
|
40706
41897
|
}
|
|
40707
41898
|
});
|
|
41899
|
+
async function issueDidWeb(opts) {
|
|
41900
|
+
if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
|
|
41901
|
+
throw new Error(
|
|
41902
|
+
`did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
|
|
41903
|
+
);
|
|
41904
|
+
}
|
|
41905
|
+
if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
|
|
41906
|
+
throw new Error(
|
|
41907
|
+
`did-web: fortress_id '${opts.fortress_id}' is not a valid label`
|
|
41908
|
+
);
|
|
41909
|
+
}
|
|
41910
|
+
if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
|
|
41911
|
+
throw new Error(
|
|
41912
|
+
`did-web: agent_label '${opts.agent_label}' is not a valid label`
|
|
41913
|
+
);
|
|
41914
|
+
}
|
|
41915
|
+
if (opts.public_key.length !== 32) {
|
|
41916
|
+
throw new Error(
|
|
41917
|
+
`did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
|
|
41918
|
+
);
|
|
41919
|
+
}
|
|
41920
|
+
const did = buildDid(opts);
|
|
41921
|
+
const verificationMethodId = `${did}#key-1`;
|
|
41922
|
+
const verificationMethod = {
|
|
41923
|
+
id: verificationMethodId,
|
|
41924
|
+
type: "JsonWebKey2020",
|
|
41925
|
+
controller: did,
|
|
41926
|
+
publicKeyJwk: {
|
|
41927
|
+
kty: "OKP",
|
|
41928
|
+
crv: "Ed25519",
|
|
41929
|
+
x: toBase64url(opts.public_key)
|
|
41930
|
+
}
|
|
41931
|
+
};
|
|
41932
|
+
const didDocument = {
|
|
41933
|
+
"@context": [...DID_CONTEXT],
|
|
41934
|
+
id: did,
|
|
41935
|
+
verificationMethod: [verificationMethod],
|
|
41936
|
+
authentication: [verificationMethodId],
|
|
41937
|
+
assertionMethod: [verificationMethodId]
|
|
41938
|
+
};
|
|
41939
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
41940
|
+
return {
|
|
41941
|
+
did,
|
|
41942
|
+
did_document: didDocument,
|
|
41943
|
+
public_key: opts.public_key,
|
|
41944
|
+
created_at: now.toISOString(),
|
|
41945
|
+
authority_host: opts.authority_host,
|
|
41946
|
+
fortress_id: opts.fortress_id,
|
|
41947
|
+
...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
|
|
41948
|
+
};
|
|
41949
|
+
}
|
|
41950
|
+
function publishDidWebDocument(identifier, opts = {}) {
|
|
41951
|
+
const path = opts.publish_path ?? canonicalPublishPath(identifier);
|
|
41952
|
+
const artifact = canonicalSerializeDidDocument(identifier.did_document);
|
|
41953
|
+
const digest = sha256.sha256(stringToBytes(artifact));
|
|
41954
|
+
const url = `https://${identifier.authority_host}${path}`;
|
|
41955
|
+
return {
|
|
41956
|
+
url,
|
|
41957
|
+
publish_path: path,
|
|
41958
|
+
artifact,
|
|
41959
|
+
sha256: hashToString(digest)
|
|
41960
|
+
};
|
|
41961
|
+
}
|
|
41962
|
+
async function resolveDidWeb(did, opts) {
|
|
41963
|
+
const parsed = parseDidWeb(did);
|
|
41964
|
+
const url = didToUrl(parsed);
|
|
41965
|
+
if (!opts.allowed_hosts.includes(parsed.authority_host)) {
|
|
41966
|
+
return {
|
|
41967
|
+
ok: false,
|
|
41968
|
+
failure: "host_not_allowed",
|
|
41969
|
+
message: `did-web: authority_host '${parsed.authority_host}' is not in the operator's allowed_hosts allowlist; resolution refused (no-outbound-by-default)`,
|
|
41970
|
+
url
|
|
41971
|
+
};
|
|
41972
|
+
}
|
|
41973
|
+
const timeoutMs = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS4;
|
|
41974
|
+
const fetcher = opts.fetcher ?? defaultFetcher;
|
|
41975
|
+
const controller = new AbortController();
|
|
41976
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41977
|
+
let response;
|
|
41978
|
+
try {
|
|
41979
|
+
response = await fetcher(url, { signal: controller.signal });
|
|
41980
|
+
} catch (err) {
|
|
41981
|
+
clearTimeout(timer);
|
|
41982
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
41983
|
+
if (controller.signal.aborted) {
|
|
41984
|
+
return {
|
|
41985
|
+
ok: false,
|
|
41986
|
+
failure: "timeout",
|
|
41987
|
+
message: `did-web: resolution exceeded ${timeoutMs}ms`,
|
|
41988
|
+
url
|
|
41989
|
+
};
|
|
41990
|
+
}
|
|
41991
|
+
return {
|
|
41992
|
+
ok: false,
|
|
41993
|
+
failure: "fetch_failed",
|
|
41994
|
+
message: `did-web: fetch error: ${message}`,
|
|
41995
|
+
url
|
|
41996
|
+
};
|
|
41997
|
+
}
|
|
41998
|
+
clearTimeout(timer);
|
|
41999
|
+
if (response.status === 404) {
|
|
42000
|
+
return {
|
|
42001
|
+
ok: false,
|
|
42002
|
+
failure: "not_found",
|
|
42003
|
+
message: `did-web: 404 from authority host`,
|
|
42004
|
+
url
|
|
42005
|
+
};
|
|
42006
|
+
}
|
|
42007
|
+
if (!response.ok) {
|
|
42008
|
+
return {
|
|
42009
|
+
ok: false,
|
|
42010
|
+
failure: "fetch_failed",
|
|
42011
|
+
message: `did-web: authority host returned ${response.status}`,
|
|
42012
|
+
url
|
|
42013
|
+
};
|
|
42014
|
+
}
|
|
42015
|
+
let body;
|
|
42016
|
+
try {
|
|
42017
|
+
body = await response.json();
|
|
42018
|
+
} catch (err) {
|
|
42019
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42020
|
+
return {
|
|
42021
|
+
ok: false,
|
|
42022
|
+
failure: "invalid_json",
|
|
42023
|
+
message: `did-web: invalid JSON: ${message}`,
|
|
42024
|
+
url
|
|
42025
|
+
};
|
|
42026
|
+
}
|
|
42027
|
+
if (!isDidDocument(body, did)) {
|
|
42028
|
+
return {
|
|
42029
|
+
ok: false,
|
|
42030
|
+
failure: "invalid_json",
|
|
42031
|
+
message: `did-web: response body is not a valid DID Document for ${did}`,
|
|
42032
|
+
url
|
|
42033
|
+
};
|
|
42034
|
+
}
|
|
42035
|
+
if (opts.expected_public_key !== void 0) {
|
|
42036
|
+
const expectedX = toBase64url(opts.expected_public_key);
|
|
42037
|
+
const actualX = body.verificationMethod[0]?.publicKeyJwk.x;
|
|
42038
|
+
if (actualX !== expectedX) {
|
|
42039
|
+
return {
|
|
42040
|
+
ok: false,
|
|
42041
|
+
failure: "signature_mismatch",
|
|
42042
|
+
message: `did-web: verificationMethod public key does not match expected key`,
|
|
42043
|
+
url
|
|
42044
|
+
};
|
|
42045
|
+
}
|
|
42046
|
+
}
|
|
42047
|
+
return { ok: true, did_document: body, url };
|
|
42048
|
+
}
|
|
42049
|
+
function parseDidWeb(did) {
|
|
42050
|
+
if (!did.startsWith("did:web:")) {
|
|
42051
|
+
throw new Error(`did-web: '${did}' is not a did:web identifier`);
|
|
42052
|
+
}
|
|
42053
|
+
const rest = did.slice("did:web:".length);
|
|
42054
|
+
const segments = rest.split(":");
|
|
42055
|
+
const authorityHost = segments[0];
|
|
42056
|
+
if (!HOST_RE.test(authorityHost)) {
|
|
42057
|
+
throw new Error(`did-web: '${authorityHost}' is not a valid DNS host`);
|
|
42058
|
+
}
|
|
42059
|
+
const parsed = { authority_host: authorityHost };
|
|
42060
|
+
if (segments.length === 1) return parsed;
|
|
42061
|
+
if (segments.length === 5 && segments[1] === "fortress" && segments[3] === "agent") {
|
|
42062
|
+
parsed.fortress_id = segments[2];
|
|
42063
|
+
parsed.agent_label = segments[4];
|
|
42064
|
+
return parsed;
|
|
42065
|
+
}
|
|
42066
|
+
throw new Error(
|
|
42067
|
+
`did-web: '${did}' does not match the supported shapes (bare did:web:<host> or did:web:<host>:fortress:<fid>:agent:<alabel>)`
|
|
42068
|
+
);
|
|
42069
|
+
}
|
|
42070
|
+
function didToUrl(parsed) {
|
|
42071
|
+
if (parsed.fortress_id === void 0 || parsed.agent_label === void 0) {
|
|
42072
|
+
return `https://${parsed.authority_host}/.well-known/did.json`;
|
|
42073
|
+
}
|
|
42074
|
+
return `https://${parsed.authority_host}/fortress/${parsed.fortress_id}/agent/${parsed.agent_label}/did.json`;
|
|
42075
|
+
}
|
|
42076
|
+
function buildDid(opts) {
|
|
42077
|
+
if (opts.agent_label === void 0) {
|
|
42078
|
+
return `did:web:${opts.authority_host}`;
|
|
42079
|
+
}
|
|
42080
|
+
return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
|
|
42081
|
+
}
|
|
42082
|
+
function canonicalPublishPath(identifier) {
|
|
42083
|
+
if (identifier.agent_label === void 0) {
|
|
42084
|
+
return "/.well-known/did.json";
|
|
42085
|
+
}
|
|
42086
|
+
return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
|
|
42087
|
+
}
|
|
42088
|
+
function canonicalSerializeDidDocument(doc) {
|
|
42089
|
+
return JSON.stringify(
|
|
42090
|
+
{
|
|
42091
|
+
"@context": doc["@context"],
|
|
42092
|
+
id: doc.id,
|
|
42093
|
+
verificationMethod: doc.verificationMethod,
|
|
42094
|
+
authentication: doc.authentication,
|
|
42095
|
+
assertionMethod: doc.assertionMethod
|
|
42096
|
+
},
|
|
42097
|
+
null,
|
|
42098
|
+
2
|
|
42099
|
+
);
|
|
42100
|
+
}
|
|
42101
|
+
function isDidDocument(value, expectedDid) {
|
|
42102
|
+
if (!value || typeof value !== "object") return false;
|
|
42103
|
+
const v = value;
|
|
42104
|
+
if (v["id"] !== expectedDid) return false;
|
|
42105
|
+
if (!Array.isArray(v["@context"])) return false;
|
|
42106
|
+
const vm = v["verificationMethod"];
|
|
42107
|
+
if (!Array.isArray(vm) || vm.length === 0) return false;
|
|
42108
|
+
const first = vm[0];
|
|
42109
|
+
if (!first || typeof first["id"] !== "string") return false;
|
|
42110
|
+
const jwk = first["publicKeyJwk"];
|
|
42111
|
+
if (!jwk || jwk["kty"] !== "OKP" || jwk["crv"] !== "Ed25519") return false;
|
|
42112
|
+
if (typeof jwk["x"] !== "string") return false;
|
|
42113
|
+
if (!Array.isArray(v["authentication"])) return false;
|
|
42114
|
+
if (!Array.isArray(v["assertionMethod"])) return false;
|
|
42115
|
+
return true;
|
|
42116
|
+
}
|
|
42117
|
+
async function defaultFetcher(url, init) {
|
|
42118
|
+
const response = await fetch(url, init);
|
|
42119
|
+
return {
|
|
42120
|
+
ok: response.ok,
|
|
42121
|
+
status: response.status,
|
|
42122
|
+
json: () => response.json()
|
|
42123
|
+
};
|
|
42124
|
+
}
|
|
42125
|
+
var DID_CONTEXT, DEFAULT_TIMEOUT_MS4, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
|
|
42126
|
+
var init_did_web = __esm({
|
|
42127
|
+
"src/recognition/did-web.ts"() {
|
|
42128
|
+
init_encoding();
|
|
42129
|
+
init_hashing();
|
|
42130
|
+
DID_CONTEXT = [
|
|
42131
|
+
"https://www.w3.org/ns/did/v1",
|
|
42132
|
+
"https://w3id.org/security/suites/jws-2020/v1"
|
|
42133
|
+
];
|
|
42134
|
+
DEFAULT_TIMEOUT_MS4 = 5e3;
|
|
42135
|
+
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;
|
|
42136
|
+
FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
42137
|
+
AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
42138
|
+
}
|
|
42139
|
+
});
|
|
40708
42140
|
|
|
40709
42141
|
// src/contracts/v1.1/exit-bundle-manifest.ts
|
|
40710
42142
|
var EXIT_BUNDLE_PATH_PATTERN, EXIT_BUNDLE_PATH_MAX_BYTES;
|
|
@@ -41408,6 +42840,7 @@ async function exportExitBundle(opts) {
|
|
|
41408
42840
|
"placeholder_vault_metadata"
|
|
41409
42841
|
)
|
|
41410
42842
|
);
|
|
42843
|
+
const didWebBinding = validateExportDidWeb(opts.didWeb);
|
|
41411
42844
|
const body = {
|
|
41412
42845
|
manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
|
|
41413
42846
|
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -41415,7 +42848,8 @@ async function exportExitBundle(opts) {
|
|
|
41415
42848
|
identity_id: identity.identity_id,
|
|
41416
42849
|
fortress_id: identity.did,
|
|
41417
42850
|
fortress_master_pubkey: identity.public_key,
|
|
41418
|
-
did: identity.did
|
|
42851
|
+
did: identity.did,
|
|
42852
|
+
...didWebBinding !== void 0 ? { did_web: didWebBinding } : {}
|
|
41419
42853
|
},
|
|
41420
42854
|
source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
|
|
41421
42855
|
artifacts,
|
|
@@ -41437,6 +42871,18 @@ async function exportExitBundle(opts) {
|
|
|
41437
42871
|
};
|
|
41438
42872
|
const manifestBytes = jsonBytes(manifest);
|
|
41439
42873
|
await promises.writeFile(path.join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
|
|
42874
|
+
if (didWebBinding !== void 0) {
|
|
42875
|
+
opts.auditLog.append(
|
|
42876
|
+
"l1",
|
|
42877
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.EXPORT_INCLUDED,
|
|
42878
|
+
identity.identity_id,
|
|
42879
|
+
{
|
|
42880
|
+
approval_id: exportApprovalAuditId,
|
|
42881
|
+
identifier: didWebBinding.identifier,
|
|
42882
|
+
authority_host: didWebBinding.authority_host
|
|
42883
|
+
}
|
|
42884
|
+
);
|
|
42885
|
+
}
|
|
41440
42886
|
await opts.auditLog.flush();
|
|
41441
42887
|
return {
|
|
41442
42888
|
bundle_dir: bundleDir,
|
|
@@ -41448,6 +42894,30 @@ async function exportExitBundle(opts) {
|
|
|
41448
42894
|
]
|
|
41449
42895
|
};
|
|
41450
42896
|
}
|
|
42897
|
+
function validateExportDidWeb(binding) {
|
|
42898
|
+
if (binding === void 0) return void 0;
|
|
42899
|
+
if (!binding.identifier || typeof binding.identifier !== "string") {
|
|
42900
|
+
throw new Error(
|
|
42901
|
+
"exit-bundle: did_web.identifier must be a non-empty did:web URI"
|
|
42902
|
+
);
|
|
42903
|
+
}
|
|
42904
|
+
if (!binding.authority_host || typeof binding.authority_host !== "string") {
|
|
42905
|
+
throw new Error(
|
|
42906
|
+
"exit-bundle: did_web.authority_host must be a non-empty DNS host"
|
|
42907
|
+
);
|
|
42908
|
+
}
|
|
42909
|
+
const parsed = parseDidWeb(binding.identifier);
|
|
42910
|
+
if (parsed.authority_host.toLowerCase() !== binding.authority_host.toLowerCase()) {
|
|
42911
|
+
throw new Error(
|
|
42912
|
+
`exit-bundle: did_web.identifier authority host '${parsed.authority_host}' does not match did_web.authority_host '${binding.authority_host}'`
|
|
42913
|
+
);
|
|
42914
|
+
}
|
|
42915
|
+
return {
|
|
42916
|
+
identifier: binding.identifier,
|
|
42917
|
+
authority_host: binding.authority_host,
|
|
42918
|
+
...binding.published_at !== void 0 ? { published_at: binding.published_at } : {}
|
|
42919
|
+
};
|
|
42920
|
+
}
|
|
41451
42921
|
function publicKeysFromIdentityArtifact(identityArtifact) {
|
|
41452
42922
|
const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
|
|
41453
42923
|
return {
|
|
@@ -41662,6 +43132,87 @@ async function importExitBundle(opts) {
|
|
|
41662
43132
|
};
|
|
41663
43133
|
}
|
|
41664
43134
|
const manifest = await readManifest(opts.bundleDir);
|
|
43135
|
+
const importWarnings = [];
|
|
43136
|
+
const manifestDidWeb = manifest.body.identity_binding.did_web;
|
|
43137
|
+
if (manifestDidWeb !== void 0 && !opts.skipDidWebVerify) {
|
|
43138
|
+
const expectedPublicKey = fromBase64url(
|
|
43139
|
+
manifest.body.identity_binding.fortress_master_pubkey
|
|
43140
|
+
);
|
|
43141
|
+
const resolveOpts = {
|
|
43142
|
+
allowed_hosts: opts.didWebAllowedHosts ?? [],
|
|
43143
|
+
expected_public_key: expectedPublicKey,
|
|
43144
|
+
...opts.didWebFetcher !== void 0 ? { fetcher: opts.didWebFetcher } : {},
|
|
43145
|
+
...opts.didWebTimeoutMs !== void 0 ? { timeout_ms: opts.didWebTimeoutMs } : {}
|
|
43146
|
+
};
|
|
43147
|
+
const resolution = await resolveDidWeb(
|
|
43148
|
+
manifestDidWeb.identifier,
|
|
43149
|
+
resolveOpts
|
|
43150
|
+
);
|
|
43151
|
+
const authorityHost = manifestDidWeb.authority_host;
|
|
43152
|
+
opts.auditLog.append(
|
|
43153
|
+
"l1",
|
|
43154
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.AUTHORITY_HOST,
|
|
43155
|
+
manifest.body.identity_binding.identity_id,
|
|
43156
|
+
{ authority_host: authorityHost, identifier: manifestDidWeb.identifier }
|
|
43157
|
+
);
|
|
43158
|
+
if (resolution.ok) {
|
|
43159
|
+
opts.auditLog.append(
|
|
43160
|
+
"l1",
|
|
43161
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43162
|
+
manifest.body.identity_binding.identity_id,
|
|
43163
|
+
{
|
|
43164
|
+
outcome: "success",
|
|
43165
|
+
identifier: manifestDidWeb.identifier,
|
|
43166
|
+
authority_host: authorityHost,
|
|
43167
|
+
resolved_url: resolution.url
|
|
43168
|
+
}
|
|
43169
|
+
);
|
|
43170
|
+
} else if (resolution.failure === "signature_mismatch") {
|
|
43171
|
+
opts.auditLog.append(
|
|
43172
|
+
"l1",
|
|
43173
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43174
|
+
manifest.body.identity_binding.identity_id,
|
|
43175
|
+
{
|
|
43176
|
+
outcome: "mismatch",
|
|
43177
|
+
identifier: manifestDidWeb.identifier,
|
|
43178
|
+
authority_host: authorityHost,
|
|
43179
|
+
resolved_url: resolution.url
|
|
43180
|
+
}
|
|
43181
|
+
);
|
|
43182
|
+
await opts.auditLog.flush();
|
|
43183
|
+
throw new ExitBundleImportError(
|
|
43184
|
+
"did_web_mismatch",
|
|
43185
|
+
`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.`
|
|
43186
|
+
);
|
|
43187
|
+
} else {
|
|
43188
|
+
opts.auditLog.append(
|
|
43189
|
+
"l1",
|
|
43190
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43191
|
+
manifest.body.identity_binding.identity_id,
|
|
43192
|
+
{
|
|
43193
|
+
outcome: "resolution_failure",
|
|
43194
|
+
failure: resolution.failure,
|
|
43195
|
+
identifier: manifestDidWeb.identifier,
|
|
43196
|
+
authority_host: authorityHost,
|
|
43197
|
+
resolved_url: resolution.url
|
|
43198
|
+
}
|
|
43199
|
+
);
|
|
43200
|
+
importWarnings.push(
|
|
43201
|
+
`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.`
|
|
43202
|
+
);
|
|
43203
|
+
}
|
|
43204
|
+
} else if (manifestDidWeb !== void 0 && opts.skipDidWebVerify) {
|
|
43205
|
+
opts.auditLog.append(
|
|
43206
|
+
"l1",
|
|
43207
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS.IMPORT_VERIFIED,
|
|
43208
|
+
manifest.body.identity_binding.identity_id,
|
|
43209
|
+
{
|
|
43210
|
+
outcome: "skipped",
|
|
43211
|
+
identifier: manifestDidWeb.identifier,
|
|
43212
|
+
authority_host: manifestDidWeb.authority_host
|
|
43213
|
+
}
|
|
43214
|
+
);
|
|
43215
|
+
}
|
|
41665
43216
|
const identityArtifact = await loadExitArtifact(
|
|
41666
43217
|
opts.bundleDir,
|
|
41667
43218
|
manifest,
|
|
@@ -41726,7 +43277,7 @@ async function importExitBundle(opts) {
|
|
|
41726
43277
|
unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
|
|
41727
43278
|
},
|
|
41728
43279
|
staged_artifacts: [],
|
|
41729
|
-
warnings: verification.warnings,
|
|
43280
|
+
warnings: [...verification.warnings, ...importWarnings],
|
|
41730
43281
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
41731
43282
|
};
|
|
41732
43283
|
}
|
|
@@ -41893,7 +43444,7 @@ async function importExitBundle(opts) {
|
|
|
41893
43444
|
state: stateResult,
|
|
41894
43445
|
reputation: reputationResult,
|
|
41895
43446
|
staged_artifacts: stagedArtifacts,
|
|
41896
|
-
warnings: verification.warnings,
|
|
43447
|
+
warnings: [...verification.warnings, ...importWarnings],
|
|
41897
43448
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
41898
43449
|
};
|
|
41899
43450
|
}
|
|
@@ -41915,12 +43466,13 @@ function exitBundleManifestShape() {
|
|
|
41915
43466
|
]
|
|
41916
43467
|
};
|
|
41917
43468
|
}
|
|
41918
|
-
var ARTIFACT_DIR, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE, ExitBundleImportError;
|
|
43469
|
+
var ARTIFACT_DIR, EXIT_BUNDLE_DID_WEB_AUDIT_OPS, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE, ExitBundleImportError;
|
|
41919
43470
|
var init_bundle = __esm({
|
|
41920
43471
|
"src/exit/bundle.ts"() {
|
|
41921
43472
|
init_state_store();
|
|
41922
43473
|
init_config();
|
|
41923
43474
|
init_constants5();
|
|
43475
|
+
init_did_web();
|
|
41924
43476
|
init_canonical_json();
|
|
41925
43477
|
init_hashing();
|
|
41926
43478
|
init_encoding();
|
|
@@ -41930,6 +43482,11 @@ var init_bundle = __esm({
|
|
|
41930
43482
|
init_reputation_store();
|
|
41931
43483
|
init_verifier2();
|
|
41932
43484
|
ARTIFACT_DIR = "artifacts";
|
|
43485
|
+
EXIT_BUNDLE_DID_WEB_AUDIT_OPS = {
|
|
43486
|
+
EXPORT_INCLUDED: "exit_bundle_did_web_export_included",
|
|
43487
|
+
IMPORT_VERIFIED: "exit_bundle_did_web_import_verified",
|
|
43488
|
+
AUTHORITY_HOST: "exit_bundle_did_web_authority_host"
|
|
43489
|
+
};
|
|
41933
43490
|
EXIT_IMPORT_NAMESPACE = "_exit_imports";
|
|
41934
43491
|
EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
|
|
41935
43492
|
EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
|
|
@@ -42148,6 +43705,26 @@ ${policyErr.message}
|
|
|
42148
43705
|
}
|
|
42149
43706
|
throw policyErr;
|
|
42150
43707
|
}
|
|
43708
|
+
const includeDidWebFlag = flagValue(argv, "--include-did-web");
|
|
43709
|
+
const includeDidWebDisabled = includeDidWebFlag === "false";
|
|
43710
|
+
const didWebIdentifier = flagValue(argv, "--did-web");
|
|
43711
|
+
const didWebAuthorityHost = flagValue(argv, "--did-web-authority-host");
|
|
43712
|
+
const didWebPublishedAt = flagValue(argv, "--did-web-published-at");
|
|
43713
|
+
let exportDidWeb;
|
|
43714
|
+
if (!includeDidWebDisabled && didWebIdentifier !== void 0) {
|
|
43715
|
+
if (didWebAuthorityHost === void 0) {
|
|
43716
|
+
write(
|
|
43717
|
+
err,
|
|
43718
|
+
"Error: --did-web requires --did-web-authority-host=<host>\n"
|
|
43719
|
+
);
|
|
43720
|
+
return 2;
|
|
43721
|
+
}
|
|
43722
|
+
exportDidWeb = {
|
|
43723
|
+
identifier: didWebIdentifier,
|
|
43724
|
+
authority_host: didWebAuthorityHost,
|
|
43725
|
+
...didWebPublishedAt !== void 0 ? { published_at: didWebPublishedAt } : {}
|
|
43726
|
+
};
|
|
43727
|
+
}
|
|
42151
43728
|
const result = await exportExitBundle({
|
|
42152
43729
|
bundleDir: outDir,
|
|
42153
43730
|
storage: ctx.storage,
|
|
@@ -42159,7 +43736,8 @@ ${policyErr.message}
|
|
|
42159
43736
|
config,
|
|
42160
43737
|
stateStoragePath: ctx.stateStoragePath,
|
|
42161
43738
|
stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
|
|
42162
|
-
keySource: ctx.keySource
|
|
43739
|
+
keySource: ctx.keySource,
|
|
43740
|
+
...exportDidWeb !== void 0 ? { didWeb: exportDidWeb } : {}
|
|
42163
43741
|
});
|
|
42164
43742
|
if (json) write(out, JSON.stringify(result, null, 2) + "\n");
|
|
42165
43743
|
else {
|
|
@@ -42237,6 +43815,11 @@ ${policyErr.message}
|
|
|
42237
43815
|
write(err, "--conflict must be skip, overwrite, or version\n");
|
|
42238
43816
|
return 2;
|
|
42239
43817
|
}
|
|
43818
|
+
const didWebAllowedHosts = repeatedFlagValues(
|
|
43819
|
+
argv,
|
|
43820
|
+
"--did-web-allowed-host"
|
|
43821
|
+
);
|
|
43822
|
+
const skipDidWebVerify = hasFlag(argv, "--skip-did-web-verify");
|
|
42240
43823
|
let result;
|
|
42241
43824
|
try {
|
|
42242
43825
|
result = await importExitBundle({
|
|
@@ -42252,7 +43835,9 @@ ${policyErr.message}
|
|
|
42252
43835
|
conflictResolution: conflict,
|
|
42253
43836
|
sourcePassphrase: flagValue(argv, "--source-passphrase"),
|
|
42254
43837
|
sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
|
|
42255
|
-
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
|
|
43838
|
+
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id"),
|
|
43839
|
+
...didWebAllowedHosts.length > 0 ? { didWebAllowedHosts } : {},
|
|
43840
|
+
skipDidWebVerify
|
|
42256
43841
|
});
|
|
42257
43842
|
} catch (e) {
|
|
42258
43843
|
if (e instanceof InvalidExitBundleError) {
|
|
@@ -43037,12 +44622,14 @@ ${err.message}
|
|
|
43037
44622
|
fortressId: fortressIdForAggregator
|
|
43038
44623
|
});
|
|
43039
44624
|
const handoffEventBridge = new HandoffEventBridge();
|
|
44625
|
+
const workflowStateTracker = new WorkflowStateTracker();
|
|
43040
44626
|
if (dashboard) {
|
|
43041
44627
|
dashboard.setHandoffLog({
|
|
43042
44628
|
handoffLog,
|
|
43043
44629
|
eventBridge: handoffEventBridge,
|
|
43044
44630
|
auditLog,
|
|
43045
|
-
operatorId: aggregatorIdentityId
|
|
44631
|
+
operatorId: aggregatorIdentityId,
|
|
44632
|
+
workflowStateTracker
|
|
43046
44633
|
});
|
|
43047
44634
|
}
|
|
43048
44635
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
@@ -43244,6 +44831,7 @@ var init_src = __esm({
|
|
|
43244
44831
|
init_anomaly_pipeline();
|
|
43245
44832
|
init_handoff_log();
|
|
43246
44833
|
init_handoff_routes();
|
|
44834
|
+
init_workflow_state_tracker();
|
|
43247
44835
|
init_sentinels();
|
|
43248
44836
|
init_subscription_store();
|
|
43249
44837
|
init_tools4();
|
|
@@ -47350,7 +48938,7 @@ async function probeTenantDashboard(tenant, options = {}) {
|
|
|
47350
48938
|
if (!rt) {
|
|
47351
48939
|
return { running: false, status: null, reason: "no runtime.json" };
|
|
47352
48940
|
}
|
|
47353
|
-
const timeoutMs = options.timeoutMs ??
|
|
48941
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
|
|
47354
48942
|
return await new Promise((resolve8) => {
|
|
47355
48943
|
const req = http.get(
|
|
47356
48944
|
{
|
|
@@ -47386,10 +48974,10 @@ async function probeTenantDashboard(tenant, options = {}) {
|
|
|
47386
48974
|
});
|
|
47387
48975
|
});
|
|
47388
48976
|
}
|
|
47389
|
-
var
|
|
48977
|
+
var DEFAULT_TIMEOUT_MS5;
|
|
47390
48978
|
var init_health = __esm({
|
|
47391
48979
|
"src/cli/agents/health.ts"() {
|
|
47392
|
-
|
|
48980
|
+
DEFAULT_TIMEOUT_MS5 = 500;
|
|
47393
48981
|
}
|
|
47394
48982
|
});
|
|
47395
48983
|
function resolveCtx(args) {
|
|
@@ -48640,108 +50228,6 @@ var init_sentinel2 = __esm({
|
|
|
48640
50228
|
init_sentinels();
|
|
48641
50229
|
}
|
|
48642
50230
|
});
|
|
48643
|
-
async function issueDidWeb(opts) {
|
|
48644
|
-
if (!opts.authority_host || !HOST_RE.test(opts.authority_host)) {
|
|
48645
|
-
throw new Error(
|
|
48646
|
-
`did-web: authority_host '${opts.authority_host}' is not a valid DNS host`
|
|
48647
|
-
);
|
|
48648
|
-
}
|
|
48649
|
-
if (!FORTRESS_LABEL_RE.test(opts.fortress_id)) {
|
|
48650
|
-
throw new Error(
|
|
48651
|
-
`did-web: fortress_id '${opts.fortress_id}' is not a valid label`
|
|
48652
|
-
);
|
|
48653
|
-
}
|
|
48654
|
-
if (opts.agent_label !== void 0 && !AGENT_LABEL_RE.test(opts.agent_label)) {
|
|
48655
|
-
throw new Error(
|
|
48656
|
-
`did-web: agent_label '${opts.agent_label}' is not a valid label`
|
|
48657
|
-
);
|
|
48658
|
-
}
|
|
48659
|
-
if (opts.public_key.length !== 32) {
|
|
48660
|
-
throw new Error(
|
|
48661
|
-
`did-web: public_key must be exactly 32 bytes (Ed25519), got ${opts.public_key.length}`
|
|
48662
|
-
);
|
|
48663
|
-
}
|
|
48664
|
-
const did = buildDid(opts);
|
|
48665
|
-
const verificationMethodId = `${did}#key-1`;
|
|
48666
|
-
const verificationMethod = {
|
|
48667
|
-
id: verificationMethodId,
|
|
48668
|
-
type: "JsonWebKey2020",
|
|
48669
|
-
controller: did,
|
|
48670
|
-
publicKeyJwk: {
|
|
48671
|
-
kty: "OKP",
|
|
48672
|
-
crv: "Ed25519",
|
|
48673
|
-
x: toBase64url(opts.public_key)
|
|
48674
|
-
}
|
|
48675
|
-
};
|
|
48676
|
-
const didDocument = {
|
|
48677
|
-
"@context": [...DID_CONTEXT],
|
|
48678
|
-
id: did,
|
|
48679
|
-
verificationMethod: [verificationMethod],
|
|
48680
|
-
authentication: [verificationMethodId],
|
|
48681
|
-
assertionMethod: [verificationMethodId]
|
|
48682
|
-
};
|
|
48683
|
-
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
48684
|
-
return {
|
|
48685
|
-
did,
|
|
48686
|
-
did_document: didDocument,
|
|
48687
|
-
public_key: opts.public_key,
|
|
48688
|
-
created_at: now.toISOString(),
|
|
48689
|
-
authority_host: opts.authority_host,
|
|
48690
|
-
fortress_id: opts.fortress_id,
|
|
48691
|
-
...opts.agent_label !== void 0 ? { agent_label: opts.agent_label } : {}
|
|
48692
|
-
};
|
|
48693
|
-
}
|
|
48694
|
-
function publishDidWebDocument(identifier, opts = {}) {
|
|
48695
|
-
const path = opts.publish_path ?? canonicalPublishPath(identifier);
|
|
48696
|
-
const artifact = canonicalSerializeDidDocument(identifier.did_document);
|
|
48697
|
-
const digest = sha256.sha256(stringToBytes(artifact));
|
|
48698
|
-
const url = `https://${identifier.authority_host}${path}`;
|
|
48699
|
-
return {
|
|
48700
|
-
url,
|
|
48701
|
-
publish_path: path,
|
|
48702
|
-
artifact,
|
|
48703
|
-
sha256: hashToString(digest)
|
|
48704
|
-
};
|
|
48705
|
-
}
|
|
48706
|
-
function buildDid(opts) {
|
|
48707
|
-
if (opts.agent_label === void 0) {
|
|
48708
|
-
return `did:web:${opts.authority_host}`;
|
|
48709
|
-
}
|
|
48710
|
-
return `did:web:${opts.authority_host}:fortress:${opts.fortress_id}:agent:${opts.agent_label}`;
|
|
48711
|
-
}
|
|
48712
|
-
function canonicalPublishPath(identifier) {
|
|
48713
|
-
if (identifier.agent_label === void 0) {
|
|
48714
|
-
return "/.well-known/did.json";
|
|
48715
|
-
}
|
|
48716
|
-
return `/fortress/${identifier.fortress_id}/agent/${identifier.agent_label}/did.json`;
|
|
48717
|
-
}
|
|
48718
|
-
function canonicalSerializeDidDocument(doc) {
|
|
48719
|
-
return JSON.stringify(
|
|
48720
|
-
{
|
|
48721
|
-
"@context": doc["@context"],
|
|
48722
|
-
id: doc.id,
|
|
48723
|
-
verificationMethod: doc.verificationMethod,
|
|
48724
|
-
authentication: doc.authentication,
|
|
48725
|
-
assertionMethod: doc.assertionMethod
|
|
48726
|
-
},
|
|
48727
|
-
null,
|
|
48728
|
-
2
|
|
48729
|
-
);
|
|
48730
|
-
}
|
|
48731
|
-
var DID_CONTEXT, HOST_RE, FORTRESS_LABEL_RE, AGENT_LABEL_RE;
|
|
48732
|
-
var init_did_web = __esm({
|
|
48733
|
-
"src/recognition/did-web.ts"() {
|
|
48734
|
-
init_encoding();
|
|
48735
|
-
init_hashing();
|
|
48736
|
-
DID_CONTEXT = [
|
|
48737
|
-
"https://www.w3.org/ns/did/v1",
|
|
48738
|
-
"https://w3id.org/security/suites/jws-2020/v1"
|
|
48739
|
-
];
|
|
48740
|
-
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;
|
|
48741
|
-
FORTRESS_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
48742
|
-
AGENT_LABEL_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
48743
|
-
}
|
|
48744
|
-
});
|
|
48745
50231
|
|
|
48746
50232
|
// src/cli/did-web.ts
|
|
48747
50233
|
var did_web_exports = {};
|
|
@@ -49000,6 +50486,689 @@ var init_did_web2 = __esm({
|
|
|
49000
50486
|
}
|
|
49001
50487
|
});
|
|
49002
50488
|
|
|
50489
|
+
// src/anomaly-detection/classifiers/rolling-baseline.ts
|
|
50490
|
+
var ROLLING_BASELINE_CLASSIFIER_ID, DEFAULT_MIN_SAMPLES_FOR_PREDICTION, STDDEV_FLOOR, RollingBaselineClassifier;
|
|
50491
|
+
var init_rolling_baseline = __esm({
|
|
50492
|
+
"src/anomaly-detection/classifiers/rolling-baseline.ts"() {
|
|
50493
|
+
ROLLING_BASELINE_CLASSIFIER_ID = "rolling-baseline";
|
|
50494
|
+
DEFAULT_MIN_SAMPLES_FOR_PREDICTION = 7;
|
|
50495
|
+
STDDEV_FLOOR = 0.5;
|
|
50496
|
+
RollingBaselineClassifier = class {
|
|
50497
|
+
classifierId = ROLLING_BASELINE_CLASSIFIER_ID;
|
|
50498
|
+
stateStore;
|
|
50499
|
+
minSamplesForPrediction;
|
|
50500
|
+
/** In-memory cache of per-agent state; loaded lazily on first touch. */
|
|
50501
|
+
cache = /* @__PURE__ */ new Map();
|
|
50502
|
+
/** Agents whose in-memory state has been mutated since last train(). */
|
|
50503
|
+
dirty = /* @__PURE__ */ new Set();
|
|
50504
|
+
constructor(opts) {
|
|
50505
|
+
this.stateStore = opts.stateStore;
|
|
50506
|
+
this.minSamplesForPrediction = opts.minSamplesForPrediction ?? DEFAULT_MIN_SAMPLES_FOR_PREDICTION;
|
|
50507
|
+
}
|
|
50508
|
+
async observe(vector) {
|
|
50509
|
+
const state = await this.loadOrInit(vector.agent_id);
|
|
50510
|
+
for (const [featureName, observed] of Object.entries(vector.features)) {
|
|
50511
|
+
if (!Number.isFinite(observed)) continue;
|
|
50512
|
+
const welford = state.features[featureName] ?? {
|
|
50513
|
+
n: 0,
|
|
50514
|
+
mean: 0,
|
|
50515
|
+
m2: 0
|
|
50516
|
+
};
|
|
50517
|
+
const nextN = welford.n + 1;
|
|
50518
|
+
const delta = observed - welford.mean;
|
|
50519
|
+
const nextMean = welford.mean + delta / nextN;
|
|
50520
|
+
const delta2 = observed - nextMean;
|
|
50521
|
+
const nextM2 = welford.m2 + delta * delta2;
|
|
50522
|
+
state.features[featureName] = { n: nextN, mean: nextMean, m2: nextM2 };
|
|
50523
|
+
}
|
|
50524
|
+
state.observation_count += 1;
|
|
50525
|
+
state.last_observed_at = vector.observed_at;
|
|
50526
|
+
this.dirty.add(vector.agent_id);
|
|
50527
|
+
}
|
|
50528
|
+
async predict(vector) {
|
|
50529
|
+
const state = await this.loadOrInit(vector.agent_id);
|
|
50530
|
+
if (state.observation_count < this.minSamplesForPrediction) {
|
|
50531
|
+
return {
|
|
50532
|
+
anomaly_score: 0,
|
|
50533
|
+
explanation: [],
|
|
50534
|
+
feature_contributions: [],
|
|
50535
|
+
baseline_ready: false
|
|
50536
|
+
};
|
|
50537
|
+
}
|
|
50538
|
+
const contributions = [];
|
|
50539
|
+
let sumSquaredZ = 0;
|
|
50540
|
+
for (const [featureName, observed] of Object.entries(vector.features)) {
|
|
50541
|
+
if (!Number.isFinite(observed)) continue;
|
|
50542
|
+
const welford = state.features[featureName];
|
|
50543
|
+
if (!welford || welford.n < 2) continue;
|
|
50544
|
+
const variance = welford.m2 / (welford.n - 1);
|
|
50545
|
+
const stddev = Math.max(Math.sqrt(variance), STDDEV_FLOOR);
|
|
50546
|
+
const z = (observed - welford.mean) / stddev;
|
|
50547
|
+
sumSquaredZ += z * z;
|
|
50548
|
+
contributions.push({
|
|
50549
|
+
feature_name: featureName,
|
|
50550
|
+
observed,
|
|
50551
|
+
baseline_mean: welford.mean,
|
|
50552
|
+
baseline_stddev: stddev,
|
|
50553
|
+
z_score: z
|
|
50554
|
+
});
|
|
50555
|
+
}
|
|
50556
|
+
contributions.sort(
|
|
50557
|
+
(a, b) => Math.abs(b.z_score) - Math.abs(a.z_score)
|
|
50558
|
+
);
|
|
50559
|
+
const anomalyScore = Math.sqrt(sumSquaredZ);
|
|
50560
|
+
const explanation = contributions.map(
|
|
50561
|
+
(c) => `${c.feature_name} ${c.observed.toFixed(2)} vs baseline ${c.baseline_mean.toFixed(2)}+/-${c.baseline_stddev.toFixed(2)} (z=${c.z_score.toFixed(2)})`
|
|
50562
|
+
);
|
|
50563
|
+
return {
|
|
50564
|
+
anomaly_score: anomalyScore,
|
|
50565
|
+
explanation,
|
|
50566
|
+
feature_contributions: contributions,
|
|
50567
|
+
baseline_ready: true
|
|
50568
|
+
};
|
|
50569
|
+
}
|
|
50570
|
+
async train() {
|
|
50571
|
+
const dirtyAgents = [...this.dirty];
|
|
50572
|
+
for (const agentId of dirtyAgents) {
|
|
50573
|
+
const state = this.cache.get(agentId);
|
|
50574
|
+
if (!state) continue;
|
|
50575
|
+
await this.stateStore.saveState(this.classifierId, agentId, state);
|
|
50576
|
+
this.dirty.delete(agentId);
|
|
50577
|
+
}
|
|
50578
|
+
let sampleCount = 0;
|
|
50579
|
+
for (const state of this.cache.values()) {
|
|
50580
|
+
sampleCount += state.observation_count;
|
|
50581
|
+
}
|
|
50582
|
+
return {
|
|
50583
|
+
trained_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
50584
|
+
sample_count: sampleCount,
|
|
50585
|
+
agent_count: this.cache.size
|
|
50586
|
+
};
|
|
50587
|
+
}
|
|
50588
|
+
/** Test helper: read the in-memory state for an agent. */
|
|
50589
|
+
getAgentState(agentId) {
|
|
50590
|
+
return this.cache.get(agentId);
|
|
50591
|
+
}
|
|
50592
|
+
async loadOrInit(agentId) {
|
|
50593
|
+
const cached = this.cache.get(agentId);
|
|
50594
|
+
if (cached) return cached;
|
|
50595
|
+
const persisted = await this.stateStore.loadState(
|
|
50596
|
+
this.classifierId,
|
|
50597
|
+
agentId
|
|
50598
|
+
);
|
|
50599
|
+
const state = persisted ?? {
|
|
50600
|
+
observation_count: 0,
|
|
50601
|
+
last_observed_at: null,
|
|
50602
|
+
features: {}
|
|
50603
|
+
};
|
|
50604
|
+
this.cache.set(agentId, state);
|
|
50605
|
+
return state;
|
|
50606
|
+
}
|
|
50607
|
+
};
|
|
50608
|
+
}
|
|
50609
|
+
});
|
|
50610
|
+
|
|
50611
|
+
// src/anomaly-detection/feature-extractors/per-agent-activity.ts
|
|
50612
|
+
function emptyBucket() {
|
|
50613
|
+
return {
|
|
50614
|
+
tool_call_count: 0,
|
|
50615
|
+
egress_call_count: 0,
|
|
50616
|
+
credential_use_count: 0,
|
|
50617
|
+
audit_event_count: 0,
|
|
50618
|
+
recent_receipt_count: 0
|
|
50619
|
+
};
|
|
50620
|
+
}
|
|
50621
|
+
function bucketToFeatures(bucket) {
|
|
50622
|
+
return {
|
|
50623
|
+
tool_call_count: bucket.tool_call_count,
|
|
50624
|
+
egress_call_count: bucket.egress_call_count,
|
|
50625
|
+
credential_use_count: bucket.credential_use_count,
|
|
50626
|
+
audit_event_count: bucket.audit_event_count,
|
|
50627
|
+
recent_receipt_count: bucket.recent_receipt_count
|
|
50628
|
+
};
|
|
50629
|
+
}
|
|
50630
|
+
function classifyEntry(entry, bucket) {
|
|
50631
|
+
bucket.audit_event_count += 1;
|
|
50632
|
+
bucket.tool_call_count += 1;
|
|
50633
|
+
if (entry.operation.startsWith("proxy_call:")) {
|
|
50634
|
+
bucket.egress_call_count += 1;
|
|
50635
|
+
}
|
|
50636
|
+
if (entry.operation.startsWith("broker_secret_") || entry.operation.startsWith("broker_token_")) {
|
|
50637
|
+
bucket.credential_use_count += 1;
|
|
50638
|
+
}
|
|
50639
|
+
if (entry.operation.startsWith("composition_receipt_") || entry.operation === "reputation_record" || entry.operation === "reputation_query" || entry.operation === "reputation_publish") {
|
|
50640
|
+
bucket.recent_receipt_count += 1;
|
|
50641
|
+
}
|
|
50642
|
+
}
|
|
50643
|
+
async function extractPerAgentActivity(context) {
|
|
50644
|
+
const now = context.now();
|
|
50645
|
+
const sinceIso = new Date(now.getTime() - WINDOW_MS2).toISOString();
|
|
50646
|
+
const result = await context.auditLog.query({
|
|
50647
|
+
since: sinceIso,
|
|
50648
|
+
limit: QUERY_LIMIT6
|
|
50649
|
+
});
|
|
50650
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
50651
|
+
for (const entry of result.entries) {
|
|
50652
|
+
const agentId = entry.identity_id && entry.identity_id.length > 0 ? entry.identity_id : SYSTEM_AGENT_BUCKET;
|
|
50653
|
+
let bucket = buckets.get(agentId);
|
|
50654
|
+
if (!bucket) {
|
|
50655
|
+
bucket = emptyBucket();
|
|
50656
|
+
buckets.set(agentId, bucket);
|
|
50657
|
+
}
|
|
50658
|
+
classifyEntry(entry, bucket);
|
|
50659
|
+
}
|
|
50660
|
+
const observedAt = now.toISOString();
|
|
50661
|
+
const vectors = [];
|
|
50662
|
+
for (const [agentId, bucket] of buckets.entries()) {
|
|
50663
|
+
vectors.push({
|
|
50664
|
+
agent_id: agentId,
|
|
50665
|
+
observed_at: observedAt,
|
|
50666
|
+
features: bucketToFeatures(bucket),
|
|
50667
|
+
window_label: PER_AGENT_ACTIVITY_WINDOW_LABEL
|
|
50668
|
+
});
|
|
50669
|
+
}
|
|
50670
|
+
vectors.sort((a, b) => a.agent_id < b.agent_id ? -1 : 1);
|
|
50671
|
+
return vectors;
|
|
50672
|
+
}
|
|
50673
|
+
var PER_AGENT_ACTIVITY_EXTRACTOR_ID, WINDOW_MS2, QUERY_LIMIT6, SYSTEM_AGENT_BUCKET, PER_AGENT_ACTIVITY_WINDOW_LABEL;
|
|
50674
|
+
var init_per_agent_activity = __esm({
|
|
50675
|
+
"src/anomaly-detection/feature-extractors/per-agent-activity.ts"() {
|
|
50676
|
+
PER_AGENT_ACTIVITY_EXTRACTOR_ID = "per-agent-activity";
|
|
50677
|
+
WINDOW_MS2 = 24 * 60 * 60 * 1e3;
|
|
50678
|
+
QUERY_LIMIT6 = 1e4;
|
|
50679
|
+
SYSTEM_AGENT_BUCKET = "system";
|
|
50680
|
+
PER_AGENT_ACTIVITY_WINDOW_LABEL = "24h_rolling";
|
|
50681
|
+
}
|
|
50682
|
+
});
|
|
50683
|
+
|
|
50684
|
+
// src/anomaly-detection/detectors/per-agent-activity-detector.ts
|
|
50685
|
+
var PER_AGENT_ACTIVITY_DETECTOR_ID, PerAgentActivityDetector, PendingClassifier;
|
|
50686
|
+
var init_per_agent_activity_detector = __esm({
|
|
50687
|
+
"src/anomaly-detection/detectors/per-agent-activity-detector.ts"() {
|
|
50688
|
+
init_types4();
|
|
50689
|
+
init_rolling_baseline();
|
|
50690
|
+
init_classifier_state_store();
|
|
50691
|
+
init_per_agent_activity();
|
|
50692
|
+
PER_AGENT_ACTIVITY_DETECTOR_ID = PER_AGENT_ACTIVITY_EXTRACTOR_ID;
|
|
50693
|
+
PerAgentActivityDetector = class extends AnomalyDetector {
|
|
50694
|
+
detectorId = PER_AGENT_ACTIVITY_DETECTOR_ID;
|
|
50695
|
+
description = "Per-agent statistical drift detector: tool-call count, egress volume, credential-use rate, audit-event count, recent-receipt count over a 24h rolling window. Compared against a per-agent rolling baseline (Welford running mean + variance).";
|
|
50696
|
+
classifier;
|
|
50697
|
+
explicitClassifier;
|
|
50698
|
+
minSamplesForPrediction;
|
|
50699
|
+
constructor(opts) {
|
|
50700
|
+
super();
|
|
50701
|
+
this.explicitClassifier = opts?.classifier !== void 0;
|
|
50702
|
+
if (opts?.classifier) {
|
|
50703
|
+
this.classifier = opts.classifier;
|
|
50704
|
+
} else {
|
|
50705
|
+
this.classifier = new PendingClassifier();
|
|
50706
|
+
}
|
|
50707
|
+
this.minSamplesForPrediction = opts?.minSamplesForPrediction;
|
|
50708
|
+
}
|
|
50709
|
+
async subscribe(context) {
|
|
50710
|
+
await super.subscribe(context);
|
|
50711
|
+
if (this.explicitClassifier) return;
|
|
50712
|
+
const stateStore = new ClassifierStateStore({
|
|
50713
|
+
storage: context.storage,
|
|
50714
|
+
masterKey: context.masterKey,
|
|
50715
|
+
fortressId: context.fortressId,
|
|
50716
|
+
now: context.now
|
|
50717
|
+
});
|
|
50718
|
+
const realClassifier = new RollingBaselineClassifier({
|
|
50719
|
+
stateStore,
|
|
50720
|
+
...this.minSamplesForPrediction !== void 0 ? { minSamplesForPrediction: this.minSamplesForPrediction } : {}
|
|
50721
|
+
});
|
|
50722
|
+
this.classifier = realClassifier;
|
|
50723
|
+
}
|
|
50724
|
+
async featureExtract(context) {
|
|
50725
|
+
return extractPerAgentActivity(context);
|
|
50726
|
+
}
|
|
50727
|
+
};
|
|
50728
|
+
PendingClassifier = class {
|
|
50729
|
+
classifierId = "pending";
|
|
50730
|
+
async observe() {
|
|
50731
|
+
throw new Error(
|
|
50732
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50733
|
+
);
|
|
50734
|
+
}
|
|
50735
|
+
async predict() {
|
|
50736
|
+
throw new Error(
|
|
50737
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50738
|
+
);
|
|
50739
|
+
}
|
|
50740
|
+
async train() {
|
|
50741
|
+
throw new Error(
|
|
50742
|
+
"anomaly-detector: classifier accessed before subscribe()"
|
|
50743
|
+
);
|
|
50744
|
+
}
|
|
50745
|
+
};
|
|
50746
|
+
}
|
|
50747
|
+
});
|
|
50748
|
+
|
|
50749
|
+
// src/anomaly-detection/anomaly-catalog.ts
|
|
50750
|
+
function findCatalogEntry(detectorId, classifierId) {
|
|
50751
|
+
return ANOMALY_CATALOG.find(
|
|
50752
|
+
(e) => e.detectorId === detectorId && e.classifierId === classifierId
|
|
50753
|
+
);
|
|
50754
|
+
}
|
|
50755
|
+
var ANOMALY_CATALOG;
|
|
50756
|
+
var init_anomaly_catalog = __esm({
|
|
50757
|
+
"src/anomaly-detection/anomaly-catalog.ts"() {
|
|
50758
|
+
init_per_agent_activity_detector();
|
|
50759
|
+
init_rolling_baseline();
|
|
50760
|
+
ANOMALY_CATALOG = [
|
|
50761
|
+
{
|
|
50762
|
+
detectorId: PER_AGENT_ACTIVITY_DETECTOR_ID,
|
|
50763
|
+
classifierId: ROLLING_BASELINE_CLASSIFIER_ID,
|
|
50764
|
+
description: "Per-agent statistical drift detector: tool-call count, egress volume, credential-use rate, audit-event count, recent-receipt count over a 24h rolling window. Welford running mean + variance baseline per agent.",
|
|
50765
|
+
factory: () => new PerAgentActivityDetector()
|
|
50766
|
+
}
|
|
50767
|
+
];
|
|
50768
|
+
}
|
|
50769
|
+
});
|
|
50770
|
+
function anomalySubscriptionsPath(storagePath) {
|
|
50771
|
+
return path.join(storagePath, "anomaly-subscriptions.json");
|
|
50772
|
+
}
|
|
50773
|
+
async function loadAnomalySubscriptions(storagePath) {
|
|
50774
|
+
const filePath = anomalySubscriptionsPath(storagePath);
|
|
50775
|
+
try {
|
|
50776
|
+
const raw = await promises.readFile(filePath, "utf8");
|
|
50777
|
+
const parsed = JSON.parse(raw);
|
|
50778
|
+
if (parsed.version !== FILE_VERSION2) return [];
|
|
50779
|
+
if (!Array.isArray(parsed.subscribed)) return [];
|
|
50780
|
+
return parsed.subscribed.filter(
|
|
50781
|
+
(t) => t !== null && typeof t === "object" && typeof t.detector_id === "string" && typeof t.classifier_id === "string"
|
|
50782
|
+
);
|
|
50783
|
+
} catch {
|
|
50784
|
+
return [];
|
|
50785
|
+
}
|
|
50786
|
+
}
|
|
50787
|
+
async function saveAnomalySubscriptions(storagePath, subscriptions) {
|
|
50788
|
+
const filePath = anomalySubscriptionsPath(storagePath);
|
|
50789
|
+
await promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
50790
|
+
const payload = {
|
|
50791
|
+
version: FILE_VERSION2,
|
|
50792
|
+
// Deduplicate.
|
|
50793
|
+
subscribed: dedupe(subscriptions)
|
|
50794
|
+
};
|
|
50795
|
+
await promises.writeFile(filePath, JSON.stringify(payload, null, 2), {
|
|
50796
|
+
mode: 384
|
|
50797
|
+
});
|
|
50798
|
+
}
|
|
50799
|
+
function dedupe(items) {
|
|
50800
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50801
|
+
const out = [];
|
|
50802
|
+
for (const item of items) {
|
|
50803
|
+
const key = `${item.detector_id}|${item.classifier_id}`;
|
|
50804
|
+
if (seen.has(key)) continue;
|
|
50805
|
+
seen.add(key);
|
|
50806
|
+
out.push(item);
|
|
50807
|
+
}
|
|
50808
|
+
return out;
|
|
50809
|
+
}
|
|
50810
|
+
var FILE_VERSION2;
|
|
50811
|
+
var init_anomaly_subscription_store = __esm({
|
|
50812
|
+
"src/anomaly-detection/anomaly-subscription-store.ts"() {
|
|
50813
|
+
FILE_VERSION2 = 1;
|
|
50814
|
+
}
|
|
50815
|
+
});
|
|
50816
|
+
|
|
50817
|
+
// src/cli/anomaly.ts
|
|
50818
|
+
var anomaly_exports = {};
|
|
50819
|
+
__export(anomaly_exports, {
|
|
50820
|
+
runAnomalyCommand: () => runAnomalyCommand
|
|
50821
|
+
});
|
|
50822
|
+
async function runAnomalyCommand(args) {
|
|
50823
|
+
const out = args.out ?? process.stdout;
|
|
50824
|
+
const err = args.err ?? process.stderr;
|
|
50825
|
+
const [sub, ...rest] = args.argv;
|
|
50826
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
50827
|
+
printUsage8(out);
|
|
50828
|
+
return 0;
|
|
50829
|
+
}
|
|
50830
|
+
try {
|
|
50831
|
+
switch (sub) {
|
|
50832
|
+
case "detectors":
|
|
50833
|
+
return cmdDetectors(rest, { out });
|
|
50834
|
+
case "list-subscribed":
|
|
50835
|
+
return await cmdListSubscribed2({ out, args });
|
|
50836
|
+
case "subscribe":
|
|
50837
|
+
return await cmdSubscribe2(rest, { out, err, args });
|
|
50838
|
+
case "unsubscribe":
|
|
50839
|
+
return await cmdUnsubscribe2(rest, { out, err, args });
|
|
50840
|
+
case "findings":
|
|
50841
|
+
return await cmdFindings2(rest, { out, err, args });
|
|
50842
|
+
case "classifier-state":
|
|
50843
|
+
return await cmdClassifierState(rest, { out, err, args });
|
|
50844
|
+
default:
|
|
50845
|
+
err.write(`Unknown subcommand: ${sub}
|
|
50846
|
+
`);
|
|
50847
|
+
printUsage8(err);
|
|
50848
|
+
return 2;
|
|
50849
|
+
}
|
|
50850
|
+
} catch (e) {
|
|
50851
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
50852
|
+
err.write(`sanctuary anomaly: ${msg}
|
|
50853
|
+
`);
|
|
50854
|
+
return 1;
|
|
50855
|
+
}
|
|
50856
|
+
}
|
|
50857
|
+
function printUsage8(s) {
|
|
50858
|
+
s.write(`Usage: sanctuary anomaly <command> [args]
|
|
50859
|
+
|
|
50860
|
+
detectors list Catalog of available
|
|
50861
|
+
detector + classifier tuples.
|
|
50862
|
+
list-subscribed Subscriptions on this fortress.
|
|
50863
|
+
subscribe <detector-id> --classifier <id>
|
|
50864
|
+
Opt in. Writes the
|
|
50865
|
+
subscription file; the server
|
|
50866
|
+
picks it up on next boot.
|
|
50867
|
+
unsubscribe <detector-id> --classifier <id>
|
|
50868
|
+
Opt out.
|
|
50869
|
+
findings [opts] Read anomaly findings.
|
|
50870
|
+
--since <iso> observed_at >= iso.
|
|
50871
|
+
--severity <info|warn|alert> Filter by severity.
|
|
50872
|
+
--detector-id <id> Filter by emitting detector.
|
|
50873
|
+
--agent-id <id> Filter by agent attribution.
|
|
50874
|
+
--limit <n> Cap result count (default 100).
|
|
50875
|
+
findings show <finding-id> Full drift-inspector detail.
|
|
50876
|
+
classifier-state <detector-id> --classifier <id>
|
|
50877
|
+
Per-agent training state.
|
|
50878
|
+
`);
|
|
50879
|
+
}
|
|
50880
|
+
function flagValue4(argv, name) {
|
|
50881
|
+
const i = argv.indexOf(name);
|
|
50882
|
+
if (i === -1) return void 0;
|
|
50883
|
+
return argv[i + 1];
|
|
50884
|
+
}
|
|
50885
|
+
function cmdDetectors(argv, ctx) {
|
|
50886
|
+
const sub = argv[0];
|
|
50887
|
+
if (sub !== void 0 && sub !== "list") {
|
|
50888
|
+
ctx.out.write(`Unknown detectors subcommand: ${sub}
|
|
50889
|
+
`);
|
|
50890
|
+
return 2;
|
|
50891
|
+
}
|
|
50892
|
+
if (ANOMALY_CATALOG.length === 0) {
|
|
50893
|
+
ctx.out.write("(no detectors registered)\n");
|
|
50894
|
+
return 0;
|
|
50895
|
+
}
|
|
50896
|
+
for (const entry of ANOMALY_CATALOG) {
|
|
50897
|
+
ctx.out.write(
|
|
50898
|
+
`${entry.detectorId} [classifier: ${entry.classifierId}]
|
|
50899
|
+
${entry.description}
|
|
50900
|
+
`
|
|
50901
|
+
);
|
|
50902
|
+
}
|
|
50903
|
+
return 0;
|
|
50904
|
+
}
|
|
50905
|
+
async function cmdListSubscribed2(ctx) {
|
|
50906
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50907
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50908
|
+
if (subscribed.length === 0) {
|
|
50909
|
+
ctx.out.write("(no subscriptions)\n");
|
|
50910
|
+
return 0;
|
|
50911
|
+
}
|
|
50912
|
+
for (const t of subscribed) {
|
|
50913
|
+
ctx.out.write(`${t.detector_id} [classifier: ${t.classifier_id}]
|
|
50914
|
+
`);
|
|
50915
|
+
}
|
|
50916
|
+
return 0;
|
|
50917
|
+
}
|
|
50918
|
+
async function cmdSubscribe2(argv, ctx) {
|
|
50919
|
+
const detectorId = argv[0];
|
|
50920
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
50921
|
+
if (!detectorId) {
|
|
50922
|
+
ctx.err.write("subscribe requires a detector-id\n");
|
|
50923
|
+
return 2;
|
|
50924
|
+
}
|
|
50925
|
+
if (!classifierId) {
|
|
50926
|
+
ctx.err.write("subscribe requires --classifier <id>\n");
|
|
50927
|
+
return 2;
|
|
50928
|
+
}
|
|
50929
|
+
const entry = findCatalogEntry(detectorId, classifierId);
|
|
50930
|
+
if (!entry) {
|
|
50931
|
+
ctx.err.write(
|
|
50932
|
+
`Unknown detector/classifier pair: ${detectorId} / ${classifierId}
|
|
50933
|
+
`
|
|
50934
|
+
);
|
|
50935
|
+
return 2;
|
|
50936
|
+
}
|
|
50937
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50938
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50939
|
+
const exists = subscribed.some(
|
|
50940
|
+
(t) => t.detector_id === detectorId && t.classifier_id === classifierId
|
|
50941
|
+
);
|
|
50942
|
+
if (exists) {
|
|
50943
|
+
ctx.out.write(
|
|
50944
|
+
`Already subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50945
|
+
`
|
|
50946
|
+
);
|
|
50947
|
+
return 0;
|
|
50948
|
+
}
|
|
50949
|
+
subscribed.push({ detector_id: detectorId, classifier_id: classifierId });
|
|
50950
|
+
await saveAnomalySubscriptions(storagePath, subscribed);
|
|
50951
|
+
ctx.out.write(
|
|
50952
|
+
`Subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50953
|
+
Restart Sanctuary or wait for the next dispatcher tick.
|
|
50954
|
+
`
|
|
50955
|
+
);
|
|
50956
|
+
return 0;
|
|
50957
|
+
}
|
|
50958
|
+
async function cmdUnsubscribe2(argv, ctx) {
|
|
50959
|
+
const detectorId = argv[0];
|
|
50960
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
50961
|
+
if (!detectorId) {
|
|
50962
|
+
ctx.err.write("unsubscribe requires a detector-id\n");
|
|
50963
|
+
return 2;
|
|
50964
|
+
}
|
|
50965
|
+
if (!classifierId) {
|
|
50966
|
+
ctx.err.write("unsubscribe requires --classifier <id>\n");
|
|
50967
|
+
return 2;
|
|
50968
|
+
}
|
|
50969
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50970
|
+
const subscribed = await loadAnomalySubscriptions(storagePath);
|
|
50971
|
+
const filtered = subscribed.filter(
|
|
50972
|
+
(t) => !(t.detector_id === detectorId && t.classifier_id === classifierId)
|
|
50973
|
+
);
|
|
50974
|
+
if (filtered.length === subscribed.length) {
|
|
50975
|
+
ctx.out.write(
|
|
50976
|
+
`Not subscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50977
|
+
`
|
|
50978
|
+
);
|
|
50979
|
+
return 0;
|
|
50980
|
+
}
|
|
50981
|
+
await saveAnomalySubscriptions(storagePath, filtered);
|
|
50982
|
+
ctx.out.write(
|
|
50983
|
+
`Unsubscribed: ${detectorId} [classifier: ${classifierId}]
|
|
50984
|
+
`
|
|
50985
|
+
);
|
|
50986
|
+
return 0;
|
|
50987
|
+
}
|
|
50988
|
+
async function cmdFindings2(argv, ctx) {
|
|
50989
|
+
if (argv[0] === "show") {
|
|
50990
|
+
return await cmdFindingsShow(argv.slice(1), ctx);
|
|
50991
|
+
}
|
|
50992
|
+
const filters = parseFindingFilters2(argv);
|
|
50993
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
50994
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
50995
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
50996
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
50997
|
+
const store = new SentinelFindingStore({
|
|
50998
|
+
storage,
|
|
50999
|
+
masterKey,
|
|
51000
|
+
fortressId
|
|
51001
|
+
});
|
|
51002
|
+
const filterSentinelId = filters.detectorId !== void 0 ? `${ANOMALY_SENTINEL_ID_PREFIX}${filters.detectorId}` : void 0;
|
|
51003
|
+
const allFindings = await store.listFindings({
|
|
51004
|
+
limit: filters.limit ?? 100,
|
|
51005
|
+
...filters.since !== void 0 ? { since: filters.since } : {},
|
|
51006
|
+
...filters.severity !== void 0 ? { severity: filters.severity } : {},
|
|
51007
|
+
...filterSentinelId !== void 0 ? { sentinelId: filterSentinelId } : {},
|
|
51008
|
+
...filters.agentId !== void 0 ? { agentId: filters.agentId } : {}
|
|
51009
|
+
});
|
|
51010
|
+
const anomalyFindings = filterSentinelId !== void 0 ? allFindings : allFindings.filter(
|
|
51011
|
+
(f) => f.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)
|
|
51012
|
+
);
|
|
51013
|
+
if (anomalyFindings.length === 0) {
|
|
51014
|
+
ctx.out.write("(no findings)\n");
|
|
51015
|
+
return 0;
|
|
51016
|
+
}
|
|
51017
|
+
for (const finding of anomalyFindings) {
|
|
51018
|
+
const detectorId = finding.details["detector_id"] ?? "";
|
|
51019
|
+
const score = finding.details["anomaly_score"];
|
|
51020
|
+
const scoreStr = typeof score === "number" ? ` score=${score.toFixed(2)}` : "";
|
|
51021
|
+
ctx.out.write(
|
|
51022
|
+
`[${finding.observed_at}] ${finding.severity.toUpperCase()} ${detectorId}${finding.agent_id ? ` (agent ${finding.agent_id})` : ""}${scoreStr}: ${finding.summary}
|
|
51023
|
+
`
|
|
51024
|
+
);
|
|
51025
|
+
}
|
|
51026
|
+
return 0;
|
|
51027
|
+
}
|
|
51028
|
+
async function cmdFindingsShow(argv, ctx) {
|
|
51029
|
+
const findingId = argv[0];
|
|
51030
|
+
if (!findingId) {
|
|
51031
|
+
ctx.err.write("findings show requires a finding-id\n");
|
|
51032
|
+
return 2;
|
|
51033
|
+
}
|
|
51034
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
51035
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51036
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51037
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
51038
|
+
const store = new SentinelFindingStore({ storage, masterKey, fortressId });
|
|
51039
|
+
const finding = await store.loadFinding(findingId);
|
|
51040
|
+
if (!finding) {
|
|
51041
|
+
ctx.err.write(`Finding not found: ${findingId}
|
|
51042
|
+
`);
|
|
51043
|
+
return 1;
|
|
51044
|
+
}
|
|
51045
|
+
if (!finding.sentinel_id.startsWith(ANOMALY_SENTINEL_ID_PREFIX)) {
|
|
51046
|
+
ctx.err.write(
|
|
51047
|
+
`Finding ${findingId} is not an anomaly finding; try sanctuary sentinel findings.
|
|
51048
|
+
`
|
|
51049
|
+
);
|
|
51050
|
+
return 1;
|
|
51051
|
+
}
|
|
51052
|
+
ctx.out.write(JSON.stringify(finding, null, 2) + "\n");
|
|
51053
|
+
return 0;
|
|
51054
|
+
}
|
|
51055
|
+
async function cmdClassifierState(argv, ctx) {
|
|
51056
|
+
const detectorId = argv[0];
|
|
51057
|
+
const classifierId = flagValue4(argv, "--classifier");
|
|
51058
|
+
if (!detectorId) {
|
|
51059
|
+
ctx.err.write("classifier-state requires a detector-id\n");
|
|
51060
|
+
return 2;
|
|
51061
|
+
}
|
|
51062
|
+
if (!classifierId) {
|
|
51063
|
+
ctx.err.write("classifier-state requires --classifier <id>\n");
|
|
51064
|
+
return 2;
|
|
51065
|
+
}
|
|
51066
|
+
const entry = findCatalogEntry(detectorId, classifierId);
|
|
51067
|
+
if (!entry) {
|
|
51068
|
+
ctx.err.write(
|
|
51069
|
+
`Unknown detector/classifier pair: ${detectorId} / ${classifierId}
|
|
51070
|
+
`
|
|
51071
|
+
);
|
|
51072
|
+
return 2;
|
|
51073
|
+
}
|
|
51074
|
+
const masterKey = await deriveFortressMasterKey(ctx);
|
|
51075
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51076
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51077
|
+
const fortressId = fortressIdFromStoragePath(storagePath);
|
|
51078
|
+
const stateStore = new ClassifierStateStore({
|
|
51079
|
+
storage,
|
|
51080
|
+
masterKey,
|
|
51081
|
+
fortressId
|
|
51082
|
+
});
|
|
51083
|
+
const agentIds = await stateStore.listAgents(classifierId);
|
|
51084
|
+
if (agentIds.length === 0) {
|
|
51085
|
+
ctx.out.write("(no classifier state yet)\n");
|
|
51086
|
+
return 0;
|
|
51087
|
+
}
|
|
51088
|
+
for (const agentId of agentIds) {
|
|
51089
|
+
try {
|
|
51090
|
+
const raw = await stateStore.loadState(classifierId, agentId);
|
|
51091
|
+
if (raw === null) continue;
|
|
51092
|
+
const sampleCount = typeof raw.sample_count === "number" ? raw.sample_count : "?";
|
|
51093
|
+
ctx.out.write(`${agentId}: sample_count=${sampleCount}
|
|
51094
|
+
`);
|
|
51095
|
+
} catch {
|
|
51096
|
+
ctx.out.write(`${agentId}: (load failed)
|
|
51097
|
+
`);
|
|
51098
|
+
}
|
|
51099
|
+
}
|
|
51100
|
+
return 0;
|
|
51101
|
+
}
|
|
51102
|
+
function parseFindingFilters2(argv) {
|
|
51103
|
+
const filters = {};
|
|
51104
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
51105
|
+
const arg = argv[i];
|
|
51106
|
+
if (arg === "--since" && argv[i + 1]) {
|
|
51107
|
+
filters.since = argv[++i];
|
|
51108
|
+
} else if (arg === "--severity" && argv[i + 1]) {
|
|
51109
|
+
const next = argv[++i];
|
|
51110
|
+
if (next === "info" || next === "warn" || next === "alert") {
|
|
51111
|
+
filters.severity = next;
|
|
51112
|
+
}
|
|
51113
|
+
} else if (arg === "--detector-id" && argv[i + 1]) {
|
|
51114
|
+
filters.detectorId = argv[++i];
|
|
51115
|
+
} else if (arg === "--agent-id" && argv[i + 1]) {
|
|
51116
|
+
filters.agentId = argv[++i];
|
|
51117
|
+
} else if (arg === "--limit" && argv[i + 1]) {
|
|
51118
|
+
const n = Number.parseInt(argv[++i], 10);
|
|
51119
|
+
if (!Number.isNaN(n) && n > 0) filters.limit = n;
|
|
51120
|
+
}
|
|
51121
|
+
}
|
|
51122
|
+
return filters;
|
|
51123
|
+
}
|
|
51124
|
+
async function resolveStoragePath3(args) {
|
|
51125
|
+
if (args.storagePath) return args.storagePath;
|
|
51126
|
+
const config = await loadConfig();
|
|
51127
|
+
return config.storage_path;
|
|
51128
|
+
}
|
|
51129
|
+
async function deriveFortressMasterKey(ctx) {
|
|
51130
|
+
const storagePath = await resolveStoragePath3(ctx.args);
|
|
51131
|
+
const storage = new FilesystemStorage(`${storagePath}/state`);
|
|
51132
|
+
let passphrase = ctx.args.passphrase ?? process.env["SANCTUARY_PASSPHRASE"];
|
|
51133
|
+
if (!passphrase) {
|
|
51134
|
+
const resolved = await getOrCreatePassphrase();
|
|
51135
|
+
passphrase = resolved.value;
|
|
51136
|
+
}
|
|
51137
|
+
let existingParams;
|
|
51138
|
+
try {
|
|
51139
|
+
const raw = await storage.read("_meta", "key-params");
|
|
51140
|
+
if (raw) existingParams = JSON.parse(bytesToString(raw));
|
|
51141
|
+
} catch {
|
|
51142
|
+
}
|
|
51143
|
+
const { key: masterKey, params } = await deriveMasterKey(
|
|
51144
|
+
passphrase,
|
|
51145
|
+
existingParams
|
|
51146
|
+
);
|
|
51147
|
+
if (!existingParams) {
|
|
51148
|
+
await storage.write(
|
|
51149
|
+
"_meta",
|
|
51150
|
+
"key-params",
|
|
51151
|
+
stringToBytes(JSON.stringify(params))
|
|
51152
|
+
);
|
|
51153
|
+
}
|
|
51154
|
+
return masterKey;
|
|
51155
|
+
}
|
|
51156
|
+
var init_anomaly = __esm({
|
|
51157
|
+
"src/cli/anomaly.ts"() {
|
|
51158
|
+
init_config();
|
|
51159
|
+
init_filesystem();
|
|
51160
|
+
init_key_derivation();
|
|
51161
|
+
init_encoding();
|
|
51162
|
+
init_passphrase();
|
|
51163
|
+
init_wiring();
|
|
51164
|
+
init_sentinel_finding_store();
|
|
51165
|
+
init_anomaly_catalog();
|
|
51166
|
+
init_anomaly_subscription_store();
|
|
51167
|
+
init_classifier_state_store();
|
|
51168
|
+
init_types4();
|
|
51169
|
+
}
|
|
51170
|
+
});
|
|
51171
|
+
|
|
49003
51172
|
// src/mcp/broker-server.ts
|
|
49004
51173
|
var broker_server_exports = {};
|
|
49005
51174
|
__export(broker_server_exports, {
|
|
@@ -49869,6 +52038,11 @@ async function main() {
|
|
|
49869
52038
|
const code = await runDidWebCommand2({ argv: args.slice(1) });
|
|
49870
52039
|
process.exit(code);
|
|
49871
52040
|
}
|
|
52041
|
+
if (args[0] === "anomaly") {
|
|
52042
|
+
const { runAnomalyCommand: runAnomalyCommand2 } = await Promise.resolve().then(() => (init_anomaly(), anomaly_exports));
|
|
52043
|
+
const code = await runAnomalyCommand2({ argv: args.slice(1) });
|
|
52044
|
+
process.exit(code);
|
|
52045
|
+
}
|
|
49872
52046
|
if (args[0] === "broker-server") {
|
|
49873
52047
|
const { openBroker: openBroker2 } = await Promise.resolve().then(() => (init_open(), open_exports));
|
|
49874
52048
|
const { createBrokerMcpServer: createBrokerMcpServer2 } = await Promise.resolve().then(() => (init_broker_server(), broker_server_exports));
|