kontext-sdk 0.5.0 → 0.6.0
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/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 +12 -10
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs4 from 'fs';
|
|
3
|
+
import * as path4 from 'path';
|
|
4
|
+
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
4
11
|
|
|
5
12
|
// src/utils.ts
|
|
6
13
|
function generateId() {
|
|
@@ -119,7 +126,7 @@ var TrustScorer = class {
|
|
|
119
126
|
const flagged = riskScore >= RISK_FLAG_THRESHOLD;
|
|
120
127
|
const recommendation = riskScore >= RISK_BLOCK_THRESHOLD ? "block" : riskScore >= RISK_REVIEW_THRESHOLD ? "review" : "approve";
|
|
121
128
|
return {
|
|
122
|
-
txHash: tx.txHash,
|
|
129
|
+
...tx.txHash ? { txHash: tx.txHash } : {},
|
|
123
130
|
riskScore,
|
|
124
131
|
riskLevel,
|
|
125
132
|
factors,
|
|
@@ -389,7 +396,7 @@ var TrustScorer = class {
|
|
|
389
396
|
return {
|
|
390
397
|
name: "amount_risk",
|
|
391
398
|
score,
|
|
392
|
-
description: `Transaction amount ${tx.amount} ${tx.token}`
|
|
399
|
+
description: `Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""}`
|
|
393
400
|
};
|
|
394
401
|
}
|
|
395
402
|
computeNewDestinationRisk(tx) {
|
|
@@ -497,6 +504,9 @@ var TrustScorer = class {
|
|
|
497
504
|
};
|
|
498
505
|
|
|
499
506
|
// src/types.ts
|
|
507
|
+
function isCryptoTransaction(input) {
|
|
508
|
+
return !!input.txHash && !!input.chain && !!input.token;
|
|
509
|
+
}
|
|
500
510
|
var KontextErrorCode = /* @__PURE__ */ ((KontextErrorCode2) => {
|
|
501
511
|
KontextErrorCode2["INITIALIZATION_ERROR"] = "INITIALIZATION_ERROR";
|
|
502
512
|
KontextErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
@@ -702,10 +712,10 @@ var AnomalyDetector = class {
|
|
|
702
712
|
return this.createAnomaly(
|
|
703
713
|
"unusualAmount",
|
|
704
714
|
amount > threshold * 5 ? "critical" : amount > threshold * 2 ? "high" : "medium",
|
|
705
|
-
`Transaction amount ${tx.amount} ${tx.token} exceeds threshold of ${this.thresholds.maxAmount}`,
|
|
715
|
+
`Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""} exceeds threshold of ${this.thresholds.maxAmount}`,
|
|
706
716
|
tx.agentId,
|
|
707
717
|
tx.id,
|
|
708
|
-
{ amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token }
|
|
718
|
+
{ amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token ?? tx.currency }
|
|
709
719
|
);
|
|
710
720
|
}
|
|
711
721
|
const history = this.store.getTransactionsByAgent(tx.agentId);
|
|
@@ -1137,6 +1147,18 @@ var DigestChain = class {
|
|
|
1137
1147
|
getTerminalDigest() {
|
|
1138
1148
|
return this.currentDigest;
|
|
1139
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Restore the terminal digest from persisted state.
|
|
1152
|
+
* Called after loading actions from storage so that new actions
|
|
1153
|
+
* chain correctly from the last stored digest instead of genesis.
|
|
1154
|
+
*
|
|
1155
|
+
* Does NOT reconstruct the links array — in-memory verification
|
|
1156
|
+
* via verify() will not work after restore. Use the stored action
|
|
1157
|
+
* digest/priorDigest fields for cross-process chain verification.
|
|
1158
|
+
*/
|
|
1159
|
+
restoreTerminalDigest(digest) {
|
|
1160
|
+
this.currentDigest = digest;
|
|
1161
|
+
}
|
|
1140
1162
|
/**
|
|
1141
1163
|
* Get the number of links in the chain.
|
|
1142
1164
|
*/
|
|
@@ -1404,6 +1426,7 @@ var ActionLogger = class {
|
|
|
1404
1426
|
async logTransaction(input) {
|
|
1405
1427
|
this.validateTransactionInput(input);
|
|
1406
1428
|
const correlationId = input.correlationId ?? generateId();
|
|
1429
|
+
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}` : ""}`;
|
|
1407
1430
|
const record = {
|
|
1408
1431
|
id: generateId(),
|
|
1409
1432
|
timestamp: now(),
|
|
@@ -1412,16 +1435,19 @@ var ActionLogger = class {
|
|
|
1412
1435
|
...input.sessionId ? { sessionId: input.sessionId } : {},
|
|
1413
1436
|
correlationId,
|
|
1414
1437
|
type: "transaction",
|
|
1415
|
-
description
|
|
1438
|
+
description,
|
|
1416
1439
|
metadata: {
|
|
1417
1440
|
...input.metadata
|
|
1418
1441
|
},
|
|
1419
|
-
txHash: input.txHash,
|
|
1420
|
-
chain: input.chain,
|
|
1421
1442
|
amount: input.amount,
|
|
1422
|
-
token: input.token,
|
|
1423
1443
|
from: input.from,
|
|
1424
|
-
to: input.to
|
|
1444
|
+
to: input.to,
|
|
1445
|
+
...input.txHash ? { txHash: input.txHash } : {},
|
|
1446
|
+
...input.chain ? { chain: input.chain } : {},
|
|
1447
|
+
...input.token ? { token: input.token } : {},
|
|
1448
|
+
...input.currency ? { currency: input.currency } : {},
|
|
1449
|
+
...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
|
|
1450
|
+
...input.paymentReference ? { paymentReference: input.paymentReference } : {}
|
|
1425
1451
|
};
|
|
1426
1452
|
const link = this.digestChain.append(record);
|
|
1427
1453
|
record.digest = link.digest;
|
|
@@ -1482,6 +1508,13 @@ var ActionLogger = class {
|
|
|
1482
1508
|
verifyChain(actions) {
|
|
1483
1509
|
return this.digestChain.verify(actions);
|
|
1484
1510
|
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Restore chain state from persisted actions so that new actions
|
|
1513
|
+
* chain correctly across process boundaries.
|
|
1514
|
+
*/
|
|
1515
|
+
restoreChainState(terminalDigest) {
|
|
1516
|
+
this.digestChain.restoreTerminalDigest(terminalDigest);
|
|
1517
|
+
}
|
|
1485
1518
|
// --------------------------------------------------------------------------
|
|
1486
1519
|
// Private helpers
|
|
1487
1520
|
// --------------------------------------------------------------------------
|
|
@@ -1494,53 +1527,56 @@ var ActionLogger = class {
|
|
|
1494
1527
|
{ field: "amount", value: input.amount }
|
|
1495
1528
|
);
|
|
1496
1529
|
}
|
|
1497
|
-
if (!input.txHash || input.txHash.trim() === "") {
|
|
1498
|
-
throw new KontextError(
|
|
1499
|
-
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1500
|
-
"Transaction hash is required",
|
|
1501
|
-
{ field: "txHash" }
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1504
1530
|
if (!input.from || input.from.trim() === "") {
|
|
1505
1531
|
throw new KontextError(
|
|
1506
1532
|
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1507
|
-
"Sender
|
|
1533
|
+
"Sender (from) is required",
|
|
1508
1534
|
{ field: "from" }
|
|
1509
1535
|
);
|
|
1510
1536
|
}
|
|
1511
1537
|
if (!input.to || input.to.trim() === "") {
|
|
1512
1538
|
throw new KontextError(
|
|
1513
1539
|
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1514
|
-
"Recipient
|
|
1540
|
+
"Recipient (to) is required",
|
|
1515
1541
|
{ field: "to" }
|
|
1516
1542
|
);
|
|
1517
1543
|
}
|
|
1518
|
-
const
|
|
1519
|
-
if (
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1544
|
+
const hasCryptoFields = input.txHash !== void 0 || input.chain !== void 0 || input.token !== void 0;
|
|
1545
|
+
if (hasCryptoFields) {
|
|
1546
|
+
if (!input.txHash || input.txHash.trim() === "") {
|
|
1547
|
+
throw new KontextError(
|
|
1548
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1549
|
+
"Transaction hash is required for crypto transactions",
|
|
1550
|
+
{ field: "txHash" }
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
const validChains = ["ethereum", "base", "polygon", "arbitrum", "optimism", "arc", "avalanche", "solana"];
|
|
1554
|
+
if (!input.chain || !validChains.includes(input.chain)) {
|
|
1555
|
+
throw new KontextError(
|
|
1556
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1557
|
+
`Invalid chain: ${input.chain}. Must be one of: ${validChains.join(", ")}`,
|
|
1558
|
+
{ field: "chain", value: input.chain }
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
const validTokens = ["USDC", "USDT", "DAI", "EURC", "USDP", "USDG"];
|
|
1562
|
+
if (!input.token || !validTokens.includes(input.token)) {
|
|
1563
|
+
throw new KontextError(
|
|
1564
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1565
|
+
`Invalid token: ${input.token}. Must be one of: ${validTokens.join(", ")}`,
|
|
1566
|
+
{ field: "token", value: input.token }
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1533
1569
|
}
|
|
1534
1570
|
}
|
|
1535
1571
|
flushToFile(actions) {
|
|
1536
1572
|
const outputDir = this.config.localOutputDir ?? ".kontext";
|
|
1537
|
-
const logDir =
|
|
1573
|
+
const logDir = path4.join(outputDir, "logs");
|
|
1538
1574
|
try {
|
|
1539
|
-
|
|
1575
|
+
fs4.mkdirSync(logDir, { recursive: true });
|
|
1540
1576
|
const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
|
|
1541
|
-
const filePath =
|
|
1577
|
+
const filePath = path4.join(logDir, filename);
|
|
1542
1578
|
const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
|
|
1543
|
-
|
|
1579
|
+
fs4.appendFileSync(filePath, lines, "utf-8");
|
|
1544
1580
|
} catch (error) {
|
|
1545
1581
|
this.emitLog("warn", "Failed to write log file", { error });
|
|
1546
1582
|
}
|
|
@@ -1989,7 +2025,7 @@ var AuditExporter = class {
|
|
|
1989
2025
|
if (options.agentIds && !options.agentIds.includes(tx.agentId)) {
|
|
1990
2026
|
return false;
|
|
1991
2027
|
}
|
|
1992
|
-
if (options.chains && !options.chains.includes(tx.chain)) {
|
|
2028
|
+
if (options.chains && (!tx.chain || !options.chains.includes(tx.chain))) {
|
|
1993
2029
|
return false;
|
|
1994
2030
|
}
|
|
1995
2031
|
return true;
|
|
@@ -2080,8 +2116,6 @@ var AuditExporter = class {
|
|
|
2080
2116
|
return sections.join("\n\n");
|
|
2081
2117
|
}
|
|
2082
2118
|
};
|
|
2083
|
-
|
|
2084
|
-
// src/integrations/usdc.ts
|
|
2085
2119
|
var USDC_CONTRACTS = {
|
|
2086
2120
|
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
2087
2121
|
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -2142,6 +2176,22 @@ var SANCTIONED_ADDRESSES = [
|
|
|
2142
2176
|
var SANCTIONED_SET = new Set(
|
|
2143
2177
|
SANCTIONED_ADDRESSES.map((addr) => addr.toLowerCase())
|
|
2144
2178
|
);
|
|
2179
|
+
function loadCachedSDN() {
|
|
2180
|
+
try {
|
|
2181
|
+
const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
|
|
2182
|
+
const cachePath = path4.join(dataDir, "ofac-sdn-cache.json");
|
|
2183
|
+
if (fs4.existsSync(cachePath)) {
|
|
2184
|
+
const cache = JSON.parse(fs4.readFileSync(cachePath, "utf-8"));
|
|
2185
|
+
if (Array.isArray(cache.addresses)) {
|
|
2186
|
+
for (const addr of cache.addresses) {
|
|
2187
|
+
SANCTIONED_SET.add(String(addr).toLowerCase());
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
loadCachedSDN();
|
|
2145
2195
|
var ENHANCED_DUE_DILIGENCE_THRESHOLD = 3e3;
|
|
2146
2196
|
var REPORTING_THRESHOLD = 1e4;
|
|
2147
2197
|
var LARGE_TRANSACTION_THRESHOLD = 5e4;
|
|
@@ -2441,6 +2491,183 @@ var UsdcCompliance = class _UsdcCompliance {
|
|
|
2441
2491
|
}
|
|
2442
2492
|
};
|
|
2443
2493
|
|
|
2494
|
+
// src/integrations/payment-compliance.ts
|
|
2495
|
+
var ENHANCED_DUE_DILIGENCE_THRESHOLD2 = 3e3;
|
|
2496
|
+
var REPORTING_THRESHOLD2 = 1e4;
|
|
2497
|
+
var LARGE_TRANSACTION_THRESHOLD2 = 5e4;
|
|
2498
|
+
var NAME_MATCH_THRESHOLD = 0.85;
|
|
2499
|
+
var _screener = null;
|
|
2500
|
+
var _screenerLoaded = false;
|
|
2501
|
+
function getScreener() {
|
|
2502
|
+
if (!_screenerLoaded) {
|
|
2503
|
+
_screenerLoaded = true;
|
|
2504
|
+
try {
|
|
2505
|
+
const mod = __require("./ofac-sanctions.js");
|
|
2506
|
+
if (mod.OFACSanctionsScreener) {
|
|
2507
|
+
_screener = new mod.OFACSanctionsScreener();
|
|
2508
|
+
}
|
|
2509
|
+
} catch {
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return _screener;
|
|
2513
|
+
}
|
|
2514
|
+
var PaymentCompliance = class _PaymentCompliance {
|
|
2515
|
+
/**
|
|
2516
|
+
* Run compliance checks on a general payment.
|
|
2517
|
+
*
|
|
2518
|
+
* @param input - Payment to evaluate
|
|
2519
|
+
* @returns UsdcComplianceCheck with pass/fail results and recommendations
|
|
2520
|
+
*/
|
|
2521
|
+
static checkPayment(input) {
|
|
2522
|
+
const checks = [];
|
|
2523
|
+
checks.push(_PaymentCompliance.checkAmountValid(input.amount));
|
|
2524
|
+
checks.push(_PaymentCompliance.checkEntityScreening(input.from, "sender"));
|
|
2525
|
+
checks.push(_PaymentCompliance.checkEntityScreening(input.to, "recipient"));
|
|
2526
|
+
checks.push(_PaymentCompliance.checkEnhancedDueDiligence(input.amount));
|
|
2527
|
+
checks.push(_PaymentCompliance.checkReportingThreshold(input.amount));
|
|
2528
|
+
const failedChecks = checks.filter((c) => !c.passed);
|
|
2529
|
+
const compliant = failedChecks.every((c) => c.severity === "low");
|
|
2530
|
+
const highestSeverity = failedChecks.reduce(
|
|
2531
|
+
(max, c) => {
|
|
2532
|
+
const order = ["low", "medium", "high", "critical"];
|
|
2533
|
+
return order.indexOf(c.severity) > order.indexOf(max) ? c.severity : max;
|
|
2534
|
+
},
|
|
2535
|
+
"low"
|
|
2536
|
+
);
|
|
2537
|
+
const recommendations = _PaymentCompliance.generateRecommendations(checks, input);
|
|
2538
|
+
return {
|
|
2539
|
+
compliant,
|
|
2540
|
+
checks,
|
|
2541
|
+
riskLevel: highestSeverity,
|
|
2542
|
+
recommendations
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
// --------------------------------------------------------------------------
|
|
2546
|
+
// Individual checks
|
|
2547
|
+
// --------------------------------------------------------------------------
|
|
2548
|
+
static checkAmountValid(amount) {
|
|
2549
|
+
const parsed = parseAmount(amount);
|
|
2550
|
+
const isValid = !isNaN(parsed) && parsed > 0;
|
|
2551
|
+
return {
|
|
2552
|
+
name: "amount_valid",
|
|
2553
|
+
passed: isValid,
|
|
2554
|
+
description: isValid ? `Payment amount ${amount} is valid` : `Payment amount ${amount} is invalid`,
|
|
2555
|
+
severity: isValid ? "low" : "critical"
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
static checkEntityScreening(entity, label) {
|
|
2559
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(entity)) {
|
|
2560
|
+
const sanctioned = UsdcCompliance.isSanctioned(entity);
|
|
2561
|
+
return {
|
|
2562
|
+
name: `entity_screening_${label}`,
|
|
2563
|
+
passed: !sanctioned,
|
|
2564
|
+
description: sanctioned ? `${label} address ${entity} matches OFAC SDN sanctioned address` : `${label} address passed sanctions screening`,
|
|
2565
|
+
severity: sanctioned ? "critical" : "low"
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
const screener = getScreener();
|
|
2569
|
+
if (!screener) {
|
|
2570
|
+
return {
|
|
2571
|
+
name: `entity_screening_${label}`,
|
|
2572
|
+
passed: true,
|
|
2573
|
+
description: `${label} "${entity}" \u2014 name-based OFAC screening not available (address screening active)`,
|
|
2574
|
+
severity: "low"
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
const rawMatches = screener.searchEntityName(entity, NAME_MATCH_THRESHOLD);
|
|
2578
|
+
const matches = rawMatches.filter(
|
|
2579
|
+
(m) => m.similarity >= NAME_MATCH_THRESHOLD && m.matchedOn.length >= 4
|
|
2580
|
+
);
|
|
2581
|
+
if (matches.length > 0) {
|
|
2582
|
+
const topMatch = matches[0];
|
|
2583
|
+
const isActive = topMatch.entity.list !== "DELISTED";
|
|
2584
|
+
if (isActive) {
|
|
2585
|
+
return {
|
|
2586
|
+
name: `entity_screening_${label}`,
|
|
2587
|
+
passed: false,
|
|
2588
|
+
description: `${label} "${entity}" matches OFAC SDN entity "${topMatch.matchedOn}" (${Math.round(topMatch.similarity * 100)}% match)`,
|
|
2589
|
+
severity: "critical"
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
return {
|
|
2593
|
+
name: `entity_screening_${label}`,
|
|
2594
|
+
passed: true,
|
|
2595
|
+
description: `${label} "${entity}" matches delisted entity "${topMatch.matchedOn}" \u2014 enhanced due diligence recommended`,
|
|
2596
|
+
severity: "medium"
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
return {
|
|
2600
|
+
name: `entity_screening_${label}`,
|
|
2601
|
+
passed: true,
|
|
2602
|
+
description: `${label} "${entity}" passed OFAC entity screening`,
|
|
2603
|
+
severity: "low"
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
static checkEnhancedDueDiligence(amount) {
|
|
2607
|
+
const parsed = parseAmount(amount);
|
|
2608
|
+
const requiresEdd = !isNaN(parsed) && parsed >= ENHANCED_DUE_DILIGENCE_THRESHOLD2;
|
|
2609
|
+
return {
|
|
2610
|
+
name: "enhanced_due_diligence",
|
|
2611
|
+
passed: true,
|
|
2612
|
+
description: requiresEdd ? `Amount ${amount} requires enhanced due diligence (threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2})` : `Amount ${amount} is below enhanced due diligence threshold`,
|
|
2613
|
+
severity: requiresEdd ? "medium" : "low"
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
static checkReportingThreshold(amount) {
|
|
2617
|
+
const parsed = parseAmount(amount);
|
|
2618
|
+
const requiresReporting = !isNaN(parsed) && parsed >= REPORTING_THRESHOLD2;
|
|
2619
|
+
const isLarge = !isNaN(parsed) && parsed >= LARGE_TRANSACTION_THRESHOLD2;
|
|
2620
|
+
let description;
|
|
2621
|
+
let severity;
|
|
2622
|
+
if (isLarge) {
|
|
2623
|
+
description = `Amount ${amount} is a large payment (>= ${LARGE_TRANSACTION_THRESHOLD2}) \u2014 requires enhanced monitoring`;
|
|
2624
|
+
severity = "high";
|
|
2625
|
+
} else if (requiresReporting) {
|
|
2626
|
+
description = `Amount ${amount} meets reporting threshold (>= ${REPORTING_THRESHOLD2})`;
|
|
2627
|
+
severity = "medium";
|
|
2628
|
+
} else {
|
|
2629
|
+
description = `Amount ${amount} is below reporting threshold`;
|
|
2630
|
+
severity = "low";
|
|
2631
|
+
}
|
|
2632
|
+
return {
|
|
2633
|
+
name: "reporting_threshold",
|
|
2634
|
+
passed: true,
|
|
2635
|
+
description,
|
|
2636
|
+
severity
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
// --------------------------------------------------------------------------
|
|
2640
|
+
// Recommendations
|
|
2641
|
+
// --------------------------------------------------------------------------
|
|
2642
|
+
static generateRecommendations(checks, input) {
|
|
2643
|
+
const recommendations = [];
|
|
2644
|
+
const amount = parseAmount(input.amount);
|
|
2645
|
+
for (const check of checks) {
|
|
2646
|
+
if (!check.passed && check.name.startsWith("entity_screening_")) {
|
|
2647
|
+
recommendations.push(
|
|
2648
|
+
`BLOCK: ${check.description}. Payment is prohibited under OFAC regulations.`
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
if (!isNaN(amount) && amount >= LARGE_TRANSACTION_THRESHOLD2) {
|
|
2653
|
+
recommendations.push(
|
|
2654
|
+
`Enhanced monitoring required for payment of ${input.amount} ${input.currency ?? "USD"} (above ${LARGE_TRANSACTION_THRESHOLD2} threshold).`
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
if (!isNaN(amount) && amount >= REPORTING_THRESHOLD2) {
|
|
2658
|
+
recommendations.push(
|
|
2659
|
+
`CTR reporting may be required for payment of ${input.amount} ${input.currency ?? "USD"} (meets ${REPORTING_THRESHOLD2} reporting threshold).`
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
if (!isNaN(amount) && amount >= ENHANCED_DUE_DILIGENCE_THRESHOLD2) {
|
|
2663
|
+
recommendations.push(
|
|
2664
|
+
`Enhanced due diligence recommended for payment of ${input.amount} ${input.currency ?? "USD"} (Travel Rule threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2}).`
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
return recommendations;
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
|
|
2444
2671
|
// src/plans.ts
|
|
2445
2672
|
var PLAN_LIMITS = {
|
|
2446
2673
|
free: 2e4,
|
|
@@ -2793,7 +3020,7 @@ var JsonFileExporter = class {
|
|
|
2793
3020
|
buffer = [];
|
|
2794
3021
|
bufferSize;
|
|
2795
3022
|
constructor(options) {
|
|
2796
|
-
this.outputDir =
|
|
3023
|
+
this.outputDir = path4.resolve(options?.outputDir ?? ".kontext/exports");
|
|
2797
3024
|
this.bufferSize = options?.bufferSize ?? 1;
|
|
2798
3025
|
}
|
|
2799
3026
|
async export(events) {
|
|
@@ -2808,11 +3035,11 @@ var JsonFileExporter = class {
|
|
|
2808
3035
|
const toWrite = [...this.buffer];
|
|
2809
3036
|
this.buffer = [];
|
|
2810
3037
|
try {
|
|
2811
|
-
|
|
3038
|
+
fs4.mkdirSync(this.outputDir, { recursive: true });
|
|
2812
3039
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2813
|
-
const filePath =
|
|
3040
|
+
const filePath = path4.join(this.outputDir, `events-${date}.jsonl`);
|
|
2814
3041
|
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2815
|
-
|
|
3042
|
+
fs4.appendFileSync(filePath, lines, "utf-8");
|
|
2816
3043
|
} catch (error) {
|
|
2817
3044
|
console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
|
|
2818
3045
|
}
|
|
@@ -3151,7 +3378,7 @@ var Kontext = class _Kontext {
|
|
|
3151
3378
|
* @returns The created transaction record
|
|
3152
3379
|
*/
|
|
3153
3380
|
async logTransaction(input) {
|
|
3154
|
-
if (input.chain !== "base") {
|
|
3381
|
+
if (input.chain && input.chain !== "base") {
|
|
3155
3382
|
requirePlan("multi-chain", this.planManager.getTier());
|
|
3156
3383
|
}
|
|
3157
3384
|
this.validateMetadata(input.metadata);
|
|
@@ -3188,10 +3415,22 @@ var Kontext = class _Kontext {
|
|
|
3188
3415
|
/**
|
|
3189
3416
|
* Restore state from the attached storage adapter.
|
|
3190
3417
|
* Loads previously persisted actions, transactions, tasks, and anomalies.
|
|
3418
|
+
* Also restores the digest chain's terminal digest so that new actions
|
|
3419
|
+
* chain correctly across process boundaries.
|
|
3191
3420
|
* No-op if no storage adapter is configured.
|
|
3192
3421
|
*/
|
|
3193
3422
|
async restore() {
|
|
3194
3423
|
await this.store.restore();
|
|
3424
|
+
const actions = this.store.getActions();
|
|
3425
|
+
if (actions.length > 0) {
|
|
3426
|
+
const sorted = [...actions].sort(
|
|
3427
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
3428
|
+
);
|
|
3429
|
+
const lastAction = sorted[sorted.length - 1];
|
|
3430
|
+
if (lastAction.digest) {
|
|
3431
|
+
this.logger.restoreChainState(lastAction.digest);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3195
3434
|
}
|
|
3196
3435
|
// --------------------------------------------------------------------------
|
|
3197
3436
|
// Task Confirmation
|
|
@@ -3494,7 +3733,7 @@ var Kontext = class _Kontext {
|
|
|
3494
3733
|
*/
|
|
3495
3734
|
async verify(input) {
|
|
3496
3735
|
const transaction = await this.logTransaction(input);
|
|
3497
|
-
const compliance = UsdcCompliance.checkTransaction(input);
|
|
3736
|
+
const compliance = isCryptoTransaction(input) ? UsdcCompliance.checkTransaction(input) : PaymentCompliance.checkPayment(input);
|
|
3498
3737
|
let reasoningId;
|
|
3499
3738
|
if (input.reasoning) {
|
|
3500
3739
|
const entry = await this.logReasoning({
|
|
@@ -3523,18 +3762,22 @@ var Kontext = class _Kontext {
|
|
|
3523
3762
|
const threshold = parseAmount(this.config.approvalThreshold);
|
|
3524
3763
|
if (amount > threshold) {
|
|
3525
3764
|
requiresApproval = true;
|
|
3765
|
+
const label = input.token ? `${input.token} ${input.amount} transfer` : `${input.currency ?? "USD"} ${input.amount} payment`;
|
|
3526
3766
|
task = await this.createTask({
|
|
3527
|
-
description: `Approve ${
|
|
3767
|
+
description: `Approve ${label} from ${input.from} to ${input.to}`,
|
|
3528
3768
|
agentId: input.agentId,
|
|
3529
|
-
requiredEvidence: ["txHash"],
|
|
3769
|
+
requiredEvidence: input.txHash ? ["txHash"] : ["paymentReference"],
|
|
3530
3770
|
metadata: {
|
|
3531
|
-
txHash: input.txHash,
|
|
3532
|
-
chain: input.chain,
|
|
3533
3771
|
amount: input.amount,
|
|
3534
|
-
token: input.token,
|
|
3535
3772
|
from: input.from,
|
|
3536
3773
|
to: input.to,
|
|
3537
|
-
approvalThreshold: this.config.approvalThreshold
|
|
3774
|
+
approvalThreshold: this.config.approvalThreshold,
|
|
3775
|
+
...input.txHash ? { txHash: input.txHash } : {},
|
|
3776
|
+
...input.chain ? { chain: input.chain } : {},
|
|
3777
|
+
...input.token ? { token: input.token } : {},
|
|
3778
|
+
...input.currency ? { currency: input.currency } : {},
|
|
3779
|
+
...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
|
|
3780
|
+
...input.paymentReference ? { paymentReference: input.paymentReference } : {}
|
|
3538
3781
|
}
|
|
3539
3782
|
});
|
|
3540
3783
|
}
|
|
@@ -3633,7 +3876,7 @@ var Kontext = class _Kontext {
|
|
|
3633
3876
|
* and cryptographically verifies the digest chain integrity.
|
|
3634
3877
|
*
|
|
3635
3878
|
* The certificate includes: action/transaction/reasoning counts, digest chain
|
|
3636
|
-
* verification status (
|
|
3879
|
+
* verification status (patented), the agent's current trust
|
|
3637
3880
|
* score, and an overall compliance status. A SHA-256 content hash of the
|
|
3638
3881
|
* certificate itself is included for tamper-evidence.
|
|
3639
3882
|
*
|
|
@@ -3871,20 +4114,20 @@ var MemoryStorage = class {
|
|
|
3871
4114
|
var FileStorage = class {
|
|
3872
4115
|
baseDir;
|
|
3873
4116
|
constructor(baseDir) {
|
|
3874
|
-
this.baseDir =
|
|
4117
|
+
this.baseDir = path4.resolve(baseDir);
|
|
3875
4118
|
}
|
|
3876
4119
|
async save(key, data) {
|
|
3877
|
-
|
|
4120
|
+
fs4.mkdirSync(this.baseDir, { recursive: true });
|
|
3878
4121
|
const filePath = this.keyToPath(key);
|
|
3879
|
-
const dir =
|
|
3880
|
-
|
|
3881
|
-
|
|
4122
|
+
const dir = path4.dirname(filePath);
|
|
4123
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
4124
|
+
fs4.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
3882
4125
|
}
|
|
3883
4126
|
async load(key) {
|
|
3884
4127
|
const filePath = this.keyToPath(key);
|
|
3885
|
-
if (!
|
|
4128
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3886
4129
|
try {
|
|
3887
|
-
const raw =
|
|
4130
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3888
4131
|
return JSON.parse(raw);
|
|
3889
4132
|
} catch {
|
|
3890
4133
|
return null;
|
|
@@ -3892,12 +4135,12 @@ var FileStorage = class {
|
|
|
3892
4135
|
}
|
|
3893
4136
|
async delete(key) {
|
|
3894
4137
|
const filePath = this.keyToPath(key);
|
|
3895
|
-
if (
|
|
3896
|
-
|
|
4138
|
+
if (fs4.existsSync(filePath)) {
|
|
4139
|
+
fs4.unlinkSync(filePath);
|
|
3897
4140
|
}
|
|
3898
4141
|
}
|
|
3899
4142
|
async list(prefix) {
|
|
3900
|
-
if (!
|
|
4143
|
+
if (!fs4.existsSync(this.baseDir)) return [];
|
|
3901
4144
|
return this.listRecursive(this.baseDir, prefix);
|
|
3902
4145
|
}
|
|
3903
4146
|
/** Get the base directory path. */
|
|
@@ -3909,18 +4152,18 @@ var FileStorage = class {
|
|
|
3909
4152
|
// --------------------------------------------------------------------------
|
|
3910
4153
|
keyToPath(key) {
|
|
3911
4154
|
const safeName = key.replace(/[<>"|?*]/g, "_");
|
|
3912
|
-
return
|
|
4155
|
+
return path4.join(this.baseDir, `${safeName}.json`);
|
|
3913
4156
|
}
|
|
3914
4157
|
pathToKey(filePath) {
|
|
3915
|
-
const relative2 =
|
|
4158
|
+
const relative2 = path4.relative(this.baseDir, filePath);
|
|
3916
4159
|
return relative2.replace(/\.json$/, "");
|
|
3917
4160
|
}
|
|
3918
4161
|
listRecursive(dir, prefix) {
|
|
3919
4162
|
const keys = [];
|
|
3920
|
-
if (!
|
|
3921
|
-
const entries =
|
|
4163
|
+
if (!fs4.existsSync(dir)) return keys;
|
|
4164
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
3922
4165
|
for (const entry of entries) {
|
|
3923
|
-
const fullPath =
|
|
4166
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3924
4167
|
if (entry.isDirectory()) {
|
|
3925
4168
|
keys.push(...this.listRecursive(fullPath, prefix));
|
|
3926
4169
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
@@ -3934,387 +4177,6 @@ var FileStorage = class {
|
|
|
3934
4177
|
}
|
|
3935
4178
|
};
|
|
3936
4179
|
|
|
3937
|
-
|
|
3938
|
-
var FirestoreStorageAdapter = class {
|
|
3939
|
-
config;
|
|
3940
|
-
/** In-memory cache for load() — avoids re-fetching on every store.restore() */
|
|
3941
|
-
cache = /* @__PURE__ */ new Map();
|
|
3942
|
-
/** Token cache: avoid metadata server round-trips on every write */
|
|
3943
|
-
cachedToken = null;
|
|
3944
|
-
tokenExpiresAt = 0;
|
|
3945
|
-
constructor(config) {
|
|
3946
|
-
this.config = {
|
|
3947
|
-
gcpProjectId: config.gcpProjectId,
|
|
3948
|
-
userId: config.userId,
|
|
3949
|
-
accessToken: config.accessToken,
|
|
3950
|
-
databaseId: config.databaseId ?? "(default)",
|
|
3951
|
-
firestoreBaseUrl: config.firestoreBaseUrl ?? "https://firestore.googleapis.com",
|
|
3952
|
-
writeDocumentsIndividually: config.writeDocumentsIndividually ?? true
|
|
3953
|
-
};
|
|
3954
|
-
}
|
|
3955
|
-
// --------------------------------------------------------------------------
|
|
3956
|
-
// StorageAdapter interface
|
|
3957
|
-
// --------------------------------------------------------------------------
|
|
3958
|
-
/**
|
|
3959
|
-
* Save data under a Kontext storage key.
|
|
3960
|
-
*
|
|
3961
|
-
* The flat key space (kontext:actions, kontext:transactions, etc.) is mapped
|
|
3962
|
-
* to structured Firestore paths. List data (actions, transactions) is written
|
|
3963
|
-
* as individual documents under sub-collections for queryability.
|
|
3964
|
-
*/
|
|
3965
|
-
async save(key, data) {
|
|
3966
|
-
this.cache.set(key, data);
|
|
3967
|
-
if (key === "kontext:actions" && Array.isArray(data)) {
|
|
3968
|
-
await this.saveActionList(data);
|
|
3969
|
-
} else if (key === "kontext:transactions" && Array.isArray(data)) {
|
|
3970
|
-
await this.saveTransactionList(data);
|
|
3971
|
-
} else if (key === "kontext:tasks" && Array.isArray(data)) {
|
|
3972
|
-
await this.saveTaskList(data);
|
|
3973
|
-
} else if (key === "kontext:anomalies" && Array.isArray(data)) {
|
|
3974
|
-
await this.saveAnomalyList(data);
|
|
3975
|
-
} else {
|
|
3976
|
-
await this.saveDocument(this.keyToPath(key), data);
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
/**
|
|
3980
|
-
* Load data for a Kontext storage key.
|
|
3981
|
-
* Returns cached data if available (populated by save()).
|
|
3982
|
-
* Falls back to Firestore fetch for cold starts.
|
|
3983
|
-
*/
|
|
3984
|
-
async load(key) {
|
|
3985
|
-
if (this.cache.has(key)) {
|
|
3986
|
-
return this.cache.get(key) ?? null;
|
|
3987
|
-
}
|
|
3988
|
-
if (key === "kontext:actions") {
|
|
3989
|
-
const actions = await this.loadActionList();
|
|
3990
|
-
this.cache.set(key, actions);
|
|
3991
|
-
return actions;
|
|
3992
|
-
} else if (key === "kontext:transactions") {
|
|
3993
|
-
const txs = await this.loadTransactionList();
|
|
3994
|
-
this.cache.set(key, txs);
|
|
3995
|
-
return txs;
|
|
3996
|
-
} else if (key === "kontext:tasks") {
|
|
3997
|
-
const tasks = await this.loadTaskList();
|
|
3998
|
-
this.cache.set(key, tasks);
|
|
3999
|
-
return tasks;
|
|
4000
|
-
} else if (key === "kontext:anomalies") {
|
|
4001
|
-
const anomalies = await this.loadAnomalyList();
|
|
4002
|
-
this.cache.set(key, anomalies);
|
|
4003
|
-
return anomalies;
|
|
4004
|
-
} else {
|
|
4005
|
-
return this.loadDocument(this.keyToPath(key));
|
|
4006
|
-
}
|
|
4007
|
-
}
|
|
4008
|
-
async delete(key) {
|
|
4009
|
-
this.cache.delete(key);
|
|
4010
|
-
await this.deleteDocument(this.keyToPath(key));
|
|
4011
|
-
}
|
|
4012
|
-
async list(prefix) {
|
|
4013
|
-
const all = Array.from(this.cache.keys());
|
|
4014
|
-
if (!prefix) return all;
|
|
4015
|
-
return all.filter((k) => k.startsWith(prefix));
|
|
4016
|
-
}
|
|
4017
|
-
// --------------------------------------------------------------------------
|
|
4018
|
-
// Structured write methods (per-record documents)
|
|
4019
|
-
// --------------------------------------------------------------------------
|
|
4020
|
-
/**
|
|
4021
|
-
* Write a single action log to its canonical Firestore path.
|
|
4022
|
-
* Called by the SDK when `writeDocumentsIndividually` is true.
|
|
4023
|
-
*
|
|
4024
|
-
* Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/actions/{actionId}
|
|
4025
|
-
*/
|
|
4026
|
-
async writeAction(action) {
|
|
4027
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4028
|
-
const path4 = this.actionPath(action);
|
|
4029
|
-
await this.saveDocument(path4, action);
|
|
4030
|
-
}
|
|
4031
|
-
/**
|
|
4032
|
-
* Write a single transaction record to its canonical Firestore path.
|
|
4033
|
-
*
|
|
4034
|
-
* Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/transactions/{txId}
|
|
4035
|
-
*/
|
|
4036
|
-
async writeTransaction(tx) {
|
|
4037
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4038
|
-
const path4 = this.transactionPath(tx);
|
|
4039
|
-
await this.saveDocument(path4, tx);
|
|
4040
|
-
}
|
|
4041
|
-
/**
|
|
4042
|
-
* Write a single task to its canonical Firestore path.
|
|
4043
|
-
*
|
|
4044
|
-
* Path: users/{userId}/projects/{projectId}/tasks/{taskId}
|
|
4045
|
-
*/
|
|
4046
|
-
async writeTask(task) {
|
|
4047
|
-
if (!this.config.writeDocumentsIndividually) return;
|
|
4048
|
-
const path4 = this.taskPath(task.id);
|
|
4049
|
-
await this.saveDocument(path4, task);
|
|
4050
|
-
}
|
|
4051
|
-
// --------------------------------------------------------------------------
|
|
4052
|
-
// Path builders
|
|
4053
|
-
// --------------------------------------------------------------------------
|
|
4054
|
-
get basePath() {
|
|
4055
|
-
return `users/${this.config.userId}/projects`;
|
|
4056
|
-
}
|
|
4057
|
-
actionPath(action) {
|
|
4058
|
-
const sessionId = action.sessionId ?? "_default";
|
|
4059
|
-
return `${this.basePath}/${action.projectId}/agents/${sanitize(action.agentId)}/sessions/${sanitize(sessionId)}/actions/${action.id}`;
|
|
4060
|
-
}
|
|
4061
|
-
transactionPath(tx) {
|
|
4062
|
-
const sessionId = tx.sessionId ?? "_default";
|
|
4063
|
-
return `${this.basePath}/${tx.projectId}/agents/${sanitize(tx.agentId)}/sessions/${sanitize(sessionId)}/transactions/${tx.id}`;
|
|
4064
|
-
}
|
|
4065
|
-
taskPath(taskId) {
|
|
4066
|
-
return `${this.basePath}/_tasks/${taskId}`;
|
|
4067
|
-
}
|
|
4068
|
-
anomalyPath(anomalyId, agentId) {
|
|
4069
|
-
return `${this.basePath}/_anomalies/agents/${sanitize(agentId)}/${anomalyId}`;
|
|
4070
|
-
}
|
|
4071
|
-
keyToPath(key) {
|
|
4072
|
-
const safe = key.replace(/:/g, "/").replace(/[^a-zA-Z0-9/_-]/g, "_");
|
|
4073
|
-
return `${this.basePath}/_meta/${safe}`;
|
|
4074
|
-
}
|
|
4075
|
-
// --------------------------------------------------------------------------
|
|
4076
|
-
// Bulk save/load (called by KontextStore.flush / restore)
|
|
4077
|
-
// --------------------------------------------------------------------------
|
|
4078
|
-
async saveActionList(actions) {
|
|
4079
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4080
|
-
await this.saveDocument(`${this.basePath}/_snapshots/actions`, actions);
|
|
4081
|
-
return;
|
|
4082
|
-
}
|
|
4083
|
-
await Promise.allSettled(actions.map((a) => this.saveDocument(this.actionPath(a), a)));
|
|
4084
|
-
}
|
|
4085
|
-
async saveTransactionList(txs) {
|
|
4086
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4087
|
-
await this.saveDocument(`${this.basePath}/_snapshots/transactions`, txs);
|
|
4088
|
-
return;
|
|
4089
|
-
}
|
|
4090
|
-
await Promise.allSettled(txs.map((tx) => this.saveDocument(this.transactionPath(tx), tx)));
|
|
4091
|
-
}
|
|
4092
|
-
async saveTaskList(entries) {
|
|
4093
|
-
await Promise.allSettled(
|
|
4094
|
-
entries.map(([_id, task]) => this.saveDocument(this.taskPath(task.id), task))
|
|
4095
|
-
);
|
|
4096
|
-
}
|
|
4097
|
-
async saveAnomalyList(anomalies) {
|
|
4098
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4099
|
-
await this.saveDocument(`${this.basePath}/_snapshots/anomalies`, anomalies);
|
|
4100
|
-
return;
|
|
4101
|
-
}
|
|
4102
|
-
await Promise.allSettled(
|
|
4103
|
-
anomalies.map((a) => this.saveDocument(this.anomalyPath(a.id, a.agentId), a))
|
|
4104
|
-
);
|
|
4105
|
-
}
|
|
4106
|
-
async loadActionList() {
|
|
4107
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4108
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/actions`);
|
|
4109
|
-
return Array.isArray(snap) ? snap : [];
|
|
4110
|
-
}
|
|
4111
|
-
return this.queryCollectionGroup("actions");
|
|
4112
|
-
}
|
|
4113
|
-
async loadTransactionList() {
|
|
4114
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4115
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/transactions`);
|
|
4116
|
-
return Array.isArray(snap) ? snap : [];
|
|
4117
|
-
}
|
|
4118
|
-
return this.queryCollectionGroup("transactions");
|
|
4119
|
-
}
|
|
4120
|
-
async loadTaskList() {
|
|
4121
|
-
const tasks = await this.queryCollection(`${this.basePath}/_tasks`);
|
|
4122
|
-
return tasks.map((t) => [t.id, t]);
|
|
4123
|
-
}
|
|
4124
|
-
async loadAnomalyList() {
|
|
4125
|
-
if (!this.config.writeDocumentsIndividually) {
|
|
4126
|
-
const snap = await this.loadDocument(`${this.basePath}/_snapshots/anomalies`);
|
|
4127
|
-
return Array.isArray(snap) ? snap : [];
|
|
4128
|
-
}
|
|
4129
|
-
return this.queryCollectionGroup("anomalies");
|
|
4130
|
-
}
|
|
4131
|
-
// --------------------------------------------------------------------------
|
|
4132
|
-
// Firestore REST API helpers
|
|
4133
|
-
// --------------------------------------------------------------------------
|
|
4134
|
-
firestoreBase() {
|
|
4135
|
-
return `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
|
|
4136
|
-
}
|
|
4137
|
-
/** Save an arbitrary JS object as a Firestore document at the given path. */
|
|
4138
|
-
async saveDocument(fsPath, data) {
|
|
4139
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4140
|
-
const token = await this.getToken();
|
|
4141
|
-
const body = JSON.stringify({ fields: toFirestoreFields(data) });
|
|
4142
|
-
const res = await fetch(url, {
|
|
4143
|
-
method: "PATCH",
|
|
4144
|
-
headers: {
|
|
4145
|
-
"Content-Type": "application/json",
|
|
4146
|
-
Authorization: `Bearer ${token}`
|
|
4147
|
-
},
|
|
4148
|
-
body
|
|
4149
|
-
});
|
|
4150
|
-
if (!res.ok) {
|
|
4151
|
-
const text = await res.text().catch(() => "");
|
|
4152
|
-
throw new Error(`Firestore write failed [${res.status}] ${fsPath}: ${text}`);
|
|
4153
|
-
}
|
|
4154
|
-
}
|
|
4155
|
-
/** Load a document at the given Firestore path. Returns null if not found. */
|
|
4156
|
-
async loadDocument(fsPath) {
|
|
4157
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4158
|
-
const token = await this.getToken();
|
|
4159
|
-
const res = await fetch(url, {
|
|
4160
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4161
|
-
});
|
|
4162
|
-
if (res.status === 404) return null;
|
|
4163
|
-
if (!res.ok) return null;
|
|
4164
|
-
const doc = await res.json();
|
|
4165
|
-
return fromFirestoreFields(doc.fields);
|
|
4166
|
-
}
|
|
4167
|
-
/** Delete a document at the given Firestore path. */
|
|
4168
|
-
async deleteDocument(fsPath) {
|
|
4169
|
-
const url = `${this.firestoreBase()}/${fsPath}`;
|
|
4170
|
-
const token = await this.getToken();
|
|
4171
|
-
await fetch(url, {
|
|
4172
|
-
method: "DELETE",
|
|
4173
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4174
|
-
});
|
|
4175
|
-
}
|
|
4176
|
-
/**
|
|
4177
|
-
* List all documents in a collection and deserialize them.
|
|
4178
|
-
* Used for tasks (simple flat collection).
|
|
4179
|
-
*/
|
|
4180
|
-
async queryCollection(collectionPath) {
|
|
4181
|
-
const url = `${this.firestoreBase()}/${collectionPath}`;
|
|
4182
|
-
const token = await this.getToken();
|
|
4183
|
-
const res = await fetch(url, {
|
|
4184
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
4185
|
-
});
|
|
4186
|
-
if (!res.ok) return [];
|
|
4187
|
-
const body = await res.json();
|
|
4188
|
-
if (!body.documents) return [];
|
|
4189
|
-
return body.documents.map((doc) => fromFirestoreFields(doc.fields));
|
|
4190
|
-
}
|
|
4191
|
-
/**
|
|
4192
|
-
* Run a Firestore collection group query to fetch documents across all
|
|
4193
|
-
* nested sub-collections with the given name (e.g., 'actions' across all
|
|
4194
|
-
* agents and sessions).
|
|
4195
|
-
*
|
|
4196
|
-
* Scoped to the user's project root to prevent cross-tenant reads.
|
|
4197
|
-
*/
|
|
4198
|
-
async queryCollectionGroup(collectionId) {
|
|
4199
|
-
const parent = `projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
|
|
4200
|
-
`${this.config.firestoreBaseUrl}/v1/${parent}:runQuery`;
|
|
4201
|
-
const token = await this.getToken();
|
|
4202
|
-
const scopedUrl = `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents/users/${this.config.userId}:runQuery`;
|
|
4203
|
-
const body = {
|
|
4204
|
-
structuredQuery: {
|
|
4205
|
-
from: [{ collectionId, allDescendants: true }],
|
|
4206
|
-
orderBy: [{ field: { fieldPath: "timestamp" }, direction: "ASCENDING" }]
|
|
4207
|
-
}
|
|
4208
|
-
};
|
|
4209
|
-
const res = await fetch(scopedUrl, {
|
|
4210
|
-
method: "POST",
|
|
4211
|
-
headers: {
|
|
4212
|
-
"Content-Type": "application/json",
|
|
4213
|
-
Authorization: `Bearer ${token}`
|
|
4214
|
-
},
|
|
4215
|
-
body: JSON.stringify(body)
|
|
4216
|
-
});
|
|
4217
|
-
if (!res.ok) return [];
|
|
4218
|
-
const results = await res.json();
|
|
4219
|
-
return results.filter((r) => r.document?.fields).map((r) => fromFirestoreFields(r.document.fields));
|
|
4220
|
-
}
|
|
4221
|
-
// --------------------------------------------------------------------------
|
|
4222
|
-
// Auth: GCP metadata server + token caching
|
|
4223
|
-
// --------------------------------------------------------------------------
|
|
4224
|
-
async getToken() {
|
|
4225
|
-
if (this.config.accessToken) {
|
|
4226
|
-
return this.config.accessToken;
|
|
4227
|
-
}
|
|
4228
|
-
const now2 = Date.now();
|
|
4229
|
-
if (this.cachedToken && now2 < this.tokenExpiresAt - 3e5) {
|
|
4230
|
-
return this.cachedToken;
|
|
4231
|
-
}
|
|
4232
|
-
const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
|
4233
|
-
const res = await fetch(metadataUrl, {
|
|
4234
|
-
headers: { "Metadata-Flavor": "Google" }
|
|
4235
|
-
});
|
|
4236
|
-
if (!res.ok) {
|
|
4237
|
-
throw new Error(
|
|
4238
|
-
"FirestoreStorageAdapter: Could not fetch GCP access token. On GCP, ensure the service account has Firestore write access. For local dev, pass accessToken in FirestoreStorageConfig."
|
|
4239
|
-
);
|
|
4240
|
-
}
|
|
4241
|
-
const data = await res.json();
|
|
4242
|
-
this.cachedToken = data.access_token;
|
|
4243
|
-
this.tokenExpiresAt = now2 + data.expires_in * 1e3;
|
|
4244
|
-
return this.cachedToken;
|
|
4245
|
-
}
|
|
4246
|
-
};
|
|
4247
|
-
function toFirestoreValue(value) {
|
|
4248
|
-
if (value === null || value === void 0) {
|
|
4249
|
-
return { nullValue: null };
|
|
4250
|
-
}
|
|
4251
|
-
if (typeof value === "boolean") {
|
|
4252
|
-
return { booleanValue: value };
|
|
4253
|
-
}
|
|
4254
|
-
if (typeof value === "number") {
|
|
4255
|
-
if (Number.isInteger(value)) {
|
|
4256
|
-
return { integerValue: String(value) };
|
|
4257
|
-
}
|
|
4258
|
-
return { doubleValue: value };
|
|
4259
|
-
}
|
|
4260
|
-
if (typeof value === "string") {
|
|
4261
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
4262
|
-
return { timestampValue: value };
|
|
4263
|
-
}
|
|
4264
|
-
return { stringValue: value };
|
|
4265
|
-
}
|
|
4266
|
-
if (Array.isArray(value)) {
|
|
4267
|
-
return {
|
|
4268
|
-
arrayValue: {
|
|
4269
|
-
values: value.map(toFirestoreValue)
|
|
4270
|
-
}
|
|
4271
|
-
};
|
|
4272
|
-
}
|
|
4273
|
-
if (typeof value === "object") {
|
|
4274
|
-
return {
|
|
4275
|
-
mapValue: {
|
|
4276
|
-
fields: toFirestoreFields(value)
|
|
4277
|
-
}
|
|
4278
|
-
};
|
|
4279
|
-
}
|
|
4280
|
-
return { stringValue: String(value) };
|
|
4281
|
-
}
|
|
4282
|
-
function toFirestoreFields(obj) {
|
|
4283
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
4284
|
-
return {};
|
|
4285
|
-
}
|
|
4286
|
-
const result = {};
|
|
4287
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
4288
|
-
result[k] = toFirestoreValue(v);
|
|
4289
|
-
}
|
|
4290
|
-
return result;
|
|
4291
|
-
}
|
|
4292
|
-
function fromFirestoreValue(value) {
|
|
4293
|
-
if ("nullValue" in value) return null;
|
|
4294
|
-
if ("booleanValue" in value) return value.booleanValue;
|
|
4295
|
-
if ("integerValue" in value) return parseInt(value.integerValue, 10);
|
|
4296
|
-
if ("doubleValue" in value) return value.doubleValue;
|
|
4297
|
-
if ("stringValue" in value) return value.stringValue;
|
|
4298
|
-
if ("timestampValue" in value) return value.timestampValue;
|
|
4299
|
-
if ("arrayValue" in value) {
|
|
4300
|
-
return (value.arrayValue.values ?? []).map(fromFirestoreValue);
|
|
4301
|
-
}
|
|
4302
|
-
if ("mapValue" in value) {
|
|
4303
|
-
return fromFirestoreFields(value.mapValue.fields);
|
|
4304
|
-
}
|
|
4305
|
-
return null;
|
|
4306
|
-
}
|
|
4307
|
-
function fromFirestoreFields(fields) {
|
|
4308
|
-
const result = {};
|
|
4309
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
4310
|
-
result[k] = fromFirestoreValue(v);
|
|
4311
|
-
}
|
|
4312
|
-
return result;
|
|
4313
|
-
}
|
|
4314
|
-
function sanitize(id) {
|
|
4315
|
-
return id.replace(/[/.]/g, "_").slice(0, 1500);
|
|
4316
|
-
}
|
|
4317
|
-
|
|
4318
|
-
export { AnomalyDetector, ConsoleExporter, DigestChain, FeatureFlagManager, FileStorage, FirestoreStorageAdapter, JsonFileExporter, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, PLAN_LIMITS, PlanManager, TrustScorer, UsdcCompliance, isFeatureAvailable, requirePlan, verifyExportedChain };
|
|
4180
|
+
export { AnomalyDetector, ConsoleExporter, DigestChain, FeatureFlagManager, FileStorage, JsonFileExporter, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, PLAN_LIMITS, PaymentCompliance, PlanManager, TrustScorer, UsdcCompliance, isCryptoTransaction, isFeatureAvailable, requirePlan, verifyExportedChain };
|
|
4319
4181
|
//# sourceMappingURL=index.mjs.map
|
|
4320
4182
|
//# sourceMappingURL=index.mjs.map
|