kontext-sdk 0.5.0 → 0.5.2
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/README.md +169 -60
- package/dist/cli.js +5452 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +5428 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +55 -159
- package/dist/index.d.ts +55 -159
- package/dist/index.js +317 -454
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -452
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -11
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var crypto$1 = require('crypto');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var fs4 = require('fs');
|
|
5
|
+
var path4 = require('path');
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
@@ -22,8 +22,15 @@ function _interopNamespace(e) {
|
|
|
22
22
|
return Object.freeze(n);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
var
|
|
26
|
-
var
|
|
25
|
+
var fs4__namespace = /*#__PURE__*/_interopNamespace(fs4);
|
|
26
|
+
var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
|
|
27
|
+
|
|
28
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
29
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
30
|
+
}) : x)(function(x) {
|
|
31
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
32
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
33
|
+
});
|
|
27
34
|
|
|
28
35
|
// src/utils.ts
|
|
29
36
|
function generateId() {
|
|
@@ -142,7 +149,7 @@ var TrustScorer = class {
|
|
|
142
149
|
const flagged = riskScore >= RISK_FLAG_THRESHOLD;
|
|
143
150
|
const recommendation = riskScore >= RISK_BLOCK_THRESHOLD ? "block" : riskScore >= RISK_REVIEW_THRESHOLD ? "review" : "approve";
|
|
144
151
|
return {
|
|
145
|
-
txHash: tx.txHash,
|
|
152
|
+
...tx.txHash ? { txHash: tx.txHash } : {},
|
|
146
153
|
riskScore,
|
|
147
154
|
riskLevel,
|
|
148
155
|
factors,
|
|
@@ -412,7 +419,7 @@ var TrustScorer = class {
|
|
|
412
419
|
return {
|
|
413
420
|
name: "amount_risk",
|
|
414
421
|
score,
|
|
415
|
-
description: `Transaction amount ${tx.amount} ${tx.token}`
|
|
422
|
+
description: `Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""}`
|
|
416
423
|
};
|
|
417
424
|
}
|
|
418
425
|
computeNewDestinationRisk(tx) {
|
|
@@ -520,6 +527,9 @@ var TrustScorer = class {
|
|
|
520
527
|
};
|
|
521
528
|
|
|
522
529
|
// src/types.ts
|
|
530
|
+
function isCryptoTransaction(input) {
|
|
531
|
+
return !!input.txHash && !!input.chain && !!input.token;
|
|
532
|
+
}
|
|
523
533
|
var KontextErrorCode = /* @__PURE__ */ ((KontextErrorCode2) => {
|
|
524
534
|
KontextErrorCode2["INITIALIZATION_ERROR"] = "INITIALIZATION_ERROR";
|
|
525
535
|
KontextErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
@@ -725,10 +735,10 @@ var AnomalyDetector = class {
|
|
|
725
735
|
return this.createAnomaly(
|
|
726
736
|
"unusualAmount",
|
|
727
737
|
amount > threshold * 5 ? "critical" : amount > threshold * 2 ? "high" : "medium",
|
|
728
|
-
`Transaction amount ${tx.amount} ${tx.token} exceeds threshold of ${this.thresholds.maxAmount}`,
|
|
738
|
+
`Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""} exceeds threshold of ${this.thresholds.maxAmount}`,
|
|
729
739
|
tx.agentId,
|
|
730
740
|
tx.id,
|
|
731
|
-
{ amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token }
|
|
741
|
+
{ amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token ?? tx.currency }
|
|
732
742
|
);
|
|
733
743
|
}
|
|
734
744
|
const history = this.store.getTransactionsByAgent(tx.agentId);
|
|
@@ -1160,6 +1170,18 @@ var DigestChain = class {
|
|
|
1160
1170
|
getTerminalDigest() {
|
|
1161
1171
|
return this.currentDigest;
|
|
1162
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Restore the terminal digest from persisted state.
|
|
1175
|
+
* Called after loading actions from storage so that new actions
|
|
1176
|
+
* chain correctly from the last stored digest instead of genesis.
|
|
1177
|
+
*
|
|
1178
|
+
* Does NOT reconstruct the links array — in-memory verification
|
|
1179
|
+
* via verify() will not work after restore. Use the stored action
|
|
1180
|
+
* digest/priorDigest fields for cross-process chain verification.
|
|
1181
|
+
*/
|
|
1182
|
+
restoreTerminalDigest(digest) {
|
|
1183
|
+
this.currentDigest = digest;
|
|
1184
|
+
}
|
|
1163
1185
|
/**
|
|
1164
1186
|
* Get the number of links in the chain.
|
|
1165
1187
|
*/
|
|
@@ -1427,6 +1449,7 @@ var ActionLogger = class {
|
|
|
1427
1449
|
async logTransaction(input) {
|
|
1428
1450
|
this.validateTransactionInput(input);
|
|
1429
1451
|
const correlationId = input.correlationId ?? generateId();
|
|
1452
|
+
const description = input.token && input.chain ? `${input.token} transfer of ${input.amount} on ${input.chain}` : `${input.currency ?? "USD"} payment of ${input.amount}${input.paymentMethod ? ` via ${input.paymentMethod}` : ""}`;
|
|
1430
1453
|
const record = {
|
|
1431
1454
|
id: generateId(),
|
|
1432
1455
|
timestamp: now(),
|
|
@@ -1435,16 +1458,19 @@ var ActionLogger = class {
|
|
|
1435
1458
|
...input.sessionId ? { sessionId: input.sessionId } : {},
|
|
1436
1459
|
correlationId,
|
|
1437
1460
|
type: "transaction",
|
|
1438
|
-
description
|
|
1461
|
+
description,
|
|
1439
1462
|
metadata: {
|
|
1440
1463
|
...input.metadata
|
|
1441
1464
|
},
|
|
1442
|
-
txHash: input.txHash,
|
|
1443
|
-
chain: input.chain,
|
|
1444
1465
|
amount: input.amount,
|
|
1445
|
-
token: input.token,
|
|
1446
1466
|
from: input.from,
|
|
1447
|
-
to: input.to
|
|
1467
|
+
to: input.to,
|
|
1468
|
+
...input.txHash ? { txHash: input.txHash } : {},
|
|
1469
|
+
...input.chain ? { chain: input.chain } : {},
|
|
1470
|
+
...input.token ? { token: input.token } : {},
|
|
1471
|
+
...input.currency ? { currency: input.currency } : {},
|
|
1472
|
+
...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
|
|
1473
|
+
...input.paymentReference ? { paymentReference: input.paymentReference } : {}
|
|
1448
1474
|
};
|
|
1449
1475
|
const link = this.digestChain.append(record);
|
|
1450
1476
|
record.digest = link.digest;
|
|
@@ -1505,6 +1531,13 @@ var ActionLogger = class {
|
|
|
1505
1531
|
verifyChain(actions) {
|
|
1506
1532
|
return this.digestChain.verify(actions);
|
|
1507
1533
|
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Restore chain state from persisted actions so that new actions
|
|
1536
|
+
* chain correctly across process boundaries.
|
|
1537
|
+
*/
|
|
1538
|
+
restoreChainState(terminalDigest) {
|
|
1539
|
+
this.digestChain.restoreTerminalDigest(terminalDigest);
|
|
1540
|
+
}
|
|
1508
1541
|
// --------------------------------------------------------------------------
|
|
1509
1542
|
// Private helpers
|
|
1510
1543
|
// --------------------------------------------------------------------------
|
|
@@ -1517,53 +1550,56 @@ var ActionLogger = class {
|
|
|
1517
1550
|
{ field: "amount", value: input.amount }
|
|
1518
1551
|
);
|
|
1519
1552
|
}
|
|
1520
|
-
if (!input.txHash || input.txHash.trim() === "") {
|
|
1521
|
-
throw new KontextError(
|
|
1522
|
-
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1523
|
-
"Transaction hash is required",
|
|
1524
|
-
{ field: "txHash" }
|
|
1525
|
-
);
|
|
1526
|
-
}
|
|
1527
1553
|
if (!input.from || input.from.trim() === "") {
|
|
1528
1554
|
throw new KontextError(
|
|
1529
1555
|
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1530
|
-
"Sender
|
|
1556
|
+
"Sender (from) is required",
|
|
1531
1557
|
{ field: "from" }
|
|
1532
1558
|
);
|
|
1533
1559
|
}
|
|
1534
1560
|
if (!input.to || input.to.trim() === "") {
|
|
1535
1561
|
throw new KontextError(
|
|
1536
1562
|
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1537
|
-
"Recipient
|
|
1563
|
+
"Recipient (to) is required",
|
|
1538
1564
|
{ field: "to" }
|
|
1539
1565
|
);
|
|
1540
1566
|
}
|
|
1541
|
-
const
|
|
1542
|
-
if (
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1567
|
+
const hasCryptoFields = input.txHash !== void 0 || input.chain !== void 0 || input.token !== void 0;
|
|
1568
|
+
if (hasCryptoFields) {
|
|
1569
|
+
if (!input.txHash || input.txHash.trim() === "") {
|
|
1570
|
+
throw new KontextError(
|
|
1571
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1572
|
+
"Transaction hash is required for crypto transactions",
|
|
1573
|
+
{ field: "txHash" }
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
const validChains = ["ethereum", "base", "polygon", "arbitrum", "optimism", "arc", "avalanche", "solana"];
|
|
1577
|
+
if (!input.chain || !validChains.includes(input.chain)) {
|
|
1578
|
+
throw new KontextError(
|
|
1579
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1580
|
+
`Invalid chain: ${input.chain}. Must be one of: ${validChains.join(", ")}`,
|
|
1581
|
+
{ field: "chain", value: input.chain }
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const validTokens = ["USDC", "USDT", "DAI", "EURC", "USDP", "USDG"];
|
|
1585
|
+
if (!input.token || !validTokens.includes(input.token)) {
|
|
1586
|
+
throw new KontextError(
|
|
1587
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1588
|
+
`Invalid token: ${input.token}. Must be one of: ${validTokens.join(", ")}`,
|
|
1589
|
+
{ field: "token", value: input.token }
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1556
1592
|
}
|
|
1557
1593
|
}
|
|
1558
1594
|
flushToFile(actions) {
|
|
1559
1595
|
const outputDir = this.config.localOutputDir ?? ".kontext";
|
|
1560
|
-
const logDir =
|
|
1596
|
+
const logDir = path4__namespace.join(outputDir, "logs");
|
|
1561
1597
|
try {
|
|
1562
|
-
|
|
1598
|
+
fs4__namespace.mkdirSync(logDir, { recursive: true });
|
|
1563
1599
|
const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
|
|
1564
|
-
const filePath =
|
|
1600
|
+
const filePath = path4__namespace.join(logDir, filename);
|
|
1565
1601
|
const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
|
|
1566
|
-
|
|
1602
|
+
fs4__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
1567
1603
|
} catch (error) {
|
|
1568
1604
|
this.emitLog("warn", "Failed to write log file", { error });
|
|
1569
1605
|
}
|
|
@@ -2012,7 +2048,7 @@ var AuditExporter = class {
|
|
|
2012
2048
|
if (options.agentIds && !options.agentIds.includes(tx.agentId)) {
|
|
2013
2049
|
return false;
|
|
2014
2050
|
}
|
|
2015
|
-
if (options.chains && !options.chains.includes(tx.chain)) {
|
|
2051
|
+
if (options.chains && (!tx.chain || !options.chains.includes(tx.chain))) {
|
|
2016
2052
|
return false;
|
|
2017
2053
|
}
|
|
2018
2054
|
return true;
|
|
@@ -2103,8 +2139,6 @@ var AuditExporter = class {
|
|
|
2103
2139
|
return sections.join("\n\n");
|
|
2104
2140
|
}
|
|
2105
2141
|
};
|
|
2106
|
-
|
|
2107
|
-
// src/integrations/usdc.ts
|
|
2108
2142
|
var USDC_CONTRACTS = {
|
|
2109
2143
|
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
2110
2144
|
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -2165,6 +2199,22 @@ var SANCTIONED_ADDRESSES = [
|
|
|
2165
2199
|
var SANCTIONED_SET = new Set(
|
|
2166
2200
|
SANCTIONED_ADDRESSES.map((addr) => addr.toLowerCase())
|
|
2167
2201
|
);
|
|
2202
|
+
function loadCachedSDN() {
|
|
2203
|
+
try {
|
|
2204
|
+
const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
|
|
2205
|
+
const cachePath = path4__namespace.join(dataDir, "ofac-sdn-cache.json");
|
|
2206
|
+
if (fs4__namespace.existsSync(cachePath)) {
|
|
2207
|
+
const cache = JSON.parse(fs4__namespace.readFileSync(cachePath, "utf-8"));
|
|
2208
|
+
if (Array.isArray(cache.addresses)) {
|
|
2209
|
+
for (const addr of cache.addresses) {
|
|
2210
|
+
SANCTIONED_SET.add(String(addr).toLowerCase());
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
} catch {
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
loadCachedSDN();
|
|
2168
2218
|
var ENHANCED_DUE_DILIGENCE_THRESHOLD = 3e3;
|
|
2169
2219
|
var REPORTING_THRESHOLD = 1e4;
|
|
2170
2220
|
var LARGE_TRANSACTION_THRESHOLD = 5e4;
|
|
@@ -2464,6 +2514,183 @@ var UsdcCompliance = class _UsdcCompliance {
|
|
|
2464
2514
|
}
|
|
2465
2515
|
};
|
|
2466
2516
|
|
|
2517
|
+
// src/integrations/payment-compliance.ts
|
|
2518
|
+
var ENHANCED_DUE_DILIGENCE_THRESHOLD2 = 3e3;
|
|
2519
|
+
var REPORTING_THRESHOLD2 = 1e4;
|
|
2520
|
+
var LARGE_TRANSACTION_THRESHOLD2 = 5e4;
|
|
2521
|
+
var NAME_MATCH_THRESHOLD = 0.85;
|
|
2522
|
+
var _screener = null;
|
|
2523
|
+
var _screenerLoaded = false;
|
|
2524
|
+
function getScreener() {
|
|
2525
|
+
if (!_screenerLoaded) {
|
|
2526
|
+
_screenerLoaded = true;
|
|
2527
|
+
try {
|
|
2528
|
+
const mod = __require("./ofac-sanctions.js");
|
|
2529
|
+
if (mod.OFACSanctionsScreener) {
|
|
2530
|
+
_screener = new mod.OFACSanctionsScreener();
|
|
2531
|
+
}
|
|
2532
|
+
} catch {
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
return _screener;
|
|
2536
|
+
}
|
|
2537
|
+
var PaymentCompliance = class _PaymentCompliance {
|
|
2538
|
+
/**
|
|
2539
|
+
* Run compliance checks on a general payment.
|
|
2540
|
+
*
|
|
2541
|
+
* @param input - Payment to evaluate
|
|
2542
|
+
* @returns UsdcComplianceCheck with pass/fail results and recommendations
|
|
2543
|
+
*/
|
|
2544
|
+
static checkPayment(input) {
|
|
2545
|
+
const checks = [];
|
|
2546
|
+
checks.push(_PaymentCompliance.checkAmountValid(input.amount));
|
|
2547
|
+
checks.push(_PaymentCompliance.checkEntityScreening(input.from, "sender"));
|
|
2548
|
+
checks.push(_PaymentCompliance.checkEntityScreening(input.to, "recipient"));
|
|
2549
|
+
checks.push(_PaymentCompliance.checkEnhancedDueDiligence(input.amount));
|
|
2550
|
+
checks.push(_PaymentCompliance.checkReportingThreshold(input.amount));
|
|
2551
|
+
const failedChecks = checks.filter((c) => !c.passed);
|
|
2552
|
+
const compliant = failedChecks.every((c) => c.severity === "low");
|
|
2553
|
+
const highestSeverity = failedChecks.reduce(
|
|
2554
|
+
(max, c) => {
|
|
2555
|
+
const order = ["low", "medium", "high", "critical"];
|
|
2556
|
+
return order.indexOf(c.severity) > order.indexOf(max) ? c.severity : max;
|
|
2557
|
+
},
|
|
2558
|
+
"low"
|
|
2559
|
+
);
|
|
2560
|
+
const recommendations = _PaymentCompliance.generateRecommendations(checks, input);
|
|
2561
|
+
return {
|
|
2562
|
+
compliant,
|
|
2563
|
+
checks,
|
|
2564
|
+
riskLevel: highestSeverity,
|
|
2565
|
+
recommendations
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
// --------------------------------------------------------------------------
|
|
2569
|
+
// Individual checks
|
|
2570
|
+
// --------------------------------------------------------------------------
|
|
2571
|
+
static checkAmountValid(amount) {
|
|
2572
|
+
const parsed = parseAmount(amount);
|
|
2573
|
+
const isValid = !isNaN(parsed) && parsed > 0;
|
|
2574
|
+
return {
|
|
2575
|
+
name: "amount_valid",
|
|
2576
|
+
passed: isValid,
|
|
2577
|
+
description: isValid ? `Payment amount ${amount} is valid` : `Payment amount ${amount} is invalid`,
|
|
2578
|
+
severity: isValid ? "low" : "critical"
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
static checkEntityScreening(entity, label) {
|
|
2582
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(entity)) {
|
|
2583
|
+
const sanctioned = UsdcCompliance.isSanctioned(entity);
|
|
2584
|
+
return {
|
|
2585
|
+
name: `entity_screening_${label}`,
|
|
2586
|
+
passed: !sanctioned,
|
|
2587
|
+
description: sanctioned ? `${label} address ${entity} matches OFAC SDN sanctioned address` : `${label} address passed sanctions screening`,
|
|
2588
|
+
severity: sanctioned ? "critical" : "low"
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
const screener = getScreener();
|
|
2592
|
+
if (!screener) {
|
|
2593
|
+
return {
|
|
2594
|
+
name: `entity_screening_${label}`,
|
|
2595
|
+
passed: true,
|
|
2596
|
+
description: `${label} "${entity}" \u2014 name-based OFAC screening not available (address screening active)`,
|
|
2597
|
+
severity: "low"
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
const rawMatches = screener.searchEntityName(entity, NAME_MATCH_THRESHOLD);
|
|
2601
|
+
const matches = rawMatches.filter(
|
|
2602
|
+
(m) => m.similarity >= NAME_MATCH_THRESHOLD && m.matchedOn.length >= 4
|
|
2603
|
+
);
|
|
2604
|
+
if (matches.length > 0) {
|
|
2605
|
+
const topMatch = matches[0];
|
|
2606
|
+
const isActive = topMatch.entity.list !== "DELISTED";
|
|
2607
|
+
if (isActive) {
|
|
2608
|
+
return {
|
|
2609
|
+
name: `entity_screening_${label}`,
|
|
2610
|
+
passed: false,
|
|
2611
|
+
description: `${label} "${entity}" matches OFAC SDN entity "${topMatch.matchedOn}" (${Math.round(topMatch.similarity * 100)}% match)`,
|
|
2612
|
+
severity: "critical"
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
return {
|
|
2616
|
+
name: `entity_screening_${label}`,
|
|
2617
|
+
passed: true,
|
|
2618
|
+
description: `${label} "${entity}" matches delisted entity "${topMatch.matchedOn}" \u2014 enhanced due diligence recommended`,
|
|
2619
|
+
severity: "medium"
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
return {
|
|
2623
|
+
name: `entity_screening_${label}`,
|
|
2624
|
+
passed: true,
|
|
2625
|
+
description: `${label} "${entity}" passed OFAC entity screening`,
|
|
2626
|
+
severity: "low"
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
static checkEnhancedDueDiligence(amount) {
|
|
2630
|
+
const parsed = parseAmount(amount);
|
|
2631
|
+
const requiresEdd = !isNaN(parsed) && parsed >= ENHANCED_DUE_DILIGENCE_THRESHOLD2;
|
|
2632
|
+
return {
|
|
2633
|
+
name: "enhanced_due_diligence",
|
|
2634
|
+
passed: true,
|
|
2635
|
+
description: requiresEdd ? `Amount ${amount} requires enhanced due diligence (threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2})` : `Amount ${amount} is below enhanced due diligence threshold`,
|
|
2636
|
+
severity: requiresEdd ? "medium" : "low"
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
static checkReportingThreshold(amount) {
|
|
2640
|
+
const parsed = parseAmount(amount);
|
|
2641
|
+
const requiresReporting = !isNaN(parsed) && parsed >= REPORTING_THRESHOLD2;
|
|
2642
|
+
const isLarge = !isNaN(parsed) && parsed >= LARGE_TRANSACTION_THRESHOLD2;
|
|
2643
|
+
let description;
|
|
2644
|
+
let severity;
|
|
2645
|
+
if (isLarge) {
|
|
2646
|
+
description = `Amount ${amount} is a large payment (>= ${LARGE_TRANSACTION_THRESHOLD2}) \u2014 requires enhanced monitoring`;
|
|
2647
|
+
severity = "high";
|
|
2648
|
+
} else if (requiresReporting) {
|
|
2649
|
+
description = `Amount ${amount} meets reporting threshold (>= ${REPORTING_THRESHOLD2})`;
|
|
2650
|
+
severity = "medium";
|
|
2651
|
+
} else {
|
|
2652
|
+
description = `Amount ${amount} is below reporting threshold`;
|
|
2653
|
+
severity = "low";
|
|
2654
|
+
}
|
|
2655
|
+
return {
|
|
2656
|
+
name: "reporting_threshold",
|
|
2657
|
+
passed: true,
|
|
2658
|
+
description,
|
|
2659
|
+
severity
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
// --------------------------------------------------------------------------
|
|
2663
|
+
// Recommendations
|
|
2664
|
+
// --------------------------------------------------------------------------
|
|
2665
|
+
static generateRecommendations(checks, input) {
|
|
2666
|
+
const recommendations = [];
|
|
2667
|
+
const amount = parseAmount(input.amount);
|
|
2668
|
+
for (const check of checks) {
|
|
2669
|
+
if (!check.passed && check.name.startsWith("entity_screening_")) {
|
|
2670
|
+
recommendations.push(
|
|
2671
|
+
`BLOCK: ${check.description}. Payment is prohibited under OFAC regulations.`
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (!isNaN(amount) && amount >= LARGE_TRANSACTION_THRESHOLD2) {
|
|
2676
|
+
recommendations.push(
|
|
2677
|
+
`Enhanced monitoring required for payment of ${input.amount} ${input.currency ?? "USD"} (above ${LARGE_TRANSACTION_THRESHOLD2} threshold).`
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
if (!isNaN(amount) && amount >= REPORTING_THRESHOLD2) {
|
|
2681
|
+
recommendations.push(
|
|
2682
|
+
`CTR reporting may be required for payment of ${input.amount} ${input.currency ?? "USD"} (meets ${REPORTING_THRESHOLD2} reporting threshold).`
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
if (!isNaN(amount) && amount >= ENHANCED_DUE_DILIGENCE_THRESHOLD2) {
|
|
2686
|
+
recommendations.push(
|
|
2687
|
+
`Enhanced due diligence recommended for payment of ${input.amount} ${input.currency ?? "USD"} (Travel Rule threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2}).`
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
return recommendations;
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
|
|
2467
2694
|
// src/plans.ts
|
|
2468
2695
|
var PLAN_LIMITS = {
|
|
2469
2696
|
free: 2e4,
|
|
@@ -2816,7 +3043,7 @@ var JsonFileExporter = class {
|
|
|
2816
3043
|
buffer = [];
|
|
2817
3044
|
bufferSize;
|
|
2818
3045
|
constructor(options) {
|
|
2819
|
-
this.outputDir =
|
|
3046
|
+
this.outputDir = path4__namespace.resolve(options?.outputDir ?? ".kontext/exports");
|
|
2820
3047
|
this.bufferSize = options?.bufferSize ?? 1;
|
|
2821
3048
|
}
|
|
2822
3049
|
async export(events) {
|
|
@@ -2831,11 +3058,11 @@ var JsonFileExporter = class {
|
|
|
2831
3058
|
const toWrite = [...this.buffer];
|
|
2832
3059
|
this.buffer = [];
|
|
2833
3060
|
try {
|
|
2834
|
-
|
|
3061
|
+
fs4__namespace.mkdirSync(this.outputDir, { recursive: true });
|
|
2835
3062
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2836
|
-
const filePath =
|
|
3063
|
+
const filePath = path4__namespace.join(this.outputDir, `events-${date}.jsonl`);
|
|
2837
3064
|
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2838
|
-
|
|
3065
|
+
fs4__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
2839
3066
|
} catch (error) {
|
|
2840
3067
|
console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
|
|
2841
3068
|
}
|
|
@@ -3174,7 +3401,7 @@ var Kontext = class _Kontext {
|
|
|
3174
3401
|
* @returns The created transaction record
|
|
3175
3402
|
*/
|
|
3176
3403
|
async logTransaction(input) {
|
|
3177
|
-
if (input.chain !== "base") {
|
|
3404
|
+
if (input.chain && input.chain !== "base") {
|
|
3178
3405
|
requirePlan("multi-chain", this.planManager.getTier());
|
|
3179
3406
|
}
|
|
3180
3407
|
this.validateMetadata(input.metadata);
|
|
@@ -3211,10 +3438,22 @@ var Kontext = class _Kontext {
|
|
|
3211
3438
|
/**
|
|
3212
3439
|
* Restore state from the attached storage adapter.
|
|
3213
3440
|
* Loads previously persisted actions, transactions, tasks, and anomalies.
|
|
3441
|
+
* Also restores the digest chain's terminal digest so that new actions
|
|
3442
|
+
* chain correctly across process boundaries.
|
|
3214
3443
|
* No-op if no storage adapter is configured.
|
|
3215
3444
|
*/
|
|
3216
3445
|
async restore() {
|
|
3217
3446
|
await this.store.restore();
|
|
3447
|
+
const actions = this.store.getActions();
|
|
3448
|
+
if (actions.length > 0) {
|
|
3449
|
+
const sorted = [...actions].sort(
|
|
3450
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
3451
|
+
);
|
|
3452
|
+
const lastAction = sorted[sorted.length - 1];
|
|
3453
|
+
if (lastAction.digest) {
|
|
3454
|
+
this.logger.restoreChainState(lastAction.digest);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3218
3457
|
}
|
|
3219
3458
|
// --------------------------------------------------------------------------
|
|
3220
3459
|
// Task Confirmation
|
|
@@ -3517,7 +3756,7 @@ var Kontext = class _Kontext {
|
|
|
3517
3756
|
*/
|
|
3518
3757
|
async verify(input) {
|
|
3519
3758
|
const transaction = await this.logTransaction(input);
|
|
3520
|
-
const compliance = UsdcCompliance.checkTransaction(input);
|
|
3759
|
+
const compliance = isCryptoTransaction(input) ? UsdcCompliance.checkTransaction(input) : PaymentCompliance.checkPayment(input);
|
|
3521
3760
|
let reasoningId;
|
|
3522
3761
|
if (input.reasoning) {
|
|
3523
3762
|
const entry = await this.logReasoning({
|
|
@@ -3546,18 +3785,22 @@ var Kontext = class _Kontext {
|
|
|
3546
3785
|
const threshold = parseAmount(this.config.approvalThreshold);
|
|
3547
3786
|
if (amount > threshold) {
|
|
3548
3787
|
requiresApproval = true;
|
|
3788
|
+
const label = input.token ? `${input.token} ${input.amount} transfer` : `${input.currency ?? "USD"} ${input.amount} payment`;
|
|
3549
3789
|
task = await this.createTask({
|
|
3550
|
-
description: `Approve ${
|
|
3790
|
+
description: `Approve ${label} from ${input.from} to ${input.to}`,
|
|
3551
3791
|
agentId: input.agentId,
|
|
3552
|
-
requiredEvidence: ["txHash"],
|
|
3792
|
+
requiredEvidence: input.txHash ? ["txHash"] : ["paymentReference"],
|
|
3553
3793
|
metadata: {
|
|
3554
|
-
txHash: input.txHash,
|
|
3555
|
-
chain: input.chain,
|
|
3556
3794
|
amount: input.amount,
|
|
3557
|
-
token: input.token,
|
|
3558
3795
|
from: input.from,
|
|
3559
3796
|
to: input.to,
|
|
3560
|
-
approvalThreshold: this.config.approvalThreshold
|
|
3797
|
+
approvalThreshold: this.config.approvalThreshold,
|
|
3798
|
+
...input.txHash ? { txHash: input.txHash } : {},
|
|
3799
|
+
...input.chain ? { chain: input.chain } : {},
|
|
3800
|
+
...input.token ? { token: input.token } : {},
|
|
3801
|
+
...input.currency ? { currency: input.currency } : {},
|
|
3802
|
+
...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
|
|
3803
|
+
...input.paymentReference ? { paymentReference: input.paymentReference } : {}
|
|
3561
3804
|
}
|
|
3562
3805
|
});
|
|
3563
3806
|
}
|
|
@@ -3656,7 +3899,7 @@ var Kontext = class _Kontext {
|
|
|
3656
3899
|
* and cryptographically verifies the digest chain integrity.
|
|
3657
3900
|
*
|
|
3658
3901
|
* The certificate includes: action/transaction/reasoning counts, digest chain
|
|
3659
|
-
* verification status (
|
|
3902
|
+
* verification status (patented), the agent's current trust
|
|
3660
3903
|
* score, and an overall compliance status. A SHA-256 content hash of the
|
|
3661
3904
|
* certificate itself is included for tamper-evidence.
|
|
3662
3905
|
*
|
|
@@ -3894,20 +4137,20 @@ var MemoryStorage = class {
|
|
|
3894
4137
|
var FileStorage = class {
|
|
3895
4138
|
baseDir;
|
|
3896
4139
|
constructor(baseDir) {
|
|
3897
|
-
this.baseDir =
|
|
4140
|
+
this.baseDir = path4__namespace.resolve(baseDir);
|
|
3898
4141
|
}
|
|
3899
4142
|
async save(key, data) {
|
|
3900
|
-
|
|
4143
|
+
fs4__namespace.mkdirSync(this.baseDir, { recursive: true });
|
|
3901
4144
|
const filePath = this.keyToPath(key);
|
|
3902
|
-
const dir =
|
|
3903
|
-
|
|
3904
|
-
|
|
4145
|
+
const dir = path4__namespace.dirname(filePath);
|
|
4146
|
+
fs4__namespace.mkdirSync(dir, { recursive: true });
|
|
4147
|
+
fs4__namespace.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
3905
4148
|
}
|
|
3906
4149
|
async load(key) {
|
|
3907
4150
|
const filePath = this.keyToPath(key);
|
|
3908
|
-
if (!
|
|
4151
|
+
if (!fs4__namespace.existsSync(filePath)) return null;
|
|
3909
4152
|
try {
|
|
3910
|
-
const raw =
|
|
4153
|
+
const raw = fs4__namespace.readFileSync(filePath, "utf-8");
|
|
3911
4154
|
return JSON.parse(raw);
|
|
3912
4155
|
} catch {
|
|
3913
4156
|
return null;
|
|
@@ -3915,12 +4158,12 @@ var FileStorage = class {
|
|
|
3915
4158
|
}
|
|
3916
4159
|
async delete(key) {
|
|
3917
4160
|
const filePath = this.keyToPath(key);
|
|
3918
|
-
if (
|
|
3919
|
-
|
|
4161
|
+
if (fs4__namespace.existsSync(filePath)) {
|
|
4162
|
+
fs4__namespace.unlinkSync(filePath);
|
|
3920
4163
|
}
|
|
3921
4164
|
}
|
|
3922
4165
|
async list(prefix) {
|
|
3923
|
-
if (!
|
|
4166
|
+
if (!fs4__namespace.existsSync(this.baseDir)) return [];
|
|
3924
4167
|
return this.listRecursive(this.baseDir, prefix);
|
|
3925
4168
|
}
|
|
3926
4169
|
/** Get the base directory path. */
|
|
@@ -3932,18 +4175,18 @@ var FileStorage = class {
|
|
|
3932
4175
|
// --------------------------------------------------------------------------
|
|
3933
4176
|
keyToPath(key) {
|
|
3934
4177
|
const safeName = key.replace(/[<>"|?*]/g, "_");
|
|
3935
|
-
return
|
|
4178
|
+
return path4__namespace.join(this.baseDir, `${safeName}.json`);
|
|
3936
4179
|
}
|
|
3937
4180
|
pathToKey(filePath) {
|
|
3938
|
-
const relative2 =
|
|
4181
|
+
const relative2 = path4__namespace.relative(this.baseDir, filePath);
|
|
3939
4182
|
return relative2.replace(/\.json$/, "");
|
|
3940
4183
|
}
|
|
3941
4184
|
listRecursive(dir, prefix) {
|
|
3942
4185
|
const keys = [];
|
|
3943
|
-
if (!
|
|
3944
|
-
const entries =
|
|
4186
|
+
if (!fs4__namespace.existsSync(dir)) return keys;
|
|
4187
|
+
const entries = fs4__namespace.readdirSync(dir, { withFileTypes: true });
|
|
3945
4188
|
for (const entry of entries) {
|
|
3946
|
-
const fullPath =
|
|
4189
|
+
const fullPath = path4__namespace.join(dir, entry.name);
|
|
3947
4190
|
if (entry.isDirectory()) {
|
|
3948
4191
|
keys.push(...this.listRecursive(fullPath, prefix));
|
|
3949
4192
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
@@ -3957,393 +4200,11 @@ var FileStorage = class {
|
|
|
3957
4200
|
}
|
|
3958
4201
|
};
|
|
3959
4202
|
|
|
3960
|
-
// src/storage-firestore.ts
|
|
3961
|
-
var FirestoreStorageAdapter = class {
|
|
3962
|
-
config;
|
|
3963
|
-
/** In-memory cache for load() — avoids re-fetching on every store.restore() */
|
|
3964
|
-
cache = /* @__PURE__ */ new Map();
|
|
3965
|
-
/** Token cache: avoid metadata server round-trips on every write */
|
|
3966
|
-
cachedToken = null;
|
|
3967
|
-
tokenExpiresAt = 0;
|
|
3968
|
-
constructor(config) {
|
|
3969
|
-
this.config = {
|
|
3970
|
-
gcpProjectId: config.gcpProjectId,
|
|
3971
|
-
userId: config.userId,
|
|
3972
|
-
accessToken: config.accessToken,
|
|
3973
|
-
databaseId: config.databaseId ?? "(default)",
|
|
3974
|
-
firestoreBaseUrl: config.firestoreBaseUrl ?? "https://firestore.googleapis.com",
|
|
3975
|
-
writeDocumentsIndividually: config.writeDocumentsIndividually ?? true
|
|
3976
|
-
};
|
|
3977
|
-
}
|
|
3978
|
-
// --------------------------------------------------------------------------
|
|
3979
|
-
// StorageAdapter interface
|
|
3980
|
-
// --------------------------------------------------------------------------
|
|
3981
|
-
/**
|
|
3982
|
-
* Save data under a Kontext storage key.
|
|
3983
|
-
*
|
|
3984
|
-
* The flat key space (kontext:actions, kontext:transactions, etc.) is mapped
|
|
3985
|
-
* to structured Firestore paths. List data (actions, transactions) is written
|
|
3986
|
-
* as individual documents under sub-collections for queryability.
|
|
3987
|
-
*/
|
|
3988
|
-
async save(key, data) {
|
|
3989
|
-
this.cache.set(key, data);
|
|
3990
|
-
if (key === "kontext:actions" && Array.isArray(data)) {
|
|
3991
|
-
await this.saveActionList(data);
|
|
3992
|
-
} else if (key === "kontext:transactions" && Array.isArray(data)) {
|
|
3993
|
-
await this.saveTransactionList(data);
|
|
3994
|
-
} else if (key === "kontext:tasks" && Array.isArray(data)) {
|
|
3995
|
-
await this.saveTaskList(data);
|
|
3996
|
-
} else if (key === "kontext:anomalies" && Array.isArray(data)) {
|
|
3997
|
-
await this.saveAnomalyList(data);
|
|
3998
|
-
} else {
|
|
3999
|
-
await this.saveDocument(this.keyToPath(key), data);
|
|
4000
|
-
}
|
|
4001
|
-
}
|
|
4002
|
-
/**
|
|
4003
|
-
* Load data for a Kontext storage key.
|
|
4004
|
-
* Returns cached data if available (populated by save()).
|
|
4005
|
-
* Falls back to Firestore fetch for cold starts.
|
|
4006
|
-
*/
|
|
4007
|
-
async load(key) {
|
|
4008
|
-
if (this.cache.has(key)) {
|
|
4009
|
-
return this.cache.get(key) ?? null;
|
|
4010
|
-
}
|
|
4011
|
-
if (key === "kontext:actions") {
|
|
4012
|
-
const actions = await this.loadActionList();
|
|
4013
|
-
this.cache.set(key, actions);
|
|
4014
|
-
return actions;
|
|
4015
|
-
} else if (key === "kontext:transactions") {
|
|
4016
|
-
const txs = await this.loadTransactionList();
|
|
4017
|
-
this.cache.set(key, txs);
|
|
4018
|
-
return txs;
|
|
4019
|
-
} else if (key === "kontext:tasks") {
|
|
4020
|
-
const tasks = await this.loadTaskList();
|
|
4021
|
-
this.cache.set(key, tasks);
|
|
4022
|
-
return tasks;
|
|
4023
|
-
} else if (key === "kontext:anomalies") {
|
|
4024
|
-
const anomalies = await this.loadAnomalyList();
|
|
4025
|
-
this.cache.set(key, anomalies);
|
|
4026
|
-
return anomalies;
|
|
4027
|
-
} else {
|
|
4028
|
-
return this.loadDocument(this.keyToPath(key));
|
|
4029
|
-
}
|
|
4030
|
-
}
|
|
4031
|
-
async delete(key) {
|
|
4032
|
-
this.cache.delete(key);
|
|
4033
|
-
await this.deleteDocument(this.keyToPath(key));
|
|
4034
|
-
}
|
|
4035
|
-
async list(prefix) {
|
|
4036
|
-
const all = Array.from(this.cache.keys());
|
|
4037
|
-
if (!prefix) return all;
|
|
4038
|
-
return all.filter((k) => k.startsWith(prefix));
|
|
4039
|
-
}
|
|
4040
|
-
// --------------------------------------------------------------------------
|
|
4041
|
-
// Structured write methods (per-record documents)
|
|
4042
|
-
// --------------------------------------------------------------------------
|
|
4043
|
-
/**
|
|
4044
|
-
* Write a single action log to its canonical Firestore path.
|
|
4045
|
-
* Called by the SDK when `writeDocumentsIndividually` is true.
|
|
4046
|
-
*
|
|
4047
|
-
* Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/actions/{actionId}
|
|
4048
|
-
*/
|
|
4049
|
-
async writeAction(action) {
|
|
4050
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4051
|
-
const path4 = this.actionPath(action);
|
|
4052
|
-
await this.saveDocument(path4, action);
|
|
4053
|
-
}
|
|
4054
|
-
/**
|
|
4055
|
-
* Write a single transaction record to its canonical Firestore path.
|
|
4056
|
-
*
|
|
4057
|
-
* Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/transactions/{txId}
|
|
4058
|
-
*/
|
|
4059
|
-
async writeTransaction(tx) {
|
|
4060
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4061
|
-
const path4 = this.transactionPath(tx);
|
|
4062
|
-
await this.saveDocument(path4, tx);
|
|
4063
|
-
}
|
|
4064
|
-
/**
|
|
4065
|
-
* Write a single task to its canonical Firestore path.
|
|
4066
|
-
*
|
|
4067
|
-
* Path: users/{userId}/projects/{projectId}/tasks/{taskId}
|
|
4068
|
-
*/
|
|
4069
|
-
async writeTask(task) {
|
|
4070
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4071
|
-
const path4 = this.taskPath(task.id);
|
|
4072
|
-
await this.saveDocument(path4, task);
|
|
4073
|
-
}
|
|
4074
|
-
// --------------------------------------------------------------------------
|
|
4075
|
-
// Path builders
|
|
4076
|
-
// --------------------------------------------------------------------------
|
|
4077
|
-
get basePath() {
|
|
4078
|
-
return `users/${this.config.userId}/projects`;
|
|
4079
|
-
}
|
|
4080
|
-
actionPath(action) {
|
|
4081
|
-
const sessionId = action.sessionId ?? "_default";
|
|
4082
|
-
return `${this.basePath}/${action.projectId}/agents/${sanitize(action.agentId)}/sessions/${sanitize(sessionId)}/actions/${action.id}`;
|
|
4083
|
-
}
|
|
4084
|
-
transactionPath(tx) {
|
|
4085
|
-
const sessionId = tx.sessionId ?? "_default";
|
|
4086
|
-
return `${this.basePath}/${tx.projectId}/agents/${sanitize(tx.agentId)}/sessions/${sanitize(sessionId)}/transactions/${tx.id}`;
|
|
4087
|
-
}
|
|
4088
|
-
taskPath(taskId) {
|
|
4089
|
-
return `${this.basePath}/_tasks/${taskId}`;
|
|
4090
|
-
}
|
|
4091
|
-
anomalyPath(anomalyId, agentId) {
|
|
4092
|
-
return `${this.basePath}/_anomalies/agents/${sanitize(agentId)}/${anomalyId}`;
|
|
4093
|
-
}
|
|
4094
|
-
keyToPath(key) {
|
|
4095
|
-
const safe = key.replace(/:/g, "/").replace(/[^a-zA-Z0-9/_-]/g, "_");
|
|
4096
|
-
return `${this.basePath}/_meta/${safe}`;
|
|
4097
|
-
}
|
|
4098
|
-
// --------------------------------------------------------------------------
|
|
4099
|
-
// Bulk save/load (called by KontextStore.flush / restore)
|
|
4100
|
-
// --------------------------------------------------------------------------
|
|
4101
|
-
async saveActionList(actions) {
|
|
4102
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4103
|
-
await this.saveDocument(`${this.basePath}/_snapshots/actions`, actions);
|
|
4104
|
-
return;
|
|
4105
|
-
}
|
|
4106
|
-
await Promise.allSettled(actions.map((a) => this.saveDocument(this.actionPath(a), a)));
|
|
4107
|
-
}
|
|
4108
|
-
async saveTransactionList(txs) {
|
|
4109
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4110
|
-
await this.saveDocument(`${this.basePath}/_snapshots/transactions`, txs);
|
|
4111
|
-
return;
|
|
4112
|
-
}
|
|
4113
|
-
await Promise.allSettled(txs.map((tx) => this.saveDocument(this.transactionPath(tx), tx)));
|
|
4114
|
-
}
|
|
4115
|
-
async saveTaskList(entries) {
|
|
4116
|
-
await Promise.allSettled(
|
|
4117
|
-
entries.map(([_id, task]) => this.saveDocument(this.taskPath(task.id), task))
|
|
4118
|
-
);
|
|
4119
|
-
}
|
|
4120
|
-
async saveAnomalyList(anomalies) {
|
|
4121
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4122
|
-
await this.saveDocument(`${this.basePath}/_snapshots/anomalies`, anomalies);
|
|
4123
|
-
return;
|
|
4124
|
-
}
|
|
4125
|
-
await Promise.allSettled(
|
|
4126
|
-
anomalies.map((a) => this.saveDocument(this.anomalyPath(a.id, a.agentId), a))
|
|
4127
|
-
);
|
|
4128
|
-
}
|
|
4129
|
-
async loadActionList() {
|
|
4130
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4131
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/actions`);
|
|
4132
|
-
return Array.isArray(snap) ? snap : [];
|
|
4133
|
-
}
|
|
4134
|
-
return this.queryCollectionGroup("actions");
|
|
4135
|
-
}
|
|
4136
|
-
async loadTransactionList() {
|
|
4137
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4138
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/transactions`);
|
|
4139
|
-
return Array.isArray(snap) ? snap : [];
|
|
4140
|
-
}
|
|
4141
|
-
return this.queryCollectionGroup("transactions");
|
|
4142
|
-
}
|
|
4143
|
-
async loadTaskList() {
|
|
4144
|
-
const tasks = await this.queryCollection(`${this.basePath}/_tasks`);
|
|
4145
|
-
return tasks.map((t) => [t.id, t]);
|
|
4146
|
-
}
|
|
4147
|
-
async loadAnomalyList() {
|
|
4148
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4149
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/anomalies`);
|
|
4150
|
-
return Array.isArray(snap) ? snap : [];
|
|
4151
|
-
}
|
|
4152
|
-
return this.queryCollectionGroup("anomalies");
|
|
4153
|
-
}
|
|
4154
|
-
// --------------------------------------------------------------------------
|
|
4155
|
-
// Firestore REST API helpers
|
|
4156
|
-
// --------------------------------------------------------------------------
|
|
4157
|
-
firestoreBase() {
|
|
4158
|
-
return `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
|
|
4159
|
-
}
|
|
4160
|
-
/** Save an arbitrary JS object as a Firestore document at the given path. */
|
|
4161
|
-
async saveDocument(fsPath, data) {
|
|
4162
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4163
|
-
const token = await this.getToken();
|
|
4164
|
-
const body = JSON.stringify({ fields: toFirestoreFields(data) });
|
|
4165
|
-
const res = await fetch(url, {
|
|
4166
|
-
method: "PATCH",
|
|
4167
|
-
headers: {
|
|
4168
|
-
"Content-Type": "application/json",
|
|
4169
|
-
Authorization: `Bearer ${token}`
|
|
4170
|
-
},
|
|
4171
|
-
body
|
|
4172
|
-
});
|
|
4173
|
-
if (!res.ok) {
|
|
4174
|
-
const text = await res.text().catch(() => "");
|
|
4175
|
-
throw new Error(`Firestore write failed [${res.status}] ${fsPath}: ${text}`);
|
|
4176
|
-
}
|
|
4177
|
-
}
|
|
4178
|
-
/** Load a document at the given Firestore path. Returns null if not found. */
|
|
4179
|
-
async loadDocument(fsPath) {
|
|
4180
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4181
|
-
const token = await this.getToken();
|
|
4182
|
-
const res = await fetch(url, {
|
|
4183
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4184
|
-
});
|
|
4185
|
-
if (res.status === 404) return null;
|
|
4186
|
-
if (!res.ok) return null;
|
|
4187
|
-
const doc = await res.json();
|
|
4188
|
-
return fromFirestoreFields(doc.fields);
|
|
4189
|
-
}
|
|
4190
|
-
/** Delete a document at the given Firestore path. */
|
|
4191
|
-
async deleteDocument(fsPath) {
|
|
4192
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4193
|
-
const token = await this.getToken();
|
|
4194
|
-
await fetch(url, {
|
|
4195
|
-
method: "DELETE",
|
|
4196
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4197
|
-
});
|
|
4198
|
-
}
|
|
4199
|
-
/**
|
|
4200
|
-
* List all documents in a collection and deserialize them.
|
|
4201
|
-
* Used for tasks (simple flat collection).
|
|
4202
|
-
*/
|
|
4203
|
-
async queryCollection(collectionPath) {
|
|
4204
|
-
const url = `${this.firestoreBase()}/${collectionPath}`;
|
|
4205
|
-
const token = await this.getToken();
|
|
4206
|
-
const res = await fetch(url, {
|
|
4207
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4208
|
-
});
|
|
4209
|
-
if (!res.ok) return [];
|
|
4210
|
-
const body = await res.json();
|
|
4211
|
-
if (!body.documents) return [];
|
|
4212
|
-
return body.documents.map((doc) => fromFirestoreFields(doc.fields));
|
|
4213
|
-
}
|
|
4214
|
-
/**
|
|
4215
|
-
* Run a Firestore collection group query to fetch documents across all
|
|
4216
|
-
* nested sub-collections with the given name (e.g., 'actions' across all
|
|
4217
|
-
* agents and sessions).
|
|
4218
|
-
*
|
|
4219
|
-
* Scoped to the user's project root to prevent cross-tenant reads.
|
|
4220
|
-
*/
|
|
4221
|
-
async queryCollectionGroup(collectionId) {
|
|
4222
|
-
const parent = `projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
|
|
4223
|
-
`${this.config.firestoreBaseUrl}/v1/${parent}:runQuery`;
|
|
4224
|
-
const token = await this.getToken();
|
|
4225
|
-
const scopedUrl = `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents/users/${this.config.userId}:runQuery`;
|
|
4226
|
-
const body = {
|
|
4227
|
-
structuredQuery: {
|
|
4228
|
-
from: [{ collectionId, allDescendants: true }],
|
|
4229
|
-
orderBy: [{ field: { fieldPath: "timestamp" }, direction: "ASCENDING" }]
|
|
4230
|
-
}
|
|
4231
|
-
};
|
|
4232
|
-
const res = await fetch(scopedUrl, {
|
|
4233
|
-
method: "POST",
|
|
4234
|
-
headers: {
|
|
4235
|
-
"Content-Type": "application/json",
|
|
4236
|
-
Authorization: `Bearer ${token}`
|
|
4237
|
-
},
|
|
4238
|
-
body: JSON.stringify(body)
|
|
4239
|
-
});
|
|
4240
|
-
if (!res.ok) return [];
|
|
4241
|
-
const results = await res.json();
|
|
4242
|
-
return results.filter((r) => r.document?.fields).map((r) => fromFirestoreFields(r.document.fields));
|
|
4243
|
-
}
|
|
4244
|
-
// --------------------------------------------------------------------------
|
|
4245
|
-
// Auth: GCP metadata server + token caching
|
|
4246
|
-
// --------------------------------------------------------------------------
|
|
4247
|
-
async getToken() {
|
|
4248
|
-
if (this.config.accessToken) {
|
|
4249
|
-
return this.config.accessToken;
|
|
4250
|
-
}
|
|
4251
|
-
const now2 = Date.now();
|
|
4252
|
-
if (this.cachedToken && now2 < this.tokenExpiresAt - 3e5) {
|
|
4253
|
-
return this.cachedToken;
|
|
4254
|
-
}
|
|
4255
|
-
const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
|
4256
|
-
const res = await fetch(metadataUrl, {
|
|
4257
|
-
headers: { "Metadata-Flavor": "Google" }
|
|
4258
|
-
});
|
|
4259
|
-
if (!res.ok) {
|
|
4260
|
-
throw new Error(
|
|
4261
|
-
"FirestoreStorageAdapter: Could not fetch GCP access token. On GCP, ensure the service account has Firestore write access. For local dev, pass accessToken in FirestoreStorageConfig."
|
|
4262
|
-
);
|
|
4263
|
-
}
|
|
4264
|
-
const data = await res.json();
|
|
4265
|
-
this.cachedToken = data.access_token;
|
|
4266
|
-
this.tokenExpiresAt = now2 + data.expires_in * 1e3;
|
|
4267
|
-
return this.cachedToken;
|
|
4268
|
-
}
|
|
4269
|
-
};
|
|
4270
|
-
function toFirestoreValue(value) {
|
|
4271
|
-
if (value === null || value === void 0) {
|
|
4272
|
-
return { nullValue: null };
|
|
4273
|
-
}
|
|
4274
|
-
if (typeof value === "boolean") {
|
|
4275
|
-
return { booleanValue: value };
|
|
4276
|
-
}
|
|
4277
|
-
if (typeof value === "number") {
|
|
4278
|
-
if (Number.isInteger(value)) {
|
|
4279
|
-
return { integerValue: String(value) };
|
|
4280
|
-
}
|
|
4281
|
-
return { doubleValue: value };
|
|
4282
|
-
}
|
|
4283
|
-
if (typeof value === "string") {
|
|
4284
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
4285
|
-
return { timestampValue: value };
|
|
4286
|
-
}
|
|
4287
|
-
return { stringValue: value };
|
|
4288
|
-
}
|
|
4289
|
-
if (Array.isArray(value)) {
|
|
4290
|
-
return {
|
|
4291
|
-
arrayValue: {
|
|
4292
|
-
values: value.map(toFirestoreValue)
|
|
4293
|
-
}
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4296
|
-
if (typeof value === "object") {
|
|
4297
|
-
return {
|
|
4298
|
-
mapValue: {
|
|
4299
|
-
fields: toFirestoreFields(value)
|
|
4300
|
-
}
|
|
4301
|
-
};
|
|
4302
|
-
}
|
|
4303
|
-
return { stringValue: String(value) };
|
|
4304
|
-
}
|
|
4305
|
-
function toFirestoreFields(obj) {
|
|
4306
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
4307
|
-
return {};
|
|
4308
|
-
}
|
|
4309
|
-
const result = {};
|
|
4310
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
4311
|
-
result[k] = toFirestoreValue(v);
|
|
4312
|
-
}
|
|
4313
|
-
return result;
|
|
4314
|
-
}
|
|
4315
|
-
function fromFirestoreValue(value) {
|
|
4316
|
-
if ("nullValue" in value) return null;
|
|
4317
|
-
if ("booleanValue" in value) return value.booleanValue;
|
|
4318
|
-
if ("integerValue" in value) return parseInt(value.integerValue, 10);
|
|
4319
|
-
if ("doubleValue" in value) return value.doubleValue;
|
|
4320
|
-
if ("stringValue" in value) return value.stringValue;
|
|
4321
|
-
if ("timestampValue" in value) return value.timestampValue;
|
|
4322
|
-
if ("arrayValue" in value) {
|
|
4323
|
-
return (value.arrayValue.values ?? []).map(fromFirestoreValue);
|
|
4324
|
-
}
|
|
4325
|
-
if ("mapValue" in value) {
|
|
4326
|
-
return fromFirestoreFields(value.mapValue.fields);
|
|
4327
|
-
}
|
|
4328
|
-
return null;
|
|
4329
|
-
}
|
|
4330
|
-
function fromFirestoreFields(fields) {
|
|
4331
|
-
const result = {};
|
|
4332
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
4333
|
-
result[k] = fromFirestoreValue(v);
|
|
4334
|
-
}
|
|
4335
|
-
return result;
|
|
4336
|
-
}
|
|
4337
|
-
function sanitize(id) {
|
|
4338
|
-
return id.replace(/[/.]/g, "_").slice(0, 1500);
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
4203
|
exports.AnomalyDetector = AnomalyDetector;
|
|
4342
4204
|
exports.ConsoleExporter = ConsoleExporter;
|
|
4343
4205
|
exports.DigestChain = DigestChain;
|
|
4344
4206
|
exports.FeatureFlagManager = FeatureFlagManager;
|
|
4345
4207
|
exports.FileStorage = FileStorage;
|
|
4346
|
-
exports.FirestoreStorageAdapter = FirestoreStorageAdapter;
|
|
4347
4208
|
exports.JsonFileExporter = JsonFileExporter;
|
|
4348
4209
|
exports.Kontext = Kontext;
|
|
4349
4210
|
exports.KontextError = KontextError;
|
|
@@ -4351,9 +4212,11 @@ exports.KontextErrorCode = KontextErrorCode;
|
|
|
4351
4212
|
exports.MemoryStorage = MemoryStorage;
|
|
4352
4213
|
exports.NoopExporter = NoopExporter;
|
|
4353
4214
|
exports.PLAN_LIMITS = PLAN_LIMITS;
|
|
4215
|
+
exports.PaymentCompliance = PaymentCompliance;
|
|
4354
4216
|
exports.PlanManager = PlanManager;
|
|
4355
4217
|
exports.TrustScorer = TrustScorer;
|
|
4356
4218
|
exports.UsdcCompliance = UsdcCompliance;
|
|
4219
|
+
exports.isCryptoTransaction = isCryptoTransaction;
|
|
4357
4220
|
exports.isFeatureAvailable = isFeatureAvailable;
|
|
4358
4221
|
exports.requirePlan = requirePlan;
|
|
4359
4222
|
exports.verifyExportedChain = verifyExportedChain;
|