@suveren/gateway 0.29.0 → 0.29.1
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/content/integrations/gmail.json +13 -2
- package/content/integrations/mail.json +12 -2
- package/content/integrations/mollie.json +10 -0
- package/dist/control-plane/index.mjs +24 -6
- package/dist/mcp-server/http.mjs +656 -388
- package/dist/ui/assets/{index-C3vTF2Ye.js → index-wtX8LuiU.js} +10 -10
- package/dist/ui/index.html +1 -1
- package/node_modules/sanitize-html/index.js +47 -10
- package/node_modules/sanitize-html/package.json +2 -3
- package/package.json +1 -1
- package/node_modules/deepmerge/.editorconfig +0 -7
- package/node_modules/deepmerge/.eslintcache +0 -1
- package/node_modules/deepmerge/changelog.md +0 -167
- package/node_modules/deepmerge/dist/cjs.js +0 -133
- package/node_modules/deepmerge/dist/umd.js +0 -139
- package/node_modules/deepmerge/index.d.ts +0 -20
- package/node_modules/deepmerge/index.js +0 -106
- package/node_modules/deepmerge/license.txt +0 -21
- package/node_modules/deepmerge/package.json +0 -42
- package/node_modules/deepmerge/readme.md +0 -264
- package/node_modules/deepmerge/rollup.config.js +0 -22
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -2190,21 +2190,168 @@ function nowSec() {
|
|
|
2190
2190
|
return Math.floor(Date.now() / 1e3);
|
|
2191
2191
|
}
|
|
2192
2192
|
|
|
2193
|
-
// src/lib/
|
|
2194
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9,
|
|
2193
|
+
// src/lib/execution-result-store.ts
|
|
2194
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
2195
2195
|
import { homedir as homedir6 } from "os";
|
|
2196
2196
|
import { dirname as dirname7, join as join9 } from "path";
|
|
2197
|
+
import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
|
|
2197
2198
|
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join9(homedir6(), ".suveren");
|
|
2198
|
-
var FILE_NAME2 = "proposal-submissions.json";
|
|
2199
2199
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
2200
|
+
var MAX_BYTES = 16 * 1024;
|
|
2201
|
+
function capResult(text, structuredContent) {
|
|
2202
|
+
const structJson = structuredContent ? JSON.stringify(structuredContent) : void 0;
|
|
2203
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
2204
|
+
const structBytes = structJson ? Buffer.byteLength(structJson, "utf8") : 0;
|
|
2205
|
+
if (textBytes + structBytes <= MAX_BYTES) {
|
|
2206
|
+
return { text, ...structuredContent ? { structuredContent } : {}, truncated: false };
|
|
2207
|
+
}
|
|
2208
|
+
if (textBytes <= MAX_BYTES) {
|
|
2209
|
+
return { text, truncated: true };
|
|
2210
|
+
}
|
|
2211
|
+
const cappedText = Buffer.from(text, "utf8").subarray(0, MAX_BYTES).toString("utf8");
|
|
2212
|
+
return { text: cappedText, truncated: true };
|
|
2213
|
+
}
|
|
2214
|
+
var ExecutionResultStore = class {
|
|
2215
|
+
entries = [];
|
|
2216
|
+
baseDir;
|
|
2217
|
+
vaultKey = null;
|
|
2218
|
+
constructor(filePath) {
|
|
2219
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR6;
|
|
2220
|
+
this.loadPlaintext();
|
|
2221
|
+
}
|
|
2222
|
+
/** Mirrors OutcomeOutbox.setVaultKey — migrates plaintext -> encrypted once, in place. */
|
|
2223
|
+
setVaultKey(key) {
|
|
2224
|
+
this.vaultKey = key;
|
|
2225
|
+
if (existsSync9(this.encryptedFilePath)) {
|
|
2226
|
+
this.loadEncrypted();
|
|
2227
|
+
} else if (this.entries.length > 0) {
|
|
2228
|
+
this.persistEncrypted();
|
|
2229
|
+
if (existsSync9(this.plaintextFilePath)) {
|
|
2230
|
+
try {
|
|
2231
|
+
unlinkSync5(this.plaintextFilePath);
|
|
2232
|
+
} catch {
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* True when encrypted evidence exists on disk that this process cannot
|
|
2239
|
+
* read yet (no vault key). Callers should not report "nothing retained" in
|
|
2240
|
+
* this state — that would claim a hollow absence over unreadable content.
|
|
2241
|
+
*/
|
|
2242
|
+
isLocked() {
|
|
2243
|
+
return this.vaultKey === null && existsSync9(this.encryptedFilePath);
|
|
2244
|
+
}
|
|
2245
|
+
/** Record the outcome of one execution. Replaces any prior entry for the same proposal. */
|
|
2246
|
+
record(input, now = nowSec2()) {
|
|
2247
|
+
const capped = capResult(input.text, input.structuredContent);
|
|
2248
|
+
this.entries = this.entries.filter((e) => e.proposalId !== input.proposalId);
|
|
2249
|
+
this.entries.push({
|
|
2250
|
+
proposalId: input.proposalId,
|
|
2251
|
+
ticketId: input.ticketId,
|
|
2252
|
+
ok: input.ok,
|
|
2253
|
+
text: capped.text,
|
|
2254
|
+
...capped.structuredContent ? { structuredContent: capped.structuredContent } : {},
|
|
2255
|
+
...capped.truncated ? { truncated: true } : {},
|
|
2256
|
+
recordedAt: now
|
|
2257
|
+
});
|
|
2258
|
+
this.prune(now);
|
|
2259
|
+
this.persist();
|
|
2260
|
+
}
|
|
2261
|
+
/** The retained outcome for a proposal, or undefined if never recorded (or pruned). */
|
|
2262
|
+
get(proposalId) {
|
|
2263
|
+
return this.entries.find((e) => e.proposalId === proposalId);
|
|
2264
|
+
}
|
|
2265
|
+
/** Diagnostic / tests. */
|
|
2266
|
+
get size() {
|
|
2267
|
+
return this.entries.length;
|
|
2268
|
+
}
|
|
2269
|
+
// ─── Retention ────────────────────────────────────────────────────────────
|
|
2270
|
+
prune(now) {
|
|
2271
|
+
const cutoff = now - MAX_AGE_SECONDS3;
|
|
2272
|
+
this.entries = this.entries.filter((e) => e.recordedAt >= cutoff);
|
|
2273
|
+
}
|
|
2274
|
+
// ─── Encryption (mirrors OutcomeOutbox / DenialLog) ────────────────────────
|
|
2275
|
+
encrypt(plaintext) {
|
|
2276
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2277
|
+
const iv = randomBytes5(12);
|
|
2278
|
+
const cipher = createCipheriv5("aes-256-gcm", this.vaultKey, iv);
|
|
2279
|
+
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2280
|
+
return { iv: iv.toString("hex"), ciphertext: enc.toString("hex"), tag: cipher.getAuthTag().toString("hex") };
|
|
2281
|
+
}
|
|
2282
|
+
decrypt(blob) {
|
|
2283
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2284
|
+
const decipher = createDecipheriv5("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2285
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2286
|
+
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
2287
|
+
}
|
|
2288
|
+
get plaintextFilePath() {
|
|
2289
|
+
return join9(this.baseDir, "execution-results.json");
|
|
2290
|
+
}
|
|
2291
|
+
get encryptedFilePath() {
|
|
2292
|
+
return join9(this.baseDir, "execution-results.enc.json");
|
|
2293
|
+
}
|
|
2294
|
+
loadPlaintext() {
|
|
2295
|
+
if (!existsSync9(this.plaintextFilePath)) {
|
|
2296
|
+
mkdirSync7(this.baseDir, { recursive: true });
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
try {
|
|
2300
|
+
const data = JSON.parse(readFileSync10(this.plaintextFilePath, "utf-8"));
|
|
2301
|
+
this.entries = data.entries ?? [];
|
|
2302
|
+
this.prune(nowSec2());
|
|
2303
|
+
} catch {
|
|
2304
|
+
console.error(`[ExecutionResultStore] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
2305
|
+
this.entries = [];
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
loadEncrypted() {
|
|
2309
|
+
if (!existsSync9(this.encryptedFilePath)) return;
|
|
2310
|
+
try {
|
|
2311
|
+
const data = JSON.parse(readFileSync10(this.encryptedFilePath, "utf-8"));
|
|
2312
|
+
const storeFile = JSON.parse(this.decrypt(data.blob));
|
|
2313
|
+
this.entries = storeFile.entries ?? [];
|
|
2314
|
+
this.prune(nowSec2());
|
|
2315
|
+
} catch (err) {
|
|
2316
|
+
console.error(`[ExecutionResultStore] Could not decrypt ${this.encryptedFilePath}:`, err);
|
|
2317
|
+
this.entries = [];
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
persist() {
|
|
2321
|
+
if (this.vaultKey) this.persistEncrypted();
|
|
2322
|
+
else this.persistPlaintext();
|
|
2323
|
+
}
|
|
2324
|
+
persistPlaintext() {
|
|
2325
|
+
const data = { version: 1, entries: this.entries };
|
|
2326
|
+
mkdirSync7(this.baseDir, { recursive: true, mode: 448 });
|
|
2327
|
+
writeFileSync7(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2328
|
+
}
|
|
2329
|
+
persistEncrypted() {
|
|
2330
|
+
const storeFile = { version: 1, entries: this.entries };
|
|
2331
|
+
const data = { version: 1, blob: this.encrypt(JSON.stringify(storeFile)) };
|
|
2332
|
+
mkdirSync7(this.baseDir, { recursive: true, mode: 448 });
|
|
2333
|
+
writeFileSync7(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2200
2336
|
function nowSec2() {
|
|
2201
2337
|
return Math.floor(Date.now() / 1e3);
|
|
2202
2338
|
}
|
|
2339
|
+
|
|
2340
|
+
// src/lib/proposal-submission-store.ts
|
|
2341
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync2 } from "fs";
|
|
2342
|
+
import { homedir as homedir7 } from "os";
|
|
2343
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
2344
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
2345
|
+
var FILE_NAME2 = "proposal-submissions.json";
|
|
2346
|
+
var MAX_AGE_SECONDS4 = 90 * 24 * 60 * 60;
|
|
2347
|
+
function nowSec3() {
|
|
2348
|
+
return Math.floor(Date.now() / 1e3);
|
|
2349
|
+
}
|
|
2203
2350
|
var ProposalSubmissionStore = class {
|
|
2204
2351
|
filePath;
|
|
2205
2352
|
constructor(filePath) {
|
|
2206
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
2207
|
-
this.filePath =
|
|
2353
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR7;
|
|
2354
|
+
this.filePath = join10(baseDir, FILE_NAME2);
|
|
2208
2355
|
}
|
|
2209
2356
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
2210
2357
|
record(entry) {
|
|
@@ -2217,7 +2364,7 @@ var ProposalSubmissionStore = class {
|
|
|
2217
2364
|
executionContextHash: hashToolArgs(entry.executionContext),
|
|
2218
2365
|
authorizationId: entry.authorizationId,
|
|
2219
2366
|
profileId: entry.profileId,
|
|
2220
|
-
submittedAt:
|
|
2367
|
+
submittedAt: nowSec3(),
|
|
2221
2368
|
...entry.previewHash ? { previewHash: entry.previewHash } : {}
|
|
2222
2369
|
});
|
|
2223
2370
|
this.persist(file);
|
|
@@ -2228,10 +2375,10 @@ var ProposalSubmissionStore = class {
|
|
|
2228
2375
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
2229
2376
|
}
|
|
2230
2377
|
load() {
|
|
2231
|
-
if (!
|
|
2378
|
+
if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
|
|
2232
2379
|
try {
|
|
2233
|
-
const parsed = JSON.parse(
|
|
2234
|
-
const cutoff =
|
|
2380
|
+
const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
|
|
2381
|
+
const cutoff = nowSec3() - MAX_AGE_SECONDS4;
|
|
2235
2382
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
2236
2383
|
return parsed;
|
|
2237
2384
|
} catch {
|
|
@@ -2239,26 +2386,26 @@ var ProposalSubmissionStore = class {
|
|
|
2239
2386
|
}
|
|
2240
2387
|
}
|
|
2241
2388
|
persist(file) {
|
|
2242
|
-
|
|
2389
|
+
mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
|
|
2243
2390
|
const tmpPath = `${this.filePath}.tmp`;
|
|
2244
|
-
|
|
2391
|
+
writeFileSync8(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2245
2392
|
renameSync2(tmpPath, this.filePath);
|
|
2246
2393
|
}
|
|
2247
2394
|
};
|
|
2248
2395
|
|
|
2249
2396
|
// src/lib/preview-log.ts
|
|
2250
|
-
import { mkdirSync as
|
|
2251
|
-
import { homedir as
|
|
2252
|
-
import { dirname as
|
|
2253
|
-
var
|
|
2397
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, renameSync as renameSync3 } from "fs";
|
|
2398
|
+
import { homedir as homedir8 } from "os";
|
|
2399
|
+
import { dirname as dirname9, join as join11 } from "path";
|
|
2400
|
+
var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
2254
2401
|
var FILE_NAME3 = "preview-log.json";
|
|
2255
2402
|
var MAX_RECORDS2 = 500;
|
|
2256
2403
|
var MAX_AGE_MS2 = 30 * 24 * 60 * 60 * 1e3;
|
|
2257
2404
|
var PreviewLog = class {
|
|
2258
2405
|
filePath;
|
|
2259
2406
|
constructor(filePath) {
|
|
2260
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
2261
|
-
this.filePath =
|
|
2407
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname9(filePath) : filePath ?? DEFAULT_DIR8;
|
|
2408
|
+
this.filePath = join11(baseDir, FILE_NAME3);
|
|
2262
2409
|
}
|
|
2263
2410
|
/** Record one preview read. Never throws — recording must not break the read. */
|
|
2264
2411
|
record(entry) {
|
|
@@ -2280,28 +2427,28 @@ var PreviewLog = class {
|
|
|
2280
2427
|
return [...this.load().entries];
|
|
2281
2428
|
}
|
|
2282
2429
|
load() {
|
|
2283
|
-
if (!
|
|
2430
|
+
if (!existsSync11(this.filePath)) return { version: 1, entries: [] };
|
|
2284
2431
|
try {
|
|
2285
|
-
const parsed = JSON.parse(
|
|
2432
|
+
const parsed = JSON.parse(readFileSync12(this.filePath, "utf-8"));
|
|
2286
2433
|
return { version: 1, entries: parsed.entries ?? [] };
|
|
2287
2434
|
} catch {
|
|
2288
2435
|
return { version: 1, entries: [] };
|
|
2289
2436
|
}
|
|
2290
2437
|
}
|
|
2291
2438
|
persist(file) {
|
|
2292
|
-
|
|
2439
|
+
mkdirSync9(dirname9(this.filePath), { recursive: true, mode: 448 });
|
|
2293
2440
|
const tmp = `${this.filePath}.tmp`;
|
|
2294
|
-
|
|
2441
|
+
writeFileSync9(tmp, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2295
2442
|
renameSync3(tmp, this.filePath);
|
|
2296
2443
|
}
|
|
2297
2444
|
};
|
|
2298
2445
|
|
|
2299
2446
|
// src/lib/outcome-outbox.ts
|
|
2300
|
-
import { mkdirSync as
|
|
2301
|
-
import { homedir as
|
|
2302
|
-
import { dirname as
|
|
2303
|
-
import { createCipheriv as
|
|
2304
|
-
var
|
|
2447
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12, unlinkSync as unlinkSync6 } from "fs";
|
|
2448
|
+
import { homedir as homedir9 } from "os";
|
|
2449
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
2450
|
+
import { createCipheriv as createCipheriv6, createDecipheriv as createDecipheriv6, randomBytes as randomBytes6 } from "crypto";
|
|
2451
|
+
var DEFAULT_DIR9 = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
|
|
2305
2452
|
var MAX_AGE_MS3 = 7 * 24 * 60 * 60 * 1e3;
|
|
2306
2453
|
function outcomeEntryFor(proposal, submittedHere, who, nowMs) {
|
|
2307
2454
|
if (!submittedHere) return null;
|
|
@@ -2316,19 +2463,19 @@ var OutcomeOutbox = class {
|
|
|
2316
2463
|
baseDir;
|
|
2317
2464
|
vaultKey = null;
|
|
2318
2465
|
constructor(filePath) {
|
|
2319
|
-
this.baseDir = filePath && filePath.endsWith(".json") ?
|
|
2466
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname10(filePath) : filePath ?? DEFAULT_DIR9;
|
|
2320
2467
|
this.loadPlaintext();
|
|
2321
2468
|
}
|
|
2322
2469
|
/** Mirrors DenialLog.setVaultKey — migrates plaintext -> encrypted once, in place. */
|
|
2323
2470
|
setVaultKey(key) {
|
|
2324
2471
|
this.vaultKey = key;
|
|
2325
|
-
if (
|
|
2472
|
+
if (existsSync12(this.encryptedFilePath)) {
|
|
2326
2473
|
this.loadEncrypted();
|
|
2327
2474
|
} else if (this.entries.length > 0) {
|
|
2328
2475
|
this.persistEncrypted();
|
|
2329
|
-
if (
|
|
2476
|
+
if (existsSync12(this.plaintextFilePath)) {
|
|
2330
2477
|
try {
|
|
2331
|
-
|
|
2478
|
+
unlinkSync6(this.plaintextFilePath);
|
|
2332
2479
|
} catch {
|
|
2333
2480
|
}
|
|
2334
2481
|
}
|
|
@@ -2377,30 +2524,30 @@ var OutcomeOutbox = class {
|
|
|
2377
2524
|
// ─── Encryption (mirrors DenialLog / ExecutionLog) ─────────────────────────
|
|
2378
2525
|
encrypt(plaintext) {
|
|
2379
2526
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2380
|
-
const iv =
|
|
2381
|
-
const cipher =
|
|
2527
|
+
const iv = randomBytes6(12);
|
|
2528
|
+
const cipher = createCipheriv6("aes-256-gcm", this.vaultKey, iv);
|
|
2382
2529
|
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2383
2530
|
return { iv: iv.toString("hex"), ciphertext: enc.toString("hex"), tag: cipher.getAuthTag().toString("hex") };
|
|
2384
2531
|
}
|
|
2385
2532
|
decrypt(blob) {
|
|
2386
2533
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2387
|
-
const decipher =
|
|
2534
|
+
const decipher = createDecipheriv6("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2388
2535
|
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2389
2536
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
2390
2537
|
}
|
|
2391
2538
|
get plaintextFilePath() {
|
|
2392
|
-
return
|
|
2539
|
+
return join12(this.baseDir, "outcome-outbox.json");
|
|
2393
2540
|
}
|
|
2394
2541
|
get encryptedFilePath() {
|
|
2395
|
-
return
|
|
2542
|
+
return join12(this.baseDir, "outcome-outbox.enc.json");
|
|
2396
2543
|
}
|
|
2397
2544
|
loadPlaintext() {
|
|
2398
|
-
if (!
|
|
2399
|
-
|
|
2545
|
+
if (!existsSync12(this.plaintextFilePath)) {
|
|
2546
|
+
mkdirSync10(this.baseDir, { recursive: true });
|
|
2400
2547
|
return;
|
|
2401
2548
|
}
|
|
2402
2549
|
try {
|
|
2403
|
-
const data = JSON.parse(
|
|
2550
|
+
const data = JSON.parse(readFileSync13(this.plaintextFilePath, "utf-8"));
|
|
2404
2551
|
this.entries = data.entries ?? [];
|
|
2405
2552
|
this.prune(Date.now());
|
|
2406
2553
|
} catch {
|
|
@@ -2409,9 +2556,9 @@ var OutcomeOutbox = class {
|
|
|
2409
2556
|
}
|
|
2410
2557
|
}
|
|
2411
2558
|
loadEncrypted() {
|
|
2412
|
-
if (!
|
|
2559
|
+
if (!existsSync12(this.encryptedFilePath)) return;
|
|
2413
2560
|
try {
|
|
2414
|
-
const data = JSON.parse(
|
|
2561
|
+
const data = JSON.parse(readFileSync13(this.encryptedFilePath, "utf-8"));
|
|
2415
2562
|
const storeFile = JSON.parse(this.decrypt(data.blob));
|
|
2416
2563
|
this.entries = storeFile.entries ?? [];
|
|
2417
2564
|
this.prune(Date.now());
|
|
@@ -2426,14 +2573,14 @@ var OutcomeOutbox = class {
|
|
|
2426
2573
|
}
|
|
2427
2574
|
persistPlaintext() {
|
|
2428
2575
|
const data = { version: 1, entries: this.entries };
|
|
2429
|
-
|
|
2430
|
-
|
|
2576
|
+
mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
|
|
2577
|
+
writeFileSync10(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2431
2578
|
}
|
|
2432
2579
|
persistEncrypted() {
|
|
2433
2580
|
const storeFile = { version: 1, entries: this.entries };
|
|
2434
2581
|
const data = { version: 1, blob: this.encrypt(JSON.stringify(storeFile)) };
|
|
2435
|
-
|
|
2436
|
-
|
|
2582
|
+
mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
|
|
2583
|
+
writeFileSync10(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2437
2584
|
}
|
|
2438
2585
|
};
|
|
2439
2586
|
function formatOutcomeLine(e) {
|
|
@@ -2545,10 +2692,10 @@ var MCPGatekeeper = class {
|
|
|
2545
2692
|
};
|
|
2546
2693
|
|
|
2547
2694
|
// src/lib/report/report-store.ts
|
|
2548
|
-
import { mkdirSync as
|
|
2549
|
-
import { homedir as
|
|
2550
|
-
import { dirname as
|
|
2551
|
-
import { createCipheriv as
|
|
2695
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync13, unlinkSync as unlinkSync7 } from "fs";
|
|
2696
|
+
import { homedir as homedir10 } from "os";
|
|
2697
|
+
import { dirname as dirname11, join as join13 } from "path";
|
|
2698
|
+
import { createCipheriv as createCipheriv7, createDecipheriv as createDecipheriv7, randomBytes as randomBytes7 } from "crypto";
|
|
2552
2699
|
|
|
2553
2700
|
// src/lib/report/record-resolvers.ts
|
|
2554
2701
|
function findById(rows, id) {
|
|
@@ -2856,29 +3003,29 @@ async function buildCoverage(caseElements, getExport, archive, referenced, windo
|
|
|
2856
3003
|
}
|
|
2857
3004
|
|
|
2858
3005
|
// src/lib/report/report-store.ts
|
|
2859
|
-
var
|
|
3006
|
+
var DEFAULT_DIR10 = process.env.SUVEREN_DATA_DIR ?? join13(homedir10(), ".suveren");
|
|
2860
3007
|
var ReportStore = class {
|
|
2861
3008
|
report = null;
|
|
2862
3009
|
baseDir;
|
|
2863
3010
|
vaultKey = null;
|
|
2864
3011
|
constructor(filePath) {
|
|
2865
3012
|
if (filePath && filePath.endsWith(".json")) {
|
|
2866
|
-
this.baseDir =
|
|
3013
|
+
this.baseDir = dirname11(filePath);
|
|
2867
3014
|
} else {
|
|
2868
|
-
this.baseDir = filePath ??
|
|
3015
|
+
this.baseDir = filePath ?? DEFAULT_DIR10;
|
|
2869
3016
|
}
|
|
2870
3017
|
this.loadPlaintext();
|
|
2871
3018
|
}
|
|
2872
3019
|
/** Set the vault key for encryption. Triggers migration from plaintext if needed. */
|
|
2873
3020
|
setVaultKey(key) {
|
|
2874
3021
|
this.vaultKey = key;
|
|
2875
|
-
if (
|
|
3022
|
+
if (existsSync13(this.encryptedFilePath)) {
|
|
2876
3023
|
this.loadEncrypted();
|
|
2877
3024
|
} else if (this.report !== null) {
|
|
2878
3025
|
this.persistEncrypted();
|
|
2879
|
-
if (
|
|
3026
|
+
if (existsSync13(this.plaintextFilePath)) {
|
|
2880
3027
|
try {
|
|
2881
|
-
|
|
3028
|
+
unlinkSync7(this.plaintextFilePath);
|
|
2882
3029
|
} catch {
|
|
2883
3030
|
}
|
|
2884
3031
|
}
|
|
@@ -2891,7 +3038,7 @@ var ReportStore = class {
|
|
|
2891
3038
|
* "the report exists but is locked".
|
|
2892
3039
|
*/
|
|
2893
3040
|
isLocked() {
|
|
2894
|
-
return this.vaultKey === null &&
|
|
3041
|
+
return this.vaultKey === null && existsSync13(this.encryptedFilePath);
|
|
2895
3042
|
}
|
|
2896
3043
|
/** The stored, already-verified report — or null if none has been saved
|
|
2897
3044
|
* (and the store is not locked; check `isLocked()` first). */
|
|
@@ -2925,49 +3072,49 @@ var ReportStore = class {
|
|
|
2925
3072
|
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
2926
3073
|
encrypt(plaintext) {
|
|
2927
3074
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2928
|
-
const iv =
|
|
2929
|
-
const cipher =
|
|
3075
|
+
const iv = randomBytes7(12);
|
|
3076
|
+
const cipher = createCipheriv7("aes-256-gcm", this.vaultKey, iv);
|
|
2930
3077
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2931
3078
|
const tag = cipher.getAuthTag();
|
|
2932
3079
|
return { iv: iv.toString("hex"), ciphertext: encrypted.toString("hex"), tag: tag.toString("hex") };
|
|
2933
3080
|
}
|
|
2934
3081
|
decrypt(blob) {
|
|
2935
3082
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2936
|
-
const decipher =
|
|
3083
|
+
const decipher = createDecipheriv7("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2937
3084
|
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2938
3085
|
const decrypted = Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]);
|
|
2939
3086
|
return decrypted.toString("utf8");
|
|
2940
3087
|
}
|
|
2941
3088
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
2942
3089
|
get plaintextFilePath() {
|
|
2943
|
-
return
|
|
3090
|
+
return join13(this.baseDir, "report.json");
|
|
2944
3091
|
}
|
|
2945
3092
|
get encryptedFilePath() {
|
|
2946
|
-
return
|
|
3093
|
+
return join13(this.baseDir, "report.enc.json");
|
|
2947
3094
|
}
|
|
2948
3095
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
2949
3096
|
loadPlaintext() {
|
|
2950
|
-
if (!
|
|
2951
|
-
|
|
3097
|
+
if (!existsSync13(this.plaintextFilePath)) {
|
|
3098
|
+
mkdirSync11(this.baseDir, { recursive: true });
|
|
2952
3099
|
return;
|
|
2953
3100
|
}
|
|
2954
3101
|
try {
|
|
2955
|
-
const raw =
|
|
3102
|
+
const raw = readFileSync14(this.plaintextFilePath, "utf-8");
|
|
2956
3103
|
const parsed = JSON.parse(raw);
|
|
2957
3104
|
this.report = parsed.report ?? null;
|
|
2958
3105
|
} catch {
|
|
2959
3106
|
console.error(`[ReportStore] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
2960
3107
|
try {
|
|
2961
|
-
|
|
3108
|
+
writeFileSync11(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync14(this.plaintextFilePath));
|
|
2962
3109
|
} catch {
|
|
2963
3110
|
}
|
|
2964
3111
|
this.report = null;
|
|
2965
3112
|
}
|
|
2966
3113
|
}
|
|
2967
3114
|
loadEncrypted() {
|
|
2968
|
-
if (!
|
|
3115
|
+
if (!existsSync13(this.encryptedFilePath)) return;
|
|
2969
3116
|
try {
|
|
2970
|
-
const raw =
|
|
3117
|
+
const raw = readFileSync14(this.encryptedFilePath, "utf-8");
|
|
2971
3118
|
const data = JSON.parse(raw);
|
|
2972
3119
|
const decrypted = this.decrypt(data.blob);
|
|
2973
3120
|
const parsed = JSON.parse(decrypted);
|
|
@@ -2975,7 +3122,7 @@ var ReportStore = class {
|
|
|
2975
3122
|
} catch (err) {
|
|
2976
3123
|
console.error(`[ReportStore] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
2977
3124
|
try {
|
|
2978
|
-
|
|
3125
|
+
writeFileSync11(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync14(this.encryptedFilePath));
|
|
2979
3126
|
} catch {
|
|
2980
3127
|
}
|
|
2981
3128
|
this.report = null;
|
|
@@ -2990,16 +3137,16 @@ var ReportStore = class {
|
|
|
2990
3137
|
}
|
|
2991
3138
|
persistPlaintext() {
|
|
2992
3139
|
const data = { version: 1, report: this.report };
|
|
2993
|
-
|
|
2994
|
-
|
|
3140
|
+
mkdirSync11(this.baseDir, { recursive: true, mode: 448 });
|
|
3141
|
+
writeFileSync11(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2995
3142
|
}
|
|
2996
3143
|
persistEncrypted() {
|
|
2997
3144
|
const data = {
|
|
2998
3145
|
version: 1,
|
|
2999
3146
|
blob: this.encrypt(JSON.stringify({ version: 1, report: this.report }))
|
|
3000
3147
|
};
|
|
3001
|
-
|
|
3002
|
-
|
|
3148
|
+
mkdirSync11(this.baseDir, { recursive: true, mode: 448 });
|
|
3149
|
+
writeFileSync11(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3003
3150
|
}
|
|
3004
3151
|
};
|
|
3005
3152
|
|
|
@@ -3017,6 +3164,8 @@ var SharedState = class {
|
|
|
3017
3164
|
reportStore;
|
|
3018
3165
|
/** Which tickets this Gatekeeper has executed — the "one execution per ticket" half of exactly-once. */
|
|
3019
3166
|
executionJournal;
|
|
3167
|
+
/** The content half of what executionJournal records — see execution-result-store.ts. */
|
|
3168
|
+
executionResults;
|
|
3020
3169
|
/** What THIS gateway submitted for each review-mode proposal it created —
|
|
3021
3170
|
* see ticket-verify.ts / commitments.ts. */
|
|
3022
3171
|
proposalSubmissions;
|
|
@@ -3057,6 +3206,7 @@ var SharedState = class {
|
|
|
3057
3206
|
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
3058
3207
|
this.reportStore = new ReportStore(gateStorePath);
|
|
3059
3208
|
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
3209
|
+
this.executionResults = new ExecutionResultStore(gateStorePath);
|
|
3060
3210
|
this.proposalSubmissions = new ProposalSubmissionStore(gateStorePath);
|
|
3061
3211
|
this.previewLog = new PreviewLog(gateStorePath);
|
|
3062
3212
|
this.outcomeOutbox = new OutcomeOutbox(gateStorePath);
|
|
@@ -3180,7 +3330,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3180
3330
|
import { z as z2 } from "zod";
|
|
3181
3331
|
|
|
3182
3332
|
// src/lib/mandate-brief.ts
|
|
3183
|
-
import { getProfile as
|
|
3333
|
+
import { getProfile as getProfile5 } from "@hap/core";
|
|
3184
3334
|
|
|
3185
3335
|
// src/lib/consumption.ts
|
|
3186
3336
|
import { boundActionTypes } from "@hap/core";
|
|
@@ -3248,29 +3398,29 @@ function formatConsumptionFull(entries) {
|
|
|
3248
3398
|
}
|
|
3249
3399
|
|
|
3250
3400
|
// src/lib/context-loader.ts
|
|
3251
|
-
import { readFileSync as
|
|
3252
|
-
import { homedir as
|
|
3253
|
-
import { join as
|
|
3254
|
-
var
|
|
3401
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12, renameSync as renameSync4 } from "fs";
|
|
3402
|
+
import { homedir as homedir11 } from "os";
|
|
3403
|
+
import { join as join14, dirname as dirname12 } from "path";
|
|
3404
|
+
var DEFAULT_DIR11 = process.env.SUVEREN_DATA_DIR ?? join14(homedir11(), ".suveren");
|
|
3255
3405
|
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
3256
3406
|
function contextFilePath(dataDir2) {
|
|
3257
|
-
return
|
|
3407
|
+
return join14(dataDir2 ?? DEFAULT_DIR11, "context.md");
|
|
3258
3408
|
}
|
|
3259
3409
|
function writeContextFile(content, dataDir2) {
|
|
3260
3410
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
3261
3411
|
if (bytes > CONTEXT_MAX_BYTES) throw new Error(`the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}.`);
|
|
3262
3412
|
const filePath = contextFilePath(dataDir2);
|
|
3263
|
-
|
|
3413
|
+
mkdirSync12(dirname12(filePath), { recursive: true });
|
|
3264
3414
|
const tmpPath = `${filePath}.tmp`;
|
|
3265
|
-
|
|
3415
|
+
writeFileSync12(tmpPath, content, "utf-8");
|
|
3266
3416
|
renameSync4(tmpPath, filePath);
|
|
3267
3417
|
}
|
|
3268
3418
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
3269
3419
|
function readContextFile(dataDir2) {
|
|
3270
3420
|
const filePath = contextFilePath(dataDir2);
|
|
3271
|
-
if (!
|
|
3421
|
+
if (!existsSync14(filePath)) return null;
|
|
3272
3422
|
try {
|
|
3273
|
-
const content =
|
|
3423
|
+
const content = readFileSync15(filePath, "utf-8").trim();
|
|
3274
3424
|
return content || null;
|
|
3275
3425
|
} catch {
|
|
3276
3426
|
return null;
|
|
@@ -3457,23 +3607,244 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
3457
3607
|
boundContent
|
|
3458
3608
|
};
|
|
3459
3609
|
}
|
|
3460
|
-
function attachTicketId(tool, args, ticketId) {
|
|
3461
|
-
const schema = tool.inputSchema;
|
|
3462
|
-
if (!schema?.properties || !("ticket_id" in schema.properties)) return args;
|
|
3463
|
-
return { ...args, ticket_id: ticketId };
|
|
3610
|
+
function attachTicketId(tool, args, ticketId) {
|
|
3611
|
+
const schema = tool.inputSchema;
|
|
3612
|
+
if (!schema?.properties || !("ticket_id" in schema.properties)) return args;
|
|
3613
|
+
return { ...args, ticket_id: ticketId };
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// src/lib/preview.ts
|
|
3617
|
+
import { computeContentHash as computeContentHash2 } from "@hap/core";
|
|
3618
|
+
|
|
3619
|
+
// src/lib/manifest-loader.ts
|
|
3620
|
+
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
3621
|
+
import { join as join15, resolve } from "path";
|
|
3622
|
+
import { getProfile as getProfile2 } from "@hap/core";
|
|
3623
|
+
|
|
3624
|
+
// src/lib/tool-gating-types.ts
|
|
3625
|
+
var MAX_PREVIEW_READS = 10;
|
|
3626
|
+
var MAX_FOREACH_ELEMENTS = 50;
|
|
3627
|
+
var MAX_RECORD_KEY_STEPS = 2;
|
|
3628
|
+
|
|
3629
|
+
// src/lib/scope-specificity.ts
|
|
3630
|
+
function tokenSet(value) {
|
|
3631
|
+
if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
|
|
3632
|
+
return new Set(
|
|
3633
|
+
String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
function subsetOrEqual(a, b) {
|
|
3637
|
+
if (b.size === 0) return true;
|
|
3638
|
+
if (a.size === 0) return false;
|
|
3639
|
+
for (const x of a) if (!b.has(x)) return false;
|
|
3640
|
+
return true;
|
|
3641
|
+
}
|
|
3642
|
+
function atLeastAsSpecific(keys, a, b) {
|
|
3643
|
+
for (const k of keys) {
|
|
3644
|
+
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
3645
|
+
}
|
|
3646
|
+
return true;
|
|
3647
|
+
}
|
|
3648
|
+
function strictlyMoreSpecific(keys, a, b) {
|
|
3649
|
+
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
3650
|
+
}
|
|
3651
|
+
function selectAuthorization(contextKeys, passers) {
|
|
3652
|
+
if (passers.length === 1) {
|
|
3653
|
+
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
3654
|
+
}
|
|
3655
|
+
const dominators = passers.filter(
|
|
3656
|
+
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
3657
|
+
);
|
|
3658
|
+
if (dominators.length === 1) {
|
|
3659
|
+
const chosen2 = dominators[0];
|
|
3660
|
+
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
3661
|
+
}
|
|
3662
|
+
const approvers = passers.filter((p) => p.requiresApproval);
|
|
3663
|
+
const pool = approvers.length > 0 ? approvers : passers;
|
|
3664
|
+
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
3665
|
+
return {
|
|
3666
|
+
chosen,
|
|
3667
|
+
superseded: passers.filter((p) => p !== chosen),
|
|
3668
|
+
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// src/lib/read-gate.ts
|
|
3673
|
+
function boundsSatisfyReadGate(bounds, gate) {
|
|
3674
|
+
if (!gate.boundField) return true;
|
|
3675
|
+
const actual = bounds?.[gate.boundField];
|
|
3676
|
+
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
3677
|
+
}
|
|
3678
|
+
function readToolIsGoverned(g) {
|
|
3679
|
+
if (!g) return false;
|
|
3680
|
+
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
3681
|
+
}
|
|
3682
|
+
function allowedResources(contexts, boundField) {
|
|
3683
|
+
const out = /* @__PURE__ */ new Set();
|
|
3684
|
+
for (const ctx of contexts) {
|
|
3685
|
+
for (const v of tokenSet(ctx?.[boundField])) out.add(v);
|
|
3686
|
+
}
|
|
3687
|
+
return out;
|
|
3688
|
+
}
|
|
3689
|
+
function deniedResources(requested, allowed) {
|
|
3690
|
+
return requested.filter((r) => !allowed.has(r));
|
|
3691
|
+
}
|
|
3692
|
+
function filterItemsByResource(items, idPath, allowed) {
|
|
3693
|
+
return items.filter((it) => {
|
|
3694
|
+
const id = getByDottedPath(it, idPath);
|
|
3695
|
+
return typeof id === "string" && allowed.has(id);
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
function hasBlockedValue(item, valuesPath, blocked) {
|
|
3699
|
+
const v = getByDottedPath(item, valuesPath);
|
|
3700
|
+
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
3701
|
+
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
3702
|
+
}
|
|
3703
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3704
|
+
function parseMessageTimestamp(value) {
|
|
3705
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3706
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3707
|
+
const n = Number(value);
|
|
3708
|
+
if (Number.isFinite(n)) return n;
|
|
3709
|
+
const iso = Date.parse(value);
|
|
3710
|
+
if (Number.isFinite(iso)) return iso;
|
|
3711
|
+
}
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3714
|
+
function ageInDays(timestampMs, nowMs) {
|
|
3715
|
+
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
3716
|
+
}
|
|
3717
|
+
function maxReadAgeDays(boundsList, field) {
|
|
3718
|
+
let max = null;
|
|
3719
|
+
for (const b of boundsList) {
|
|
3720
|
+
const raw = b?.[field];
|
|
3721
|
+
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
3722
|
+
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
3723
|
+
}
|
|
3724
|
+
return max;
|
|
3725
|
+
}
|
|
3726
|
+
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
3727
|
+
if (timestampMs === null) return true;
|
|
3728
|
+
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
3729
|
+
}
|
|
3730
|
+
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
3731
|
+
function parensBalanced(s) {
|
|
3732
|
+
let depth = 0;
|
|
3733
|
+
let inQuote = false;
|
|
3734
|
+
for (const c of s) {
|
|
3735
|
+
if (c === '"') {
|
|
3736
|
+
inQuote = !inQuote;
|
|
3737
|
+
continue;
|
|
3738
|
+
}
|
|
3739
|
+
if (inQuote) continue;
|
|
3740
|
+
if (c === "(") depth++;
|
|
3741
|
+
else if (c === ")" && --depth < 0) return false;
|
|
3742
|
+
}
|
|
3743
|
+
return !inQuote && depth === 0;
|
|
3744
|
+
}
|
|
3745
|
+
function composeReadQuery(agentQuery, clauses) {
|
|
3746
|
+
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
3747
|
+
const base = (agentQuery ?? "").trim();
|
|
3748
|
+
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
3749
|
+
if (!parensBalanced(base)) {
|
|
3750
|
+
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
3751
|
+
}
|
|
3752
|
+
if (TRAILING_OPERATOR.test(base)) {
|
|
3753
|
+
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
3754
|
+
}
|
|
3755
|
+
if (injected.length === 0) return { ok: true, query: base };
|
|
3756
|
+
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
3757
|
+
}
|
|
3758
|
+
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
3759
|
+
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
3760
|
+
let re;
|
|
3761
|
+
try {
|
|
3762
|
+
re = new RegExp(conflictPattern, "gi");
|
|
3763
|
+
} catch {
|
|
3764
|
+
return null;
|
|
3765
|
+
}
|
|
3766
|
+
let worst = null;
|
|
3767
|
+
for (const match of agentQuery.matchAll(re)) {
|
|
3768
|
+
const days = Number(match[1]);
|
|
3769
|
+
if (!Number.isFinite(days)) continue;
|
|
3770
|
+
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
3771
|
+
}
|
|
3772
|
+
return worst;
|
|
3773
|
+
}
|
|
3774
|
+
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
3775
|
+
const fields = boundsSchema?.fields;
|
|
3776
|
+
if (!fields) return null;
|
|
3777
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
3778
|
+
if (def?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
|
|
3779
|
+
}
|
|
3780
|
+
return null;
|
|
3781
|
+
}
|
|
3782
|
+
function classifyEnumSwitches(boundsSchema) {
|
|
3783
|
+
const readSide = [];
|
|
3784
|
+
const writeSide = [];
|
|
3785
|
+
for (const [name, def] of Object.entries(boundsSchema?.fields ?? {})) {
|
|
3786
|
+
if (def?.boundType?.kind !== "enum") continue;
|
|
3787
|
+
const values = new Set(def.boundType.values ?? []);
|
|
3788
|
+
if (!values.has("none")) continue;
|
|
3789
|
+
if (values.size === 2 && values.has("unlimited")) readSide.push(name);
|
|
3790
|
+
else writeSide.push(name);
|
|
3791
|
+
}
|
|
3792
|
+
return { readSide, writeSide };
|
|
3793
|
+
}
|
|
3794
|
+
function uncoveredReadSwitchReason(overrides, boundsSchema) {
|
|
3795
|
+
const { readSide, writeSide } = classifyEnumSwitches(boundsSchema);
|
|
3796
|
+
const anySwitch = [...readSide, ...writeSide];
|
|
3797
|
+
if (anySwitch.length === 0) return null;
|
|
3798
|
+
const valuesOf = (field) => boundsSchema?.fields?.[field]?.boundType?.values ?? [];
|
|
3799
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
3800
|
+
if (entry !== null && typeof entry !== "object") continue;
|
|
3801
|
+
const e = entry ?? {};
|
|
3802
|
+
if (entry !== null && e.category !== "read") continue;
|
|
3803
|
+
if (e.readGovernance === "none") {
|
|
3804
|
+
if (!e.readGovernanceReason) {
|
|
3805
|
+
return `override "${toolName}" declares readGovernance:"none" with no readGovernanceReason \u2014 an exemption from this profile's switch(es) [${anySwitch.join(", ")}] needs a written reason`;
|
|
3806
|
+
}
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
if (!e.boundField || !anySwitch.includes(e.boundField)) {
|
|
3810
|
+
return `override "${toolName}" is category:"read" under a profile that declares enum switch(es) [${anySwitch.join(", ")}] (read-side: [${readSide.join(", ")}], write-side: [${writeSide.join(", ")}]), but does not reference one via boundField (and carries no readGovernance exemption) \u2014 it would silently ignore e.g. "${anySwitch[0]}: none"`;
|
|
3811
|
+
}
|
|
3812
|
+
const positiveValues = valuesOf(e.boundField).filter((v) => v !== "none");
|
|
3813
|
+
if (!e.requiredValue || !positiveValues.includes(e.requiredValue)) {
|
|
3814
|
+
return `override "${toolName}".requiredValue for boundField "${e.boundField}" must be ${positiveValues.map((v) => `"${v}"`).join(" or ")}, not ${JSON.stringify(e.requiredValue)} \u2014 requiring "none" (or an unlisted value) would invert the gate`;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
function getByDottedPath(obj, path) {
|
|
3820
|
+
return path.split(".").reduce(
|
|
3821
|
+
(o, k) => o == null ? void 0 : o[k],
|
|
3822
|
+
obj
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
function firstParsableDate(obj, paths) {
|
|
3826
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
3827
|
+
const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
|
|
3828
|
+
if (parsed !== null) return parsed;
|
|
3829
|
+
}
|
|
3830
|
+
return null;
|
|
3831
|
+
}
|
|
3832
|
+
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
3833
|
+
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
3834
|
+
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
3835
|
+
}
|
|
3836
|
+
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
3837
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
3838
|
+
if (format === "epoch_ms") return floorMs;
|
|
3839
|
+
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
3840
|
+
return new Date(floorMs).toISOString();
|
|
3841
|
+
}
|
|
3842
|
+
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
3843
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
3844
|
+
const asked = parseMessageTimestamp(requested);
|
|
3845
|
+
if (asked !== null && asked >= floorMs) return requested;
|
|
3846
|
+
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
3464
3847
|
}
|
|
3465
|
-
|
|
3466
|
-
// src/lib/preview.ts
|
|
3467
|
-
import { computeContentHash as computeContentHash2 } from "@hap/core";
|
|
3468
|
-
|
|
3469
|
-
// src/lib/manifest-loader.ts
|
|
3470
|
-
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
3471
|
-
import { join as join14, resolve } from "path";
|
|
3472
|
-
|
|
3473
|
-
// src/lib/tool-gating-types.ts
|
|
3474
|
-
var MAX_PREVIEW_READS = 10;
|
|
3475
|
-
var MAX_FOREACH_ELEMENTS = 50;
|
|
3476
|
-
var MAX_RECORD_KEY_STEPS = 2;
|
|
3477
3848
|
|
|
3478
3849
|
// src/lib/manifest-loader.ts
|
|
3479
3850
|
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -3823,28 +4194,34 @@ function invalidManifestContextReason(manifest) {
|
|
|
3823
4194
|
}
|
|
3824
4195
|
return null;
|
|
3825
4196
|
}
|
|
4197
|
+
function invalidManifestReadGateReason(manifest) {
|
|
4198
|
+
const profileDef = getProfile2(manifest.profile);
|
|
4199
|
+
if (!profileDef) return null;
|
|
4200
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4201
|
+
return uncoveredReadSwitchReason(overrides, profileDef.boundsSchema);
|
|
4202
|
+
}
|
|
3826
4203
|
var manifests = /* @__PURE__ */ new Map();
|
|
3827
4204
|
function loadManifests(integrationsDir) {
|
|
3828
4205
|
const dir = resolve(
|
|
3829
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
4206
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join15(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3830
4207
|
);
|
|
3831
|
-
const indexPath =
|
|
3832
|
-
if (!
|
|
4208
|
+
const indexPath = join15(dir, "index.json");
|
|
4209
|
+
if (!existsSync15(indexPath)) {
|
|
3833
4210
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3834
4211
|
return 0;
|
|
3835
4212
|
}
|
|
3836
4213
|
let index;
|
|
3837
4214
|
try {
|
|
3838
|
-
index = JSON.parse(
|
|
4215
|
+
index = JSON.parse(readFileSync16(indexPath, "utf-8"));
|
|
3839
4216
|
} catch (err) {
|
|
3840
4217
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3841
4218
|
return 0;
|
|
3842
4219
|
}
|
|
3843
4220
|
let loaded = 0;
|
|
3844
4221
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3845
|
-
const manifestPath =
|
|
4222
|
+
const manifestPath = join15(dir, relativePath);
|
|
3846
4223
|
try {
|
|
3847
|
-
const manifest = JSON.parse(
|
|
4224
|
+
const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
|
|
3848
4225
|
const pinReason = invalidNpmPinReason(manifest);
|
|
3849
4226
|
if (pinReason) {
|
|
3850
4227
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
@@ -3860,6 +4237,11 @@ function loadManifests(integrationsDir) {
|
|
|
3860
4237
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
|
|
3861
4238
|
continue;
|
|
3862
4239
|
}
|
|
4240
|
+
const readGateReason = invalidManifestReadGateReason(manifest);
|
|
4241
|
+
if (readGateReason) {
|
|
4242
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${readGateReason}`);
|
|
4243
|
+
continue;
|
|
4244
|
+
}
|
|
3863
4245
|
manifests.set(id, manifest);
|
|
3864
4246
|
loaded++;
|
|
3865
4247
|
} catch (err) {
|
|
@@ -4144,189 +4526,6 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
4144
4526
|
});
|
|
4145
4527
|
}
|
|
4146
4528
|
|
|
4147
|
-
// src/lib/scope-specificity.ts
|
|
4148
|
-
function tokenSet(value) {
|
|
4149
|
-
if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
|
|
4150
|
-
return new Set(
|
|
4151
|
-
String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
4152
|
-
);
|
|
4153
|
-
}
|
|
4154
|
-
function subsetOrEqual(a, b) {
|
|
4155
|
-
if (b.size === 0) return true;
|
|
4156
|
-
if (a.size === 0) return false;
|
|
4157
|
-
for (const x of a) if (!b.has(x)) return false;
|
|
4158
|
-
return true;
|
|
4159
|
-
}
|
|
4160
|
-
function atLeastAsSpecific(keys, a, b) {
|
|
4161
|
-
for (const k of keys) {
|
|
4162
|
-
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
4163
|
-
}
|
|
4164
|
-
return true;
|
|
4165
|
-
}
|
|
4166
|
-
function strictlyMoreSpecific(keys, a, b) {
|
|
4167
|
-
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
4168
|
-
}
|
|
4169
|
-
function selectAuthorization(contextKeys, passers) {
|
|
4170
|
-
if (passers.length === 1) {
|
|
4171
|
-
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
4172
|
-
}
|
|
4173
|
-
const dominators = passers.filter(
|
|
4174
|
-
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
4175
|
-
);
|
|
4176
|
-
if (dominators.length === 1) {
|
|
4177
|
-
const chosen2 = dominators[0];
|
|
4178
|
-
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
4179
|
-
}
|
|
4180
|
-
const approvers = passers.filter((p) => p.requiresApproval);
|
|
4181
|
-
const pool = approvers.length > 0 ? approvers : passers;
|
|
4182
|
-
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
4183
|
-
return {
|
|
4184
|
-
chosen,
|
|
4185
|
-
superseded: passers.filter((p) => p !== chosen),
|
|
4186
|
-
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
4187
|
-
};
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
// src/lib/read-gate.ts
|
|
4191
|
-
function boundsSatisfyReadGate(bounds, gate) {
|
|
4192
|
-
if (!gate.boundField) return true;
|
|
4193
|
-
const actual = bounds?.[gate.boundField];
|
|
4194
|
-
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
4195
|
-
}
|
|
4196
|
-
function readToolIsGoverned(g) {
|
|
4197
|
-
if (!g) return false;
|
|
4198
|
-
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
4199
|
-
}
|
|
4200
|
-
function allowedResources(contexts, boundField) {
|
|
4201
|
-
const out = /* @__PURE__ */ new Set();
|
|
4202
|
-
for (const ctx of contexts) {
|
|
4203
|
-
for (const v of tokenSet(ctx?.[boundField])) out.add(v);
|
|
4204
|
-
}
|
|
4205
|
-
return out;
|
|
4206
|
-
}
|
|
4207
|
-
function deniedResources(requested, allowed) {
|
|
4208
|
-
return requested.filter((r) => !allowed.has(r));
|
|
4209
|
-
}
|
|
4210
|
-
function filterItemsByResource(items, idPath, allowed) {
|
|
4211
|
-
return items.filter((it) => {
|
|
4212
|
-
const id = getByDottedPath(it, idPath);
|
|
4213
|
-
return typeof id === "string" && allowed.has(id);
|
|
4214
|
-
});
|
|
4215
|
-
}
|
|
4216
|
-
function hasBlockedValue(item, valuesPath, blocked) {
|
|
4217
|
-
const v = getByDottedPath(item, valuesPath);
|
|
4218
|
-
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
4219
|
-
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
4220
|
-
}
|
|
4221
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4222
|
-
function parseMessageTimestamp(value) {
|
|
4223
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4224
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
4225
|
-
const n = Number(value);
|
|
4226
|
-
if (Number.isFinite(n)) return n;
|
|
4227
|
-
const iso = Date.parse(value);
|
|
4228
|
-
if (Number.isFinite(iso)) return iso;
|
|
4229
|
-
}
|
|
4230
|
-
return null;
|
|
4231
|
-
}
|
|
4232
|
-
function ageInDays(timestampMs, nowMs) {
|
|
4233
|
-
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
4234
|
-
}
|
|
4235
|
-
function maxReadAgeDays(boundsList, field) {
|
|
4236
|
-
let max = null;
|
|
4237
|
-
for (const b of boundsList) {
|
|
4238
|
-
const raw = b?.[field];
|
|
4239
|
-
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
4240
|
-
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
4241
|
-
}
|
|
4242
|
-
return max;
|
|
4243
|
-
}
|
|
4244
|
-
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
4245
|
-
if (timestampMs === null) return true;
|
|
4246
|
-
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
4247
|
-
}
|
|
4248
|
-
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
4249
|
-
function parensBalanced(s) {
|
|
4250
|
-
let depth = 0;
|
|
4251
|
-
let inQuote = false;
|
|
4252
|
-
for (const c of s) {
|
|
4253
|
-
if (c === '"') {
|
|
4254
|
-
inQuote = !inQuote;
|
|
4255
|
-
continue;
|
|
4256
|
-
}
|
|
4257
|
-
if (inQuote) continue;
|
|
4258
|
-
if (c === "(") depth++;
|
|
4259
|
-
else if (c === ")" && --depth < 0) return false;
|
|
4260
|
-
}
|
|
4261
|
-
return !inQuote && depth === 0;
|
|
4262
|
-
}
|
|
4263
|
-
function composeReadQuery(agentQuery, clauses) {
|
|
4264
|
-
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
4265
|
-
const base = (agentQuery ?? "").trim();
|
|
4266
|
-
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
4267
|
-
if (!parensBalanced(base)) {
|
|
4268
|
-
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
4269
|
-
}
|
|
4270
|
-
if (TRAILING_OPERATOR.test(base)) {
|
|
4271
|
-
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
4272
|
-
}
|
|
4273
|
-
if (injected.length === 0) return { ok: true, query: base };
|
|
4274
|
-
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
4275
|
-
}
|
|
4276
|
-
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
4277
|
-
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
4278
|
-
let re;
|
|
4279
|
-
try {
|
|
4280
|
-
re = new RegExp(conflictPattern, "gi");
|
|
4281
|
-
} catch {
|
|
4282
|
-
return null;
|
|
4283
|
-
}
|
|
4284
|
-
let worst = null;
|
|
4285
|
-
for (const match of agentQuery.matchAll(re)) {
|
|
4286
|
-
const days = Number(match[1]);
|
|
4287
|
-
if (!Number.isFinite(days)) continue;
|
|
4288
|
-
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
4289
|
-
}
|
|
4290
|
-
return worst;
|
|
4291
|
-
}
|
|
4292
|
-
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
4293
|
-
const fields = boundsSchema?.fields;
|
|
4294
|
-
if (!fields) return null;
|
|
4295
|
-
for (const [name, def] of Object.entries(fields)) {
|
|
4296
|
-
if (def?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
|
|
4297
|
-
}
|
|
4298
|
-
return null;
|
|
4299
|
-
}
|
|
4300
|
-
function getByDottedPath(obj, path) {
|
|
4301
|
-
return path.split(".").reduce(
|
|
4302
|
-
(o, k) => o == null ? void 0 : o[k],
|
|
4303
|
-
obj
|
|
4304
|
-
);
|
|
4305
|
-
}
|
|
4306
|
-
function firstParsableDate(obj, paths) {
|
|
4307
|
-
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
4308
|
-
const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
|
|
4309
|
-
if (parsed !== null) return parsed;
|
|
4310
|
-
}
|
|
4311
|
-
return null;
|
|
4312
|
-
}
|
|
4313
|
-
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
4314
|
-
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
4315
|
-
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
4316
|
-
}
|
|
4317
|
-
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
4318
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4319
|
-
if (format === "epoch_ms") return floorMs;
|
|
4320
|
-
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
4321
|
-
return new Date(floorMs).toISOString();
|
|
4322
|
-
}
|
|
4323
|
-
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
4324
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4325
|
-
const asked = parseMessageTimestamp(requested);
|
|
4326
|
-
if (asked !== null && asked >= floorMs) return requested;
|
|
4327
|
-
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
4328
|
-
}
|
|
4329
|
-
|
|
4330
4529
|
// src/lib/field-path.ts
|
|
4331
4530
|
function matchesWhere(element, where) {
|
|
4332
4531
|
if (element === null || typeof element !== "object" || Array.isArray(element)) return false;
|
|
@@ -4811,8 +5010,8 @@ async function verifyTicket(cache, receipt, expected) {
|
|
|
4811
5010
|
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
|
|
4812
5011
|
throw new TicketBindingMismatchError("Ticket carries no valid timestamp \u2014 refusing to execute.");
|
|
4813
5012
|
}
|
|
4814
|
-
const
|
|
4815
|
-
const ageSec =
|
|
5013
|
+
const nowSec4 = Math.floor(Date.now() / 1e3);
|
|
5014
|
+
const ageSec = nowSec4 - timestamp;
|
|
4816
5015
|
if (ageSec > TICKET_MAX_AGE_SECONDS) {
|
|
4817
5016
|
throw new TicketBindingMismatchError(
|
|
4818
5017
|
`Ticket is ${ageSec}s old, older than the ${TICKET_MAX_AGE_SECONDS}s freshness window \u2014 refusing to execute.`
|
|
@@ -4947,7 +5146,7 @@ function normalizeIncomingArgs(tool, args) {
|
|
|
4947
5146
|
}
|
|
4948
5147
|
|
|
4949
5148
|
// src/lib/numeric-execution.ts
|
|
4950
|
-
import { getProfile as
|
|
5149
|
+
import { getProfile as getProfile3 } from "@hap/core";
|
|
4951
5150
|
function parseNumericString(value) {
|
|
4952
5151
|
if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
|
|
4953
5152
|
const parsed = Number(value);
|
|
@@ -4963,7 +5162,7 @@ function declaredNumericFields(profile) {
|
|
|
4963
5162
|
return out;
|
|
4964
5163
|
}
|
|
4965
5164
|
function coerceDeclaredNumericFields(profileId, execution) {
|
|
4966
|
-
const fields = declaredNumericFields(
|
|
5165
|
+
const fields = declaredNumericFields(getProfile3(profileId));
|
|
4967
5166
|
for (const field of fields) {
|
|
4968
5167
|
const value = execution[field];
|
|
4969
5168
|
if (typeof value !== "string") continue;
|
|
@@ -4973,7 +5172,7 @@ function coerceDeclaredNumericFields(profileId, execution) {
|
|
|
4973
5172
|
}
|
|
4974
5173
|
|
|
4975
5174
|
// src/lib/tool-proxy.ts
|
|
4976
|
-
import { getProfile as
|
|
5175
|
+
import { getProfile as getProfile4, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
|
|
4977
5176
|
import { readFile } from "fs/promises";
|
|
4978
5177
|
import { extname } from "path";
|
|
4979
5178
|
import { randomUUID } from "crypto";
|
|
@@ -5045,7 +5244,7 @@ function profileMatches(profileId, shortName2) {
|
|
|
5045
5244
|
return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
|
|
5046
5245
|
}
|
|
5047
5246
|
function wireExecutionContext(profileId, execution) {
|
|
5048
|
-
const profile =
|
|
5247
|
+
const profile = getProfile4(profileId);
|
|
5049
5248
|
const allowed = /* @__PURE__ */ new Set(["action_type"]);
|
|
5050
5249
|
const fields = profile?.boundsSchema?.fields;
|
|
5051
5250
|
if (fields) {
|
|
@@ -5063,7 +5262,7 @@ function wireExecutionContext(profileId, execution) {
|
|
|
5063
5262
|
return out;
|
|
5064
5263
|
}
|
|
5065
5264
|
function countBoundsFor(profileId, actionType) {
|
|
5066
|
-
const fields =
|
|
5265
|
+
const fields = getProfile4(profileId)?.boundsSchema?.fields;
|
|
5067
5266
|
if (!fields) return null;
|
|
5068
5267
|
return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
|
|
5069
5268
|
const applies = boundActionTypes2(name, def);
|
|
@@ -5138,7 +5337,7 @@ function recordBlocked(state2, tool, rec) {
|
|
|
5138
5337
|
function recordGatekeeperRefusal(state2, tool, refusals) {
|
|
5139
5338
|
if (refusals.length === 0) return;
|
|
5140
5339
|
const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
|
|
5141
|
-
const scoped = pick.field ? Boolean(
|
|
5340
|
+
const scoped = pick.field ? Boolean(getProfile4(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
|
|
5142
5341
|
const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
|
|
5143
5342
|
recordBlocked(state2, tool, {
|
|
5144
5343
|
kind: scoped ? "scope" : "bound",
|
|
@@ -5238,7 +5437,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
5238
5437
|
};
|
|
5239
5438
|
};
|
|
5240
5439
|
}
|
|
5241
|
-
const { profile, executionMapping, staticExecution, category } = tool.gating;
|
|
5440
|
+
const { profile, executionMapping, staticExecution, category, boundField: toolBoundField, requiredValue: toolRequiredValue } = tool.gating;
|
|
5242
5441
|
const eligibleMandates = () => {
|
|
5243
5442
|
const refusals = [];
|
|
5244
5443
|
const matching = state2.getEnrichedAuthorizations().filter((a) => a.complete && profileMatches(a.profileId, profile)).filter((a) => {
|
|
@@ -5317,7 +5516,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
5317
5516
|
);
|
|
5318
5517
|
}
|
|
5319
5518
|
}
|
|
5320
|
-
const readProfile = readAdapter ?
|
|
5519
|
+
const readProfile = readAdapter ? getProfile4(matchingAuths[0].profileId) : void 0;
|
|
5321
5520
|
const boundsSchema = readProfile?.boundsSchema;
|
|
5322
5521
|
const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
|
|
5323
5522
|
const hasAgeDimension = Boolean(readAdapter?.ageField);
|
|
@@ -5481,7 +5680,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
5481
5680
|
};
|
|
5482
5681
|
}
|
|
5483
5682
|
{
|
|
5484
|
-
const profileDef =
|
|
5683
|
+
const profileDef = getProfile4(profile);
|
|
5485
5684
|
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
5486
5685
|
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
5487
5686
|
recordBlocked(state2, tool, {
|
|
@@ -5519,6 +5718,25 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
5519
5718
|
const passers = [];
|
|
5520
5719
|
const structuredRefusals = [];
|
|
5521
5720
|
for (const candidate of matchingAuths) {
|
|
5721
|
+
if (toolBoundField) {
|
|
5722
|
+
const gateOk = boundsSatisfyReadGate(
|
|
5723
|
+
candidate.bounds ?? candidate.frame,
|
|
5724
|
+
{ boundField: toolBoundField, requiredValue: toolRequiredValue }
|
|
5725
|
+
);
|
|
5726
|
+
if (!gateOk) {
|
|
5727
|
+
errors.push(
|
|
5728
|
+
`${candidate.path}: requires "${toolBoundField}: ${toolRequiredValue}", but this authorization does not grant it`
|
|
5729
|
+
);
|
|
5730
|
+
structuredRefusals.push({
|
|
5731
|
+
authorizationId: candidate.authorizationId,
|
|
5732
|
+
profileId: candidate.profileId,
|
|
5733
|
+
code: "BOUND_EXCEEDED",
|
|
5734
|
+
field: toolBoundField,
|
|
5735
|
+
bound: toolRequiredValue
|
|
5736
|
+
});
|
|
5737
|
+
continue;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5522
5740
|
const { result } = await state2.gatekeeper.verifyExecution(candidate.authorizationId, execution, {
|
|
5523
5741
|
bounds: candidate.bounds,
|
|
5524
5742
|
context: candidate.context
|
|
@@ -5566,7 +5784,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
5566
5784
|
isError: true
|
|
5567
5785
|
};
|
|
5568
5786
|
}
|
|
5569
|
-
const contextKeys =
|
|
5787
|
+
const contextKeys = getProfile4(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
|
|
5570
5788
|
return attemptWithCandidates(passers);
|
|
5571
5789
|
async function attemptWithCandidates(candidates) {
|
|
5572
5790
|
const selection = selectAuthorization(
|
|
@@ -6026,7 +6244,7 @@ function buildMandateBrief(opts) {
|
|
|
6026
6244
|
const parts = [];
|
|
6027
6245
|
if (boundsDesc) parts.push(boundsDesc);
|
|
6028
6246
|
if (executionLog) {
|
|
6029
|
-
const profile =
|
|
6247
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
6030
6248
|
const consumption = getConsumptionState(auth, executionLog, profile);
|
|
6031
6249
|
const compact = formatConsumptionCompact(consumption);
|
|
6032
6250
|
if (compact) parts.push(compact);
|
|
@@ -6064,7 +6282,7 @@ function buildMandateBrief(opts) {
|
|
|
6064
6282
|
}
|
|
6065
6283
|
|
|
6066
6284
|
// src/tools/authorizations.ts
|
|
6067
|
-
import { getProfile as
|
|
6285
|
+
import { getProfile as getProfile6 } from "@hap/core";
|
|
6068
6286
|
function shortProfileName2(profileId) {
|
|
6069
6287
|
const withoutVersion = profileId.replace(/@.*$/, "");
|
|
6070
6288
|
const parts = withoutVersion.split("/");
|
|
@@ -6182,7 +6400,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
6182
6400
|
} catch {
|
|
6183
6401
|
}
|
|
6184
6402
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
6185
|
-
const profile =
|
|
6403
|
+
const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
|
|
6186
6404
|
if (profile) {
|
|
6187
6405
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
6188
6406
|
const consumptionText = formatConsumptionFull(consumption);
|
|
@@ -6242,7 +6460,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
6242
6460
|
lines.push(" Mode: review (each action requires your approval)");
|
|
6243
6461
|
}
|
|
6244
6462
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
6245
|
-
const profile =
|
|
6463
|
+
const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
|
|
6246
6464
|
if (profile) {
|
|
6247
6465
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
6248
6466
|
const compact = formatConsumptionCompact(consumption);
|
|
@@ -6435,6 +6653,18 @@ async function decryptRejectionNote(by) {
|
|
|
6435
6653
|
}
|
|
6436
6654
|
|
|
6437
6655
|
// src/tools/commitments.ts
|
|
6656
|
+
function recordExecutionOutcome(state2, proposalId, ticketId, ok, text, structuredContent) {
|
|
6657
|
+
try {
|
|
6658
|
+
state2.executionResults?.record({ proposalId, ticketId, ok, text, structuredContent });
|
|
6659
|
+
} catch (err) {
|
|
6660
|
+
console.error(`[Suveren MCP] Could not record the execution result for proposal ${proposalId}:`, err);
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
function logExecutionOutcome(proposalId, ok, shortMessage) {
|
|
6664
|
+
console.error(
|
|
6665
|
+
`[Suveren MCP] Proposal ${proposalId}: execution outcome=${ok ? "ok" : `error (${shortMessage ?? "unknown"})`}`
|
|
6666
|
+
);
|
|
6667
|
+
}
|
|
6438
6668
|
function buildSkippedProposalNote(proposalId) {
|
|
6439
6669
|
return `Proposal ${proposalId} was approved, but was submitted from another device/installation of this operator (or one whose local record is gone) \u2014 this gateway will not execute it. Re-run the same request here to execute it from this gateway.`;
|
|
6440
6670
|
}
|
|
@@ -6701,12 +6931,19 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
6701
6931
|
try {
|
|
6702
6932
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
6703
6933
|
} catch (err) {
|
|
6934
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6704
6935
|
state2.executionJournal.complete(receiptId, "failed");
|
|
6936
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, `The tool call threw: ${msg}`);
|
|
6937
|
+
logExecutionOutcome(proposal.id, false, err instanceof Error ? err.name : "thrown error");
|
|
6938
|
+
void notifyControlPlane("execution-failed");
|
|
6705
6939
|
throw err;
|
|
6706
6940
|
}
|
|
6707
6941
|
const resultText = result.content?.[0]?.text ?? JSON.stringify(result);
|
|
6708
6942
|
if (result.isError) {
|
|
6709
6943
|
state2.executionJournal.complete(receiptId, "failed", "refused", resultText);
|
|
6944
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, resultText);
|
|
6945
|
+
logExecutionOutcome(proposal.id, false, "the connector returned an error");
|
|
6946
|
+
void notifyControlPlane("execution-failed");
|
|
6710
6947
|
return {
|
|
6711
6948
|
text: `Proposal ${proposal.id} was approved, but ${integrationId} refused to run it \u2014 nothing was done.
|
|
6712
6949
|
Reason: ${resultText}`,
|
|
@@ -6714,6 +6951,8 @@ Reason: ${resultText}`,
|
|
|
6714
6951
|
};
|
|
6715
6952
|
}
|
|
6716
6953
|
state2.executionJournal.complete(receiptId, "done");
|
|
6954
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, true, resultText, result.structuredContent);
|
|
6955
|
+
logExecutionOutcome(proposal.id, true);
|
|
6717
6956
|
state2.executionLog.record({
|
|
6718
6957
|
profileId: proposal.profileId,
|
|
6719
6958
|
path: proposal.path,
|
|
@@ -6724,6 +6963,12 @@ Reason: ${resultText}`,
|
|
|
6724
6963
|
Result: ${resultText}` };
|
|
6725
6964
|
} catch (err) {
|
|
6726
6965
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6966
|
+
if (state2.executionJournal.get(receiptId)?.state === "intent") {
|
|
6967
|
+
state2.executionJournal.complete(receiptId, "failed");
|
|
6968
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, `Receipt issued but tool execution failed: ${msg}`);
|
|
6969
|
+
logExecutionOutcome(proposal.id, false, err instanceof Error ? err.name : "error");
|
|
6970
|
+
void notifyControlPlane("execution-failed");
|
|
6971
|
+
}
|
|
6727
6972
|
return {
|
|
6728
6973
|
text: `Proposal ${proposal.id} receipt issued but tool execution failed: ${msg}`,
|
|
6729
6974
|
isError: true
|
|
@@ -6732,11 +6977,22 @@ Result: ${resultText}` };
|
|
|
6732
6977
|
}
|
|
6733
6978
|
function localOutcomeOfExecuted(proposal, state2) {
|
|
6734
6979
|
const entry = state2.executionJournal?.findByProposal?.(proposal.id);
|
|
6735
|
-
if (!entry
|
|
6980
|
+
if (!entry) return null;
|
|
6736
6981
|
const integration = proposal.tool.split("__")[0];
|
|
6982
|
+
if (entry.state === "done") {
|
|
6983
|
+
const stored = state2.executionResults?.get(proposal.id);
|
|
6984
|
+
if (!stored || !stored.ok) return null;
|
|
6985
|
+
return {
|
|
6986
|
+
text: `Proposal ${proposal.id} was approved and executed.
|
|
6987
|
+
Result: ${stored.text}`,
|
|
6988
|
+
isError: false
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6737
6991
|
if (entry.state === "failed") {
|
|
6738
|
-
const
|
|
6739
|
-
|
|
6992
|
+
const stored = state2.executionResults?.get(proposal.id);
|
|
6993
|
+
const detail = (stored && !stored.ok ? stored.text : void 0) ?? entry.detail;
|
|
6994
|
+
const reasonSuffix = detail ? `
|
|
6995
|
+
Reason: ${detail}` : "";
|
|
6740
6996
|
return {
|
|
6741
6997
|
text: entry.outcome === "as_refused" ? `Proposal ${proposal.id} was approved, but the Authority Server refused its ticket \u2014 nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).${reasonSuffix}` : entry.outcome === "refused" ? `Proposal ${proposal.id} was approved, but ${integration} refused to run it \u2014 nothing was done. Do not call the tool again with the same values; a new attempt needs a new request and approval.${reasonSuffix}` : entry.outcome === "changed" ? `Proposal ${proposal.id} was approved, but the record changed in ${integration} since it was requested \u2014 nothing was done. A new attempt needs a new request and approval.${reasonSuffix}` : `Proposal ${proposal.id} was approved, but running it failed \u2014 whether anything happened in ${integration} is unknown. Check ${integration} before trying again; a new attempt needs a new request and approval.${reasonSuffix}`,
|
|
6742
6998
|
isError: true
|
|
@@ -7083,21 +7339,21 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
7083
7339
|
}
|
|
7084
7340
|
|
|
7085
7341
|
// src/lib/agent-contact.ts
|
|
7086
|
-
import { existsSync as
|
|
7087
|
-
import { dirname as
|
|
7342
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync17, renameSync as renameSync5, writeFileSync as writeFileSync13 } from "fs";
|
|
7343
|
+
import { dirname as dirname13, join as join16 } from "path";
|
|
7088
7344
|
var FILE = "agent-contact.json";
|
|
7089
7345
|
var REWRITE_AFTER_MS = 6e4;
|
|
7090
7346
|
var AgentContactStore = class {
|
|
7091
7347
|
path;
|
|
7092
7348
|
current;
|
|
7093
7349
|
constructor(dataDir2) {
|
|
7094
|
-
this.path =
|
|
7350
|
+
this.path = join16(dataDir2, FILE);
|
|
7095
7351
|
this.current = this.load();
|
|
7096
7352
|
}
|
|
7097
7353
|
load() {
|
|
7098
|
-
if (!
|
|
7354
|
+
if (!existsSync16(this.path)) return null;
|
|
7099
7355
|
try {
|
|
7100
|
-
const v = JSON.parse(
|
|
7356
|
+
const v = JSON.parse(readFileSync17(this.path, "utf8"));
|
|
7101
7357
|
if (typeof v.clientName !== "string" || typeof v.firstSeenAt !== "string" || typeof v.lastSeenAt !== "string") return null;
|
|
7102
7358
|
return { clientName: v.clientName, clientVersion: v.clientVersion, firstSeenAt: v.firstSeenAt, lastSeenAt: v.lastSeenAt };
|
|
7103
7359
|
} catch {
|
|
@@ -7120,9 +7376,9 @@ var AgentContactStore = class {
|
|
|
7120
7376
|
};
|
|
7121
7377
|
if (!due) return;
|
|
7122
7378
|
try {
|
|
7123
|
-
|
|
7379
|
+
mkdirSync13(dirname13(this.path), { recursive: true });
|
|
7124
7380
|
const tmp = `${this.path}.tmp`;
|
|
7125
|
-
|
|
7381
|
+
writeFileSync13(tmp, JSON.stringify(this.current, null, 2));
|
|
7126
7382
|
renameSync5(tmp, this.path);
|
|
7127
7383
|
} catch (err) {
|
|
7128
7384
|
console.error("[Suveren MCP] Could not save agent contact:", err);
|
|
@@ -7157,16 +7413,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
7157
7413
|
}
|
|
7158
7414
|
|
|
7159
7415
|
// src/lib/integration-registry.ts
|
|
7160
|
-
import { mkdirSync as
|
|
7161
|
-
import { homedir as
|
|
7162
|
-
import { dirname as
|
|
7163
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
7416
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync17 } from "fs";
|
|
7417
|
+
import { homedir as homedir12 } from "os";
|
|
7418
|
+
import { dirname as dirname14, join as join17 } from "path";
|
|
7419
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join17(homedir12(), ".suveren");
|
|
7164
7420
|
var IntegrationRegistry = class {
|
|
7165
7421
|
integrations = /* @__PURE__ */ new Map();
|
|
7166
7422
|
filePath;
|
|
7167
7423
|
constructor(dataDir2) {
|
|
7168
7424
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
7169
|
-
this.filePath =
|
|
7425
|
+
this.filePath = join17(dir, "integrations.json");
|
|
7170
7426
|
this.load();
|
|
7171
7427
|
}
|
|
7172
7428
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -7197,12 +7453,12 @@ var IntegrationRegistry = class {
|
|
|
7197
7453
|
}
|
|
7198
7454
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
7199
7455
|
load() {
|
|
7200
|
-
if (!
|
|
7201
|
-
|
|
7456
|
+
if (!existsSync17(this.filePath)) {
|
|
7457
|
+
mkdirSync14(dirname14(this.filePath), { recursive: true });
|
|
7202
7458
|
return;
|
|
7203
7459
|
}
|
|
7204
7460
|
try {
|
|
7205
|
-
const raw =
|
|
7461
|
+
const raw = readFileSync18(this.filePath, "utf-8");
|
|
7206
7462
|
const data = JSON.parse(raw);
|
|
7207
7463
|
const migrated = data.integrations.map((i) => {
|
|
7208
7464
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -7231,15 +7487,15 @@ var IntegrationRegistry = class {
|
|
|
7231
7487
|
version: 1,
|
|
7232
7488
|
integrations: Array.from(this.integrations.values())
|
|
7233
7489
|
};
|
|
7234
|
-
|
|
7235
|
-
|
|
7490
|
+
mkdirSync14(dirname14(this.filePath), { recursive: true });
|
|
7491
|
+
writeFileSync14(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
7236
7492
|
}
|
|
7237
7493
|
};
|
|
7238
7494
|
|
|
7239
7495
|
// src/lib/integration-manager.ts
|
|
7240
|
-
import { homedir as
|
|
7241
|
-
import { join as
|
|
7242
|
-
import { existsSync as
|
|
7496
|
+
import { homedir as homedir13 } from "os";
|
|
7497
|
+
import { join as join18, delimiter } from "path";
|
|
7498
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync19, rmSync } from "fs";
|
|
7243
7499
|
import { execFile } from "child_process";
|
|
7244
7500
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7245
7501
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -7369,8 +7625,8 @@ function inheritedConnectorEnv(parent, passEnv = []) {
|
|
|
7369
7625
|
}
|
|
7370
7626
|
|
|
7371
7627
|
// src/lib/integration-manager.ts
|
|
7372
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
7373
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
7628
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join18(homedir13(), ".suveren");
|
|
7629
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join18(DEFAULT_DATA_DIR2, "integrations");
|
|
7374
7630
|
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
7375
7631
|
var installLock = Promise.resolve();
|
|
7376
7632
|
function withInstallLock(task) {
|
|
@@ -7378,7 +7634,7 @@ function withInstallLock(task) {
|
|
|
7378
7634
|
installLock = run.then(() => void 0, () => void 0);
|
|
7379
7635
|
return run;
|
|
7380
7636
|
}
|
|
7381
|
-
var INTEGRATIONS_BIN =
|
|
7637
|
+
var INTEGRATIONS_BIN = join18(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
7382
7638
|
function getIntegrationsBinDir() {
|
|
7383
7639
|
return INTEGRATIONS_BIN;
|
|
7384
7640
|
}
|
|
@@ -7387,12 +7643,12 @@ function buildPath() {
|
|
|
7387
7643
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
7388
7644
|
}
|
|
7389
7645
|
function ensureIntegrationsDir() {
|
|
7390
|
-
if (!
|
|
7391
|
-
|
|
7646
|
+
if (!existsSync18(INTEGRATIONS_DIR)) {
|
|
7647
|
+
mkdirSync15(INTEGRATIONS_DIR, { recursive: true });
|
|
7392
7648
|
}
|
|
7393
|
-
const pkgPath =
|
|
7394
|
-
if (!
|
|
7395
|
-
|
|
7649
|
+
const pkgPath = join18(INTEGRATIONS_DIR, "package.json");
|
|
7650
|
+
if (!existsSync18(pkgPath)) {
|
|
7651
|
+
writeFileSync15(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
7396
7652
|
}
|
|
7397
7653
|
}
|
|
7398
7654
|
function readAgeOf(config) {
|
|
@@ -7481,19 +7737,19 @@ var IntegrationManager = class {
|
|
|
7481
7737
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
7482
7738
|
*/
|
|
7483
7739
|
isUsableInstall(npmPackage) {
|
|
7484
|
-
const pkgDir =
|
|
7485
|
-
const pkgJson =
|
|
7486
|
-
if (!
|
|
7740
|
+
const pkgDir = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
7741
|
+
const pkgJson = join18(pkgDir, "package.json");
|
|
7742
|
+
if (!existsSync18(pkgDir) || !existsSync18(pkgJson)) return false;
|
|
7487
7743
|
try {
|
|
7488
|
-
const pkg = JSON.parse(
|
|
7744
|
+
const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
|
|
7489
7745
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
7490
7746
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
7491
7747
|
const binEntries = Object.entries(binMap);
|
|
7492
7748
|
for (const [binName, target] of binEntries) {
|
|
7493
|
-
if (!
|
|
7749
|
+
if (!existsSync18(join18(pkgDir, target))) return false;
|
|
7494
7750
|
if (!this.binShimExists(binName)) return false;
|
|
7495
7751
|
}
|
|
7496
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
7752
|
+
if (binEntries.length === 0 && pkg.main && !existsSync18(join18(pkgDir, pkg.main))) return false;
|
|
7497
7753
|
return true;
|
|
7498
7754
|
} catch {
|
|
7499
7755
|
return false;
|
|
@@ -7508,9 +7764,9 @@ var IntegrationManager = class {
|
|
|
7508
7764
|
*/
|
|
7509
7765
|
readInstalledVersion(npmPackage) {
|
|
7510
7766
|
if (!this.isUsableInstall(npmPackage)) return null;
|
|
7511
|
-
const pkgJson =
|
|
7767
|
+
const pkgJson = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
7512
7768
|
try {
|
|
7513
|
-
const pkg = JSON.parse(
|
|
7769
|
+
const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
|
|
7514
7770
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
7515
7771
|
} catch {
|
|
7516
7772
|
return null;
|
|
@@ -7524,7 +7780,7 @@ var IntegrationManager = class {
|
|
|
7524
7780
|
binShimExists(binName) {
|
|
7525
7781
|
if (!binName) return false;
|
|
7526
7782
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
7527
|
-
return candidates.some((c) =>
|
|
7783
|
+
return candidates.some((c) => existsSync18(join18(INTEGRATIONS_BIN, c)));
|
|
7528
7784
|
}
|
|
7529
7785
|
/**
|
|
7530
7786
|
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
@@ -7614,8 +7870,8 @@ var IntegrationManager = class {
|
|
|
7614
7870
|
* nothing at all.
|
|
7615
7871
|
*/
|
|
7616
7872
|
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
7617
|
-
const pkgDir =
|
|
7618
|
-
if (cleanFirst &&
|
|
7873
|
+
const pkgDir = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
7874
|
+
if (cleanFirst && existsSync18(pkgDir)) {
|
|
7619
7875
|
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
7620
7876
|
try {
|
|
7621
7877
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -8204,7 +8460,18 @@ var IntegrationManager = class {
|
|
|
8204
8460
|
simulationOnly: ext.simulationOnly,
|
|
8205
8461
|
approvalView: ext.approvalView,
|
|
8206
8462
|
contextKeys: ext.contextKeys,
|
|
8207
|
-
sourceFromArg: ext.sourceFromArg
|
|
8463
|
+
sourceFromArg: ext.sourceFromArg,
|
|
8464
|
+
// A WRITE override may ALSO declare a capability-gate (enum boundType,
|
|
8465
|
+
// e.g. records.json's archive_access/delete_access) — the same
|
|
8466
|
+
// mechanism the read branch uses, now generalized: hap-core's
|
|
8467
|
+
// Gatekeeper deliberately never compares an `enum` bound against
|
|
8468
|
+
// execution (gatekeeper.ts's 'enum' case: "the gateway tool-proxy
|
|
8469
|
+
// gates tool calls ... before they reach the Gatekeeper"), so if this
|
|
8470
|
+
// branch dropped it, nothing anywhere would ever enforce it. Before
|
|
8471
|
+
// this, boundField/requiredValue on a non-"read" override was silently
|
|
8472
|
+
// discarded here — the config in the manifest existed but did nothing.
|
|
8473
|
+
boundField: ext.boundField,
|
|
8474
|
+
requiredValue: ext.requiredValue
|
|
8208
8475
|
};
|
|
8209
8476
|
}
|
|
8210
8477
|
return {
|
|
@@ -8335,7 +8602,7 @@ async function buildTicketDetails(archive, ticketIds) {
|
|
|
8335
8602
|
}
|
|
8336
8603
|
|
|
8337
8604
|
// src/lib/report/window.ts
|
|
8338
|
-
import { getProfile as
|
|
8605
|
+
import { getProfile as getProfile7 } from "@hap/core";
|
|
8339
8606
|
var REPORT_AGE_FIELD = "read_age_days";
|
|
8340
8607
|
var REPORTING_PROFILE = "reporting";
|
|
8341
8608
|
var END_SKEW_SECONDS = 120;
|
|
@@ -8349,7 +8616,7 @@ function windowLabel(start, days, loadedAt) {
|
|
|
8349
8616
|
if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
|
|
8350
8617
|
return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
|
|
8351
8618
|
}
|
|
8352
|
-
var defaultLookup = (id) =>
|
|
8619
|
+
var defaultLookup = (id) => getProfile7(id);
|
|
8353
8620
|
function reportingMandateRefusal(m, lookup = defaultLookup) {
|
|
8354
8621
|
const schema = lookup(m.profileId)?.boundsSchema;
|
|
8355
8622
|
const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
|
|
@@ -8559,28 +8826,28 @@ function agentAccess(token, opensSession) {
|
|
|
8559
8826
|
}
|
|
8560
8827
|
|
|
8561
8828
|
// src/lib/profile-loader.ts
|
|
8562
|
-
import { readFileSync as
|
|
8563
|
-
import { join as
|
|
8829
|
+
import { readFileSync as readFileSync20, existsSync as existsSync19 } from "fs";
|
|
8830
|
+
import { join as join19, resolve as resolve2 } from "path";
|
|
8564
8831
|
import { registerProfile } from "@hap/core";
|
|
8565
8832
|
function loadProfiles(profilesDir) {
|
|
8566
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
8567
|
-
const indexPath =
|
|
8568
|
-
if (!
|
|
8833
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join19(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
8834
|
+
const indexPath = join19(dir, "index.json");
|
|
8835
|
+
if (!existsSync19(indexPath)) {
|
|
8569
8836
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
8570
8837
|
return 0;
|
|
8571
8838
|
}
|
|
8572
8839
|
let index;
|
|
8573
8840
|
try {
|
|
8574
|
-
index = JSON.parse(
|
|
8841
|
+
index = JSON.parse(readFileSync20(indexPath, "utf-8"));
|
|
8575
8842
|
} catch (err) {
|
|
8576
8843
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
8577
8844
|
return 0;
|
|
8578
8845
|
}
|
|
8579
8846
|
let loaded = 0;
|
|
8580
8847
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
8581
|
-
const profilePath =
|
|
8848
|
+
const profilePath = join19(dir, relativePath);
|
|
8582
8849
|
try {
|
|
8583
|
-
const profile = JSON.parse(
|
|
8850
|
+
const profile = JSON.parse(readFileSync20(profilePath, "utf-8"));
|
|
8584
8851
|
registerProfile(profileId, profile);
|
|
8585
8852
|
const shortName2 = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
8586
8853
|
if (shortName2 && shortName2 !== profileId) {
|
|
@@ -8921,20 +9188,20 @@ async function controlPlaneMandate(dryRun, request) {
|
|
|
8921
9188
|
}
|
|
8922
9189
|
|
|
8923
9190
|
// src/lib/guides.ts
|
|
8924
|
-
import { existsSync as
|
|
8925
|
-
import { dirname as
|
|
9191
|
+
import { existsSync as existsSync20, readdirSync, readFileSync as readFileSync21 } from "fs";
|
|
9192
|
+
import { dirname as dirname15, join as join20 } from "path";
|
|
8926
9193
|
var FILE_RE = /^(?:(\d+)-)?([a-z0-9][a-z0-9-]*)\.md$/;
|
|
8927
9194
|
function builtinGuidesDir() {
|
|
8928
9195
|
if (process.env.SUVEREN_BUILTIN_GUIDES_DIR) return process.env.SUVEREN_BUILTIN_GUIDES_DIR;
|
|
8929
9196
|
let dir = import.meta.dirname ?? __dirname;
|
|
8930
9197
|
for (let i = 0; i < 8; i++) {
|
|
8931
|
-
const candidate =
|
|
8932
|
-
if (
|
|
8933
|
-
const parent =
|
|
9198
|
+
const candidate = join20(dir, "content", "guides");
|
|
9199
|
+
if (existsSync20(candidate)) return candidate;
|
|
9200
|
+
const parent = dirname15(dir);
|
|
8934
9201
|
if (parent === dir) break;
|
|
8935
9202
|
dir = parent;
|
|
8936
9203
|
}
|
|
8937
|
-
return
|
|
9204
|
+
return join20(import.meta.dirname ?? __dirname, "content", "guides");
|
|
8938
9205
|
}
|
|
8939
9206
|
function parse(body) {
|
|
8940
9207
|
const lines = body.split(/\r?\n/);
|
|
@@ -8945,14 +9212,14 @@ function parse(body) {
|
|
|
8945
9212
|
return { title, summary };
|
|
8946
9213
|
}
|
|
8947
9214
|
function readDir(dir, source) {
|
|
8948
|
-
if (!dir || !
|
|
9215
|
+
if (!dir || !existsSync20(dir)) return [];
|
|
8949
9216
|
const out = [];
|
|
8950
9217
|
for (const file of readdirSync(dir)) {
|
|
8951
9218
|
const m = FILE_RE.exec(file);
|
|
8952
9219
|
if (!m) continue;
|
|
8953
9220
|
let body;
|
|
8954
9221
|
try {
|
|
8955
|
-
body =
|
|
9222
|
+
body = readFileSync21(join20(dir, file), "utf8");
|
|
8956
9223
|
} catch {
|
|
8957
9224
|
continue;
|
|
8958
9225
|
}
|
|
@@ -9087,7 +9354,7 @@ function unifiedLineDiff(before, after) {
|
|
|
9087
9354
|
}
|
|
9088
9355
|
|
|
9089
9356
|
// src/lib/builtins/setup.ts
|
|
9090
|
-
import { getProfile as
|
|
9357
|
+
import { getProfile as getProfile8 } from "@hap/core";
|
|
9091
9358
|
var DELEGATION_PROFILE = "delegation";
|
|
9092
9359
|
var DELEGATION_INTENT_HINT = [
|
|
9093
9360
|
"Why \u2014 With test data only, I want to find out which routine parts of my work an AI could take over, so people gain time \u2014 before anything touches real systems.",
|
|
@@ -9139,7 +9406,7 @@ function delegationItselfRefusal(args) {
|
|
|
9139
9406
|
}
|
|
9140
9407
|
function delegationMandateRefusal(auth) {
|
|
9141
9408
|
if (isSimulationMode()) return void 0;
|
|
9142
|
-
const profile =
|
|
9409
|
+
const profile = getProfile8(auth.profileId);
|
|
9143
9410
|
if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
|
|
9144
9411
|
return `your Delegation mandate (${shortName(auth.profileId)} ${versionOf(auth.profileId)}) does not declare which profiles or modes you may propose \u2014 only a Delegation mandate on delegation 0.4 or later works outside simulation mode. Ask the person to give a current Delegation mandate.`;
|
|
9145
9412
|
}
|
|
@@ -9197,7 +9464,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
9197
9464
|
);
|
|
9198
9465
|
const first = governing[0];
|
|
9199
9466
|
if (first) {
|
|
9200
|
-
const delegationProfile =
|
|
9467
|
+
const delegationProfile = getProfile8(first.profileId);
|
|
9201
9468
|
const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
|
|
9202
9469
|
if (entry && entry.limit !== null) {
|
|
9203
9470
|
checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
|
|
@@ -9207,7 +9474,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
9207
9474
|
}
|
|
9208
9475
|
function resolveTargetProfile(input) {
|
|
9209
9476
|
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
9210
|
-
const profile =
|
|
9477
|
+
const profile = getProfile8(input.trim());
|
|
9211
9478
|
if (!profile) return void 0;
|
|
9212
9479
|
return { profile, shortName: shortName(profile.id) };
|
|
9213
9480
|
}
|
|
@@ -9358,7 +9625,7 @@ Read each topic with get_guide(topic) before doing that step.`);
|
|
|
9358
9625
|
return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
|
|
9359
9626
|
}
|
|
9360
9627
|
const body = fillGenerated(guide.body.trim(), {
|
|
9361
|
-
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile:
|
|
9628
|
+
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile8(s.profile) }))),
|
|
9362
9629
|
package: () => packageSection(
|
|
9363
9630
|
deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
|
|
9364
9631
|
)
|
|
@@ -9500,8 +9767,8 @@ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
|
9500
9767
|
import { decodeMandateBlob as decodeMandateBlob3 } from "@hap/core";
|
|
9501
9768
|
|
|
9502
9769
|
// src/lib/committed-executor.ts
|
|
9503
|
-
import { existsSync as
|
|
9504
|
-
import { dirname as
|
|
9770
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync16, readFileSync as readFileSync22, unlinkSync as unlinkSync8, writeFileSync as writeFileSync16 } from "fs";
|
|
9771
|
+
import { dirname as dirname16, join as join21 } from "path";
|
|
9505
9772
|
function describeAutoExecutionLog(proposalId, tool, result) {
|
|
9506
9773
|
if (result.skipped) return null;
|
|
9507
9774
|
return result.isError ? `[Suveren MCP] Auto-exec proposal ${proposalId}: ${result.text}` : `[Suveren MCP] Auto-executed proposal ${proposalId}: ${tool}`;
|
|
@@ -9533,7 +9800,7 @@ var ExecutorLock = class {
|
|
|
9533
9800
|
path;
|
|
9534
9801
|
held = false;
|
|
9535
9802
|
constructor(dataDir2) {
|
|
9536
|
-
this.path =
|
|
9803
|
+
this.path = join21(dataDir2, "executor.lock");
|
|
9537
9804
|
}
|
|
9538
9805
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
9539
9806
|
acquire() {
|
|
@@ -9541,9 +9808,9 @@ var ExecutorLock = class {
|
|
|
9541
9808
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
9542
9809
|
return { held: false, holderPid: current.pid };
|
|
9543
9810
|
}
|
|
9544
|
-
|
|
9811
|
+
mkdirSync16(dirname16(this.path), { recursive: true, mode: 448 });
|
|
9545
9812
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
9546
|
-
|
|
9813
|
+
writeFileSync16(this.path, JSON.stringify(lock), { mode: 384 });
|
|
9547
9814
|
this.held = true;
|
|
9548
9815
|
return { held: true };
|
|
9549
9816
|
}
|
|
@@ -9559,15 +9826,15 @@ var ExecutorLock = class {
|
|
|
9559
9826
|
const current = this.read();
|
|
9560
9827
|
if (current?.pid === process.pid) {
|
|
9561
9828
|
try {
|
|
9562
|
-
|
|
9829
|
+
unlinkSync8(this.path);
|
|
9563
9830
|
} catch {
|
|
9564
9831
|
}
|
|
9565
9832
|
}
|
|
9566
9833
|
}
|
|
9567
9834
|
read() {
|
|
9568
|
-
if (!
|
|
9835
|
+
if (!existsSync21(this.path)) return null;
|
|
9569
9836
|
try {
|
|
9570
|
-
const parsed = JSON.parse(
|
|
9837
|
+
const parsed = JSON.parse(readFileSync22(this.path, "utf8"));
|
|
9571
9838
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
9572
9839
|
} catch {
|
|
9573
9840
|
return null;
|
|
@@ -9584,20 +9851,20 @@ function isAlive(pid) {
|
|
|
9584
9851
|
}
|
|
9585
9852
|
|
|
9586
9853
|
// bin/http.ts
|
|
9587
|
-
import { homedir as
|
|
9588
|
-
import { join as
|
|
9854
|
+
import { homedir as homedir14 } from "os";
|
|
9855
|
+
import { join as join23 } from "path";
|
|
9589
9856
|
|
|
9590
9857
|
// src/lib/mcp-as-tracker.ts
|
|
9591
|
-
import { existsSync as
|
|
9592
|
-
import { dirname as
|
|
9858
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync17, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
|
|
9859
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
9593
9860
|
function trackerPath(dataDir2) {
|
|
9594
|
-
return
|
|
9861
|
+
return join22(dataDir2, "mcp-as-pairing.json");
|
|
9595
9862
|
}
|
|
9596
9863
|
function readMcpPairedAsUrl(dataDir2) {
|
|
9597
9864
|
const path = trackerPath(dataDir2);
|
|
9598
|
-
if (!
|
|
9865
|
+
if (!existsSync22(path)) return null;
|
|
9599
9866
|
try {
|
|
9600
|
-
const data = JSON.parse(
|
|
9867
|
+
const data = JSON.parse(readFileSync23(path, "utf-8"));
|
|
9601
9868
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
9602
9869
|
} catch {
|
|
9603
9870
|
return null;
|
|
@@ -9605,12 +9872,12 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
9605
9872
|
}
|
|
9606
9873
|
function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
9607
9874
|
const path = trackerPath(dataDir2);
|
|
9608
|
-
|
|
9609
|
-
|
|
9875
|
+
mkdirSync17(dirname17(path), { recursive: true, mode: 448 });
|
|
9876
|
+
writeFileSync17(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
|
|
9610
9877
|
}
|
|
9611
9878
|
|
|
9612
9879
|
// bin/http.ts
|
|
9613
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
9880
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
|
|
9614
9881
|
var spUrl = resolveAsUrl(dataDir);
|
|
9615
9882
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
9616
9883
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -9696,7 +9963,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
9696
9963
|
state.receiptArchive.setVaultKey(key);
|
|
9697
9964
|
state.reportStore.setVaultKey(key);
|
|
9698
9965
|
state.outcomeOutbox.setVaultKey(key);
|
|
9699
|
-
|
|
9966
|
+
state.executionResults.setVaultKey(key);
|
|
9967
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive + report store + outcome outbox + execution results encryption active");
|
|
9700
9968
|
}
|
|
9701
9969
|
if (apiKey) {
|
|
9702
9970
|
state.spClient.setApiKey(apiKey);
|
|
@@ -10364,7 +10632,7 @@ app.listen(port, bindHost, () => {
|
|
|
10364
10632
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
10365
10633
|
);
|
|
10366
10634
|
installCommittedExecutor(committedExecutor);
|
|
10367
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
10635
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
|
|
10368
10636
|
const executorLock = new ExecutorLock(dataDir2);
|
|
10369
10637
|
const lock = executorLock.acquire();
|
|
10370
10638
|
if (!lock.held) {
|