@suveren/gateway 0.28.1 → 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.
@@ -2190,21 +2190,168 @@ function nowSec() {
2190
2190
  return Math.floor(Date.now() / 1e3);
2191
2191
  }
2192
2192
 
2193
- // src/lib/proposal-submission-store.ts
2194
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, renameSync as renameSync2 } from "fs";
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") ? dirname7(filePath) : filePath ?? DEFAULT_DIR6;
2207
- this.filePath = join9(baseDir, FILE_NAME2);
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: nowSec2(),
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 (!existsSync9(this.filePath)) return { version: 1, entries: [] };
2378
+ if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
2232
2379
  try {
2233
- const parsed = JSON.parse(readFileSync10(this.filePath, "utf-8"));
2234
- const cutoff = nowSec2() - MAX_AGE_SECONDS3;
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
- mkdirSync7(dirname7(this.filePath), { recursive: true, mode: 448 });
2389
+ mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
2243
2390
  const tmpPath = `${this.filePath}.tmp`;
2244
- writeFileSync7(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
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 mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync3 } from "fs";
2251
- import { homedir as homedir7 } from "os";
2252
- import { dirname as dirname8, join as join10 } from "path";
2253
- var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
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") ? dirname8(filePath) : filePath ?? DEFAULT_DIR7;
2261
- this.filePath = join10(baseDir, FILE_NAME3);
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 (!existsSync10(this.filePath)) return { version: 1, entries: [] };
2430
+ if (!existsSync11(this.filePath)) return { version: 1, entries: [] };
2284
2431
  try {
2285
- const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
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
- mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
2439
+ mkdirSync9(dirname9(this.filePath), { recursive: true, mode: 448 });
2293
2440
  const tmp = `${this.filePath}.tmp`;
2294
- writeFileSync8(tmp, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
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 mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, unlinkSync as unlinkSync5 } from "fs";
2301
- import { homedir as homedir8 } from "os";
2302
- import { dirname as dirname9, join as join11 } from "path";
2303
- import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
2304
- var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
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") ? dirname9(filePath) : filePath ?? DEFAULT_DIR8;
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 (existsSync11(this.encryptedFilePath)) {
2472
+ if (existsSync12(this.encryptedFilePath)) {
2326
2473
  this.loadEncrypted();
2327
2474
  } else if (this.entries.length > 0) {
2328
2475
  this.persistEncrypted();
2329
- if (existsSync11(this.plaintextFilePath)) {
2476
+ if (existsSync12(this.plaintextFilePath)) {
2330
2477
  try {
2331
- unlinkSync5(this.plaintextFilePath);
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 = randomBytes5(12);
2381
- const cipher = createCipheriv5("aes-256-gcm", this.vaultKey, iv);
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 = createDecipheriv5("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
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 join11(this.baseDir, "outcome-outbox.json");
2539
+ return join12(this.baseDir, "outcome-outbox.json");
2393
2540
  }
2394
2541
  get encryptedFilePath() {
2395
- return join11(this.baseDir, "outcome-outbox.enc.json");
2542
+ return join12(this.baseDir, "outcome-outbox.enc.json");
2396
2543
  }
2397
2544
  loadPlaintext() {
2398
- if (!existsSync11(this.plaintextFilePath)) {
2399
- mkdirSync9(this.baseDir, { recursive: true });
2545
+ if (!existsSync12(this.plaintextFilePath)) {
2546
+ mkdirSync10(this.baseDir, { recursive: true });
2400
2547
  return;
2401
2548
  }
2402
2549
  try {
2403
- const data = JSON.parse(readFileSync12(this.plaintextFilePath, "utf-8"));
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 (!existsSync11(this.encryptedFilePath)) return;
2559
+ if (!existsSync12(this.encryptedFilePath)) return;
2413
2560
  try {
2414
- const data = JSON.parse(readFileSync12(this.encryptedFilePath, "utf-8"));
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
- mkdirSync9(this.baseDir, { recursive: true, mode: 448 });
2430
- writeFileSync9(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
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
- mkdirSync9(this.baseDir, { recursive: true, mode: 448 });
2436
- writeFileSync9(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
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 mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12, unlinkSync as unlinkSync6 } from "fs";
2549
- import { homedir as homedir9 } from "os";
2550
- import { dirname as dirname10, join as join12 } from "path";
2551
- import { createCipheriv as createCipheriv6, createDecipheriv as createDecipheriv6, randomBytes as randomBytes6 } from "crypto";
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 DEFAULT_DIR9 = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
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 = dirname10(filePath);
3013
+ this.baseDir = dirname11(filePath);
2867
3014
  } else {
2868
- this.baseDir = filePath ?? DEFAULT_DIR9;
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 (existsSync12(this.encryptedFilePath)) {
3022
+ if (existsSync13(this.encryptedFilePath)) {
2876
3023
  this.loadEncrypted();
2877
3024
  } else if (this.report !== null) {
2878
3025
  this.persistEncrypted();
2879
- if (existsSync12(this.plaintextFilePath)) {
3026
+ if (existsSync13(this.plaintextFilePath)) {
2880
3027
  try {
2881
- unlinkSync6(this.plaintextFilePath);
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 && existsSync12(this.encryptedFilePath);
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 = randomBytes6(12);
2929
- const cipher = createCipheriv6("aes-256-gcm", this.vaultKey, iv);
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 = createDecipheriv6("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
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 join12(this.baseDir, "report.json");
3090
+ return join13(this.baseDir, "report.json");
2944
3091
  }
2945
3092
  get encryptedFilePath() {
2946
- return join12(this.baseDir, "report.enc.json");
3093
+ return join13(this.baseDir, "report.enc.json");
2947
3094
  }
2948
3095
  // ─── Load / Persist ─────────────────────────────────────────────────────
2949
3096
  loadPlaintext() {
2950
- if (!existsSync12(this.plaintextFilePath)) {
2951
- mkdirSync10(this.baseDir, { recursive: true });
3097
+ if (!existsSync13(this.plaintextFilePath)) {
3098
+ mkdirSync11(this.baseDir, { recursive: true });
2952
3099
  return;
2953
3100
  }
2954
3101
  try {
2955
- const raw = readFileSync13(this.plaintextFilePath, "utf-8");
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
- writeFileSync10(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync13(this.plaintextFilePath));
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 (!existsSync12(this.encryptedFilePath)) return;
3115
+ if (!existsSync13(this.encryptedFilePath)) return;
2969
3116
  try {
2970
- const raw = readFileSync13(this.encryptedFilePath, "utf-8");
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
- writeFileSync10(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync13(this.encryptedFilePath));
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
- mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
2994
- writeFileSync10(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
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
- mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
3002
- writeFileSync10(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
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 getProfile3 } from "@hap/core";
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 readFileSync14, existsSync as existsSync13, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11, renameSync as renameSync4 } from "fs";
3252
- import { homedir as homedir10 } from "os";
3253
- import { join as join13, dirname as dirname11 } from "path";
3254
- var DEFAULT_DIR10 = process.env.SUVEREN_DATA_DIR ?? join13(homedir10(), ".suveren");
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 join13(dataDir2 ?? DEFAULT_DIR10, "context.md");
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
- mkdirSync11(dirname11(filePath), { recursive: true });
3413
+ mkdirSync12(dirname12(filePath), { recursive: true });
3264
3414
  const tmpPath = `${filePath}.tmp`;
3265
- writeFileSync11(tmpPath, content, "utf-8");
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 (!existsSync13(filePath)) return null;
3421
+ if (!existsSync14(filePath)) return null;
3272
3422
  try {
3273
- const content = readFileSync14(filePath, "utf-8").trim();
3423
+ const content = readFileSync15(filePath, "utf-8").trim();
3274
3424
  return content || null;
3275
3425
  } catch {
3276
3426
  return null;
@@ -3467,12 +3617,234 @@ function attachTicketId(tool, args, ticketId) {
3467
3617
  import { computeContentHash as computeContentHash2 } from "@hap/core";
3468
3618
 
3469
3619
  // src/lib/manifest-loader.ts
3470
- import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
3471
- import { join as join14, resolve } from "path";
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";
3472
3623
 
3473
3624
  // src/lib/tool-gating-types.ts
3474
3625
  var MAX_PREVIEW_READS = 10;
3475
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);
3847
+ }
3476
3848
 
3477
3849
  // src/lib/manifest-loader.ts
3478
3850
  var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -3597,28 +3969,259 @@ function invalidManifestPreviewReason(manifest) {
3597
3969
  }
3598
3970
  return null;
3599
3971
  }
3972
+ function isNonEmptyString(v) {
3973
+ return typeof v === "string" && v.length > 0;
3974
+ }
3975
+ var VALID_FIELD_PATH_PARSE = ["address.name", "address.email", "epoch-ms"];
3976
+ function invalidFieldPathReason(label, value) {
3977
+ if (isNonEmptyString(value)) return null;
3978
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3979
+ return `${label} must be a non-empty string or a field-path object`;
3980
+ }
3981
+ const f = value;
3982
+ if (!isNonEmptyString(f.path)) return `${label}.path must be a non-empty string`;
3983
+ if (f.where !== void 0) {
3984
+ if (f.where === null || typeof f.where !== "object" || Array.isArray(f.where)) {
3985
+ return `${label}.where must be an object`;
3986
+ }
3987
+ for (const [k, v] of Object.entries(f.where)) {
3988
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
3989
+ return `${label}.where.${k} must be a string, number, or boolean`;
3990
+ }
3991
+ }
3992
+ }
3993
+ if (f.pick !== void 0 && !isNonEmptyString(f.pick)) {
3994
+ return `${label}.pick must be a non-empty string`;
3995
+ }
3996
+ if (f.parse !== void 0 && !VALID_FIELD_PATH_PARSE.includes(f.parse)) {
3997
+ return `${label}.parse must be one of ${VALID_FIELD_PATH_PARSE.join(", ")}`;
3998
+ }
3999
+ return null;
4000
+ }
4001
+ function invalidContextReadReason(label, entry) {
4002
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
4003
+ return `${label} must be an object`;
4004
+ }
4005
+ const r = entry;
4006
+ if (!isNonEmptyString(r.tool)) return `${label}.tool must be a non-empty string`;
4007
+ if (r.args === void 0 || r.args === null || typeof r.args !== "object" || Array.isArray(r.args)) {
4008
+ return `${label}.args must be an object`;
4009
+ }
4010
+ for (const [k, v] of Object.entries(r.args)) {
4011
+ if (typeof v !== "string" || v.length === 0) {
4012
+ return `${label}.args.${k} must be a non-empty string`;
4013
+ }
4014
+ }
4015
+ return null;
4016
+ }
4017
+ function invalidContextSourceReason(toolName, entry) {
4018
+ const label = `context.sources.${toolName}`;
4019
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
4020
+ return `${label} must be an object`;
4021
+ }
4022
+ const s = entry;
4023
+ const readReason = invalidContextReadReason(`${label}.read`, s.read);
4024
+ if (readReason) return readReason;
4025
+ for (const field of ["title", "author", "at", "body"]) {
4026
+ if (s[field] !== void 0) {
4027
+ const reason = invalidFieldPathReason(`${label}.${field}`, s[field]);
4028
+ if (reason) return reason;
4029
+ }
4030
+ }
4031
+ if (s.keys !== void 0) {
4032
+ if (s.keys === null || typeof s.keys !== "object" || Array.isArray(s.keys)) {
4033
+ return `${label}.keys must be an object`;
4034
+ }
4035
+ for (const [k, v] of Object.entries(s.keys)) {
4036
+ if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
4037
+ const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
4038
+ if (reason) return reason;
4039
+ }
4040
+ }
4041
+ if (s.chain !== void 0) {
4042
+ if (s.chain === null || typeof s.chain !== "object" || Array.isArray(s.chain)) {
4043
+ return `${label}.chain must be an object`;
4044
+ }
4045
+ const c = s.chain;
4046
+ if (!isNonEmptyString(c.field)) return `${label}.chain.field must be a non-empty string`;
4047
+ if (typeof c.max !== "number" || c.max <= 0) return `${label}.chain.max must be a positive number`;
4048
+ }
4049
+ return null;
4050
+ }
4051
+ function invalidContextHistoryReason(history) {
4052
+ const label = "context.history";
4053
+ if (history === null || typeof history !== "object" || Array.isArray(history)) {
4054
+ return `${label} must be an object`;
4055
+ }
4056
+ const h = history;
4057
+ const isKeyType = (v) => v === "email" || v === "email_domain";
4058
+ if (Array.isArray(h.byKey)) {
4059
+ if (h.byKey.length === 0) return `${label}.byKey must not be an empty array`;
4060
+ for (const [i, v] of h.byKey.entries()) {
4061
+ if (!isKeyType(v)) return `${label}.byKey[${i}] must be "email" or "email_domain"`;
4062
+ }
4063
+ } else if (!isKeyType(h.byKey)) {
4064
+ return `${label}.byKey must be "email" or "email_domain" (or an array of those, in preference order)`;
4065
+ }
4066
+ const readReason = invalidContextReadReason(`${label}.read`, h.read);
4067
+ if (readReason) return readReason;
4068
+ if (typeof h.limit !== "number" || h.limit <= 0) return `${label}.limit must be a positive number`;
4069
+ if (typeof h.maxAgeDays !== "number" || h.maxAgeDays <= 0) return `${label}.maxAgeDays must be a positive number`;
4070
+ if (h.item === null || typeof h.item !== "object" || Array.isArray(h.item)) {
4071
+ return `${label}.item must be an object`;
4072
+ }
4073
+ for (const [k, v] of Object.entries(h.item)) {
4074
+ if (k === "kind") {
4075
+ if (!isNonEmptyString(v)) return `${label}.item.kind must be a non-empty string`;
4076
+ continue;
4077
+ }
4078
+ const reason = invalidFieldPathReason(`${label}.item.${k}`, v);
4079
+ if (reason) return reason;
4080
+ }
4081
+ return null;
4082
+ }
4083
+ function invalidContextRecordKeyReason(name, entry) {
4084
+ const label = `context.recordKeys.${name}`;
4085
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
4086
+ return `${label} must be an object`;
4087
+ }
4088
+ const rk = entry;
4089
+ const readReason = invalidContextReadReason(`${label}.read`, rk.read);
4090
+ if (readReason) return readReason;
4091
+ if (rk.keys !== void 0) {
4092
+ if (rk.keys === null || typeof rk.keys !== "object" || Array.isArray(rk.keys)) {
4093
+ return `${label}.keys must be an object`;
4094
+ }
4095
+ for (const [k, v] of Object.entries(rk.keys)) {
4096
+ if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
4097
+ const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
4098
+ if (reason) return reason;
4099
+ }
4100
+ }
4101
+ if (rk.next !== void 0) {
4102
+ if (rk.next === null || typeof rk.next !== "object" || Array.isArray(rk.next)) {
4103
+ return `${label}.next must be an object`;
4104
+ }
4105
+ const next = rk.next;
4106
+ if (!isNonEmptyString(next.recordKey)) return `${label}.next.recordKey must be a non-empty string`;
4107
+ if (!isNonEmptyString(next.field)) return `${label}.next.field must be a non-empty string`;
4108
+ }
4109
+ if (rk.keys === void 0 && rk.next === void 0) {
4110
+ return `${label} must declare "keys", "next", or both \u2014 an entry with neither learns nothing from the record it reads`;
4111
+ }
4112
+ return null;
4113
+ }
4114
+ function recordKeyChainLength(name, recordKeys, seen) {
4115
+ if (seen.has(name)) return "cycle";
4116
+ const entry = recordKeys[name];
4117
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return "missing";
4118
+ const next = entry.next;
4119
+ if (!next || typeof next !== "object") return 1;
4120
+ const nextName = next.recordKey;
4121
+ if (typeof nextName !== "string" || !(nextName in recordKeys)) return "missing";
4122
+ const rest = recordKeyChainLength(nextName, recordKeys, /* @__PURE__ */ new Set([...seen, name]));
4123
+ if (rest === "cycle" || rest === "missing") return rest;
4124
+ return 1 + rest;
4125
+ }
4126
+ function invalidManifestContextReason(manifest) {
4127
+ const ctx = manifest.context;
4128
+ const recordKeyNames = new Set(Object.keys(ctx?.recordKeys ?? {}));
4129
+ if (ctx) {
4130
+ if (ctx.sources !== void 0) {
4131
+ if (ctx.sources === null || typeof ctx.sources !== "object" || Array.isArray(ctx.sources)) {
4132
+ return "context.sources must be an object";
4133
+ }
4134
+ for (const [kind, entry] of Object.entries(ctx.sources)) {
4135
+ const reason = invalidContextSourceReason(kind, entry);
4136
+ if (reason) return reason;
4137
+ }
4138
+ }
4139
+ if (ctx.history !== void 0) {
4140
+ const reason = invalidContextHistoryReason(ctx.history);
4141
+ if (reason) return reason;
4142
+ }
4143
+ if (ctx.recordKeys !== void 0) {
4144
+ if (ctx.recordKeys === null || typeof ctx.recordKeys !== "object" || Array.isArray(ctx.recordKeys)) {
4145
+ return "context.recordKeys must be an object";
4146
+ }
4147
+ for (const [name, entry] of Object.entries(ctx.recordKeys)) {
4148
+ const reason = invalidContextRecordKeyReason(name, entry);
4149
+ if (reason) return reason;
4150
+ }
4151
+ for (const name of Object.keys(ctx.recordKeys)) {
4152
+ const length = recordKeyChainLength(name, ctx.recordKeys, /* @__PURE__ */ new Set());
4153
+ if (length === "cycle") {
4154
+ return `context.recordKeys.${name}.next forms a cycle`;
4155
+ }
4156
+ if (length === "missing") {
4157
+ return `context.recordKeys.${name}.next.recordKey names an entry this manifest does not declare`;
4158
+ }
4159
+ if (length > MAX_RECORD_KEY_STEPS) {
4160
+ return `context.recordKeys.${name}.next chain is ${length} steps long \u2014 max ${MAX_RECORD_KEY_STEPS}`;
4161
+ }
4162
+ }
4163
+ }
4164
+ }
4165
+ const sourceKinds = new Set(Object.keys(ctx?.sources ?? {}));
4166
+ const overrides = manifest.toolGating?.overrides ?? {};
4167
+ for (const [toolName, entry] of Object.entries(overrides)) {
4168
+ if (entry === null || typeof entry !== "object") continue;
4169
+ const e = entry;
4170
+ if (e.contextKeys !== void 0) {
4171
+ if (e.contextKeys === null || typeof e.contextKeys !== "object" || Array.isArray(e.contextKeys)) {
4172
+ return `override "${toolName}".contextKeys must be an object`;
4173
+ }
4174
+ for (const [name, argName] of Object.entries(e.contextKeys)) {
4175
+ if (!isNonEmptyString(argName)) {
4176
+ return `override "${toolName}".contextKeys.${name} must be a non-empty string naming an action argument`;
4177
+ }
4178
+ if (name !== "email" && !recordKeyNames.has(name)) {
4179
+ return `override "${toolName}".contextKeys.${name}: "${name}" is neither "email" nor a context.recordKeys entry on this manifest`;
4180
+ }
4181
+ }
4182
+ }
4183
+ if (e.sourceFromArg !== void 0) {
4184
+ if (e.sourceFromArg === null || typeof e.sourceFromArg !== "object" || Array.isArray(e.sourceFromArg)) {
4185
+ return `override "${toolName}".sourceFromArg must be an object`;
4186
+ }
4187
+ const sfa = e.sourceFromArg;
4188
+ if (!isNonEmptyString(sfa.kind)) return `override "${toolName}".sourceFromArg.kind must be a non-empty string`;
4189
+ if (!isNonEmptyString(sfa.arg)) return `override "${toolName}".sourceFromArg.arg must be a non-empty string`;
4190
+ if (!sourceKinds.has(sfa.kind)) {
4191
+ return `override "${toolName}".sourceFromArg.kind "${String(sfa.kind)}" is not declared in this manifest's context.sources`;
4192
+ }
4193
+ }
4194
+ }
4195
+ return null;
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
+ }
3600
4203
  var manifests = /* @__PURE__ */ new Map();
3601
4204
  function loadManifests(integrationsDir) {
3602
4205
  const dir = resolve(
3603
- integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join14(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
4206
+ integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join15(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
3604
4207
  );
3605
- const indexPath = join14(dir, "index.json");
3606
- if (!existsSync14(indexPath)) {
4208
+ const indexPath = join15(dir, "index.json");
4209
+ if (!existsSync15(indexPath)) {
3607
4210
  console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
3608
4211
  return 0;
3609
4212
  }
3610
4213
  let index;
3611
4214
  try {
3612
- index = JSON.parse(readFileSync15(indexPath, "utf-8"));
4215
+ index = JSON.parse(readFileSync16(indexPath, "utf-8"));
3613
4216
  } catch (err) {
3614
4217
  console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
3615
4218
  return 0;
3616
4219
  }
3617
4220
  let loaded = 0;
3618
4221
  for (const [id, relativePath] of Object.entries(index.integrations)) {
3619
- const manifestPath = join14(dir, relativePath);
4222
+ const manifestPath = join15(dir, relativePath);
3620
4223
  try {
3621
- const manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
4224
+ const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
3622
4225
  const pinReason = invalidNpmPinReason(manifest);
3623
4226
  if (pinReason) {
3624
4227
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
@@ -3629,6 +4232,16 @@ function loadManifests(integrationsDir) {
3629
4232
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
3630
4233
  continue;
3631
4234
  }
4235
+ const contextReason = invalidManifestContextReason(manifest);
4236
+ if (contextReason) {
4237
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
4238
+ continue;
4239
+ }
4240
+ const readGateReason = invalidManifestReadGateReason(manifest);
4241
+ if (readGateReason) {
4242
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${readGateReason}`);
4243
+ continue;
4244
+ }
3632
4245
  manifests.set(id, manifest);
3633
4246
  loaded++;
3634
4247
  } catch (err) {
@@ -3897,20 +4510,436 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
3897
4510
  if (read2.status !== "ok" || read2.body.structured === void 0) {
3898
4511
  return ok();
3899
4512
  }
3900
- const current = read2.body.structured[preview.version.field];
3901
- if (typeof current !== "string" && typeof current !== "number") {
3902
- return ok();
4513
+ const current = read2.body.structured[preview.version.field];
4514
+ if (typeof current !== "string" && typeof current !== "number") {
4515
+ return ok();
4516
+ }
4517
+ const stale = String(current) !== String(approved);
4518
+ return ok({
4519
+ version: {
4520
+ field: preview.version.field,
4521
+ approved,
4522
+ current,
4523
+ stale,
4524
+ ...stale ? { currentBody: read2.body } : {}
4525
+ }
4526
+ });
4527
+ }
4528
+
4529
+ // src/lib/field-path.ts
4530
+ function matchesWhere(element, where) {
4531
+ if (element === null || typeof element !== "object" || Array.isArray(element)) return false;
4532
+ const row = element;
4533
+ return Object.entries(where).every(([key, expected]) => {
4534
+ const actual = row[key];
4535
+ if (typeof expected === "string" && typeof actual === "string") {
4536
+ return expected.toLowerCase() === actual.toLowerCase();
4537
+ }
4538
+ return actual === expected;
4539
+ });
4540
+ }
4541
+ function parseAddress(value, which) {
4542
+ const m = value.match(/^(.*?)<([^<>]+)>\s*$/);
4543
+ if (m) {
4544
+ const email = m[2].trim();
4545
+ if (which === "address.email") return email || void 0;
4546
+ const name = m[1].trim().replace(/^"(.*)"$/, "$1").trim();
4547
+ return name || void 0;
4548
+ }
4549
+ if (which === "address.email") return value.trim() || void 0;
4550
+ return void 0;
4551
+ }
4552
+ function parseEpochMs(value) {
4553
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
4554
+ if (!Number.isFinite(n)) return void 0;
4555
+ return new Date(n).toISOString();
4556
+ }
4557
+ function applyParse(value, parse2) {
4558
+ if (value === void 0 || value === null) return void 0;
4559
+ if (parse2 === "epoch-ms") return parseEpochMs(value);
4560
+ if (typeof value !== "string") return void 0;
4561
+ return parseAddress(value, parse2);
4562
+ }
4563
+ function resolveFieldPath(record, fieldPath) {
4564
+ if (fieldPath === void 0) return void 0;
4565
+ if (typeof fieldPath === "string") return getByDottedPath(record, fieldPath);
4566
+ let value = getByDottedPath(record, fieldPath.path);
4567
+ if (fieldPath.where) {
4568
+ if (!Array.isArray(value)) return void 0;
4569
+ value = value.find((el) => matchesWhere(el, fieldPath.where));
4570
+ if (value === void 0) return void 0;
4571
+ }
4572
+ if (fieldPath.pick !== void 0) {
4573
+ value = getByDottedPath(value, fieldPath.pick);
4574
+ }
4575
+ if (fieldPath.parse) {
4576
+ value = applyParse(value, fieldPath.parse);
4577
+ }
4578
+ return value;
4579
+ }
4580
+
4581
+ // src/lib/request-context.ts
4582
+ var SOURCE_LINK_ARG = "_source";
4583
+ function withSourceLink(schema) {
4584
+ const props = schema.properties ?? {};
4585
+ if ("_source" in props) return schema;
4586
+ return {
4587
+ ...schema,
4588
+ properties: {
4589
+ ...props,
4590
+ _source: {
4591
+ type: "object",
4592
+ description: 'Optional. Link the request you are answering \u2014 the ONE record (e.g. an inbound email) this action responds to, so the human approver can see why it was proposed. Only a reference: {"connector": "<integration id>", "kind": "<declared kind>", "id": "<record id>"}. Example: {"connector":"mail","kind":"message","id":"m-17"}. The gateway reads the record itself \u2014 never write a summary or quote here.',
4593
+ properties: {
4594
+ connector: { type: "string" },
4595
+ kind: { type: "string" },
4596
+ id: { type: "string" }
4597
+ }
4598
+ }
4599
+ }
4600
+ };
4601
+ }
4602
+ function stripSourceLink(args) {
4603
+ if (!(SOURCE_LINK_ARG in args)) return args;
4604
+ const { [SOURCE_LINK_ARG]: _omit, ...rest } = args;
4605
+ return rest;
4606
+ }
4607
+ var FREE_MAIL_DOMAINS = /* @__PURE__ */ new Set([
4608
+ "gmail.com",
4609
+ "googlemail.com",
4610
+ "outlook.com",
4611
+ "hotmail.com",
4612
+ "hotmail.co.uk",
4613
+ "hotmail.fr",
4614
+ "hotmail.de",
4615
+ "live.com",
4616
+ "msn.com",
4617
+ "yahoo.com",
4618
+ "yahoo.co.uk",
4619
+ "yahoo.de",
4620
+ "yahoo.fr",
4621
+ "ymail.com",
4622
+ "rocketmail.com",
4623
+ "gmx.de",
4624
+ "gmx.net",
4625
+ "gmx.at",
4626
+ "gmx.ch",
4627
+ "gmx.com",
4628
+ "web.de",
4629
+ "t-online.de",
4630
+ "icloud.com",
4631
+ "me.com",
4632
+ "mac.com",
4633
+ "aol.com",
4634
+ "protonmail.com",
4635
+ "proton.me",
4636
+ "mail.com",
4637
+ "zoho.com",
4638
+ "yandex.com"
4639
+ ]);
4640
+ function isFreeMailDomain(domain) {
4641
+ return FREE_MAIL_DOMAINS.has(domain.toLowerCase());
4642
+ }
4643
+ function deriveIdentityKeysFromEmail(email) {
4644
+ const lower = email.toLowerCase().trim();
4645
+ const at = lower.lastIndexOf("@");
4646
+ if (at <= 0 || at === lower.length - 1) return [];
4647
+ const domain = lower.slice(at + 1);
4648
+ const keys = [{ type: "email", value: lower }];
4649
+ if (!isFreeMailDomain(domain)) keys.push({ type: "email_domain", value: domain });
4650
+ return keys;
4651
+ }
4652
+ function deriveIdentityKeysFromValue(value) {
4653
+ if (typeof value === "string") return deriveIdentityKeysFromEmail(value);
4654
+ if (Array.isArray(value)) {
4655
+ return value.flatMap((v) => typeof v === "string" ? deriveIdentityKeysFromEmail(v) : []);
4656
+ }
4657
+ return [];
4658
+ }
4659
+ function dedupeKeys(keys) {
4660
+ const seen = /* @__PURE__ */ new Set();
4661
+ const out = [];
4662
+ for (const k of keys) {
4663
+ const sig = `${k.type}:${k.value}`;
4664
+ if (seen.has(sig)) continue;
4665
+ seen.add(sig);
4666
+ out.push(k);
4667
+ }
4668
+ return out;
4669
+ }
4670
+ function substitute(template, values) {
4671
+ const out = {};
4672
+ for (const [arg, token] of Object.entries(template)) {
4673
+ if (token.startsWith("$")) {
4674
+ const key = token.slice(1);
4675
+ if (key in values) out[arg] = values[key];
4676
+ } else {
4677
+ out[arg] = token;
4678
+ }
4679
+ }
4680
+ return out;
4681
+ }
4682
+ function parseContextBody(body) {
4683
+ if (body.structured !== void 0) return body.structured;
4684
+ if (body.text !== void 0) {
4685
+ try {
4686
+ return JSON.parse(body.text);
4687
+ } catch {
4688
+ return void 0;
4689
+ }
4690
+ }
4691
+ return void 0;
4692
+ }
4693
+ async function readContextRecord(integrationManager2, connectorId, tool, args) {
4694
+ const read = await readPreview(integrationManager2, connectorId, tool, args);
4695
+ if (read.status === "ok") {
4696
+ const record = parseContextBody(read.body);
4697
+ if (record === void 0) return { status: "unavailable", reason: "connector_error" };
4698
+ return { status: "ok", record };
4699
+ }
4700
+ if (read.status === "not_found") return { status: "not_found" };
4701
+ return { status: "unavailable", reason: read.reason };
4702
+ }
4703
+ function parseExplicitSourceLink(raw) {
4704
+ if (raw === void 0 || raw === null) return void 0;
4705
+ if (typeof raw !== "object" || Array.isArray(raw)) return "invalid";
4706
+ const o = raw;
4707
+ if (typeof o.connector !== "string" || !o.connector) return "invalid";
4708
+ if (typeof o.kind !== "string" || !o.kind) return "invalid";
4709
+ if (typeof o.id !== "string" || !o.id) return "invalid";
4710
+ return { connector: o.connector, kind: o.kind, id: o.id };
4711
+ }
4712
+ function resolveSourceLink(toolArgs, connectorId, sourceFromArg) {
4713
+ const explicit = parseExplicitSourceLink(toolArgs._source);
4714
+ if (explicit !== void 0) return explicit;
4715
+ if (sourceFromArg) {
4716
+ const argVal = toolArgs[sourceFromArg.arg];
4717
+ if (typeof argVal === "string" && argVal) {
4718
+ return { connector: connectorId, kind: sourceFromArg.kind, id: argVal };
4719
+ }
4720
+ }
4721
+ return void 0;
4722
+ }
4723
+ async function readSourceOnce(integrationManager2, link, sourceConfig) {
4724
+ const args = substitute(sourceConfig.read.args, { id: link.id });
4725
+ const read = await readContextRecord(integrationManager2, link.connector, sourceConfig.read.tool, args);
4726
+ if (read.status === "not_found") return { status: "not_found" };
4727
+ if (read.status === "unavailable") return { status: "unreadable", reason: read.reason };
4728
+ const keys = [];
4729
+ if (sourceConfig.keys?.email) {
4730
+ const v = resolveFieldPath(read.record, sourceConfig.keys.email);
4731
+ if (typeof v === "string" && v) keys.push(...deriveIdentityKeysFromEmail(v));
4732
+ }
4733
+ return { record: read.record, keys };
4734
+ }
4735
+ async function resolveRequestContext(integrationManager2, toolArgs, actionConnectorId, sourceFromArg, configuredConnectorIds) {
4736
+ const link = resolveSourceLink(toolArgs, actionConnectorId, sourceFromArg);
4737
+ if (link === void 0) return { result: { status: "none" }, keys: [] };
4738
+ if (link === "invalid") {
4739
+ return { result: { status: "unreadable", reason: "malformed link" }, keys: [] };
4740
+ }
4741
+ if (!configuredConnectorIds.has(link.connector)) {
4742
+ return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
4743
+ }
4744
+ const connectorManifest = getManifest(link.connector);
4745
+ if (!connectorManifest) {
4746
+ return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
4747
+ }
4748
+ const sourceConfig = connectorManifest.context?.sources?.[link.kind];
4749
+ if (!sourceConfig) {
4750
+ return { result: { status: "unreadable", reason: `connector "${link.connector}" declares no "${link.kind}" source` }, keys: [] };
4751
+ }
4752
+ const primary = await readSourceOnce(integrationManager2, link, sourceConfig);
4753
+ if ("status" in primary) {
4754
+ if (primary.status === "not_found") return { result: { status: "not_found" }, keys: [] };
4755
+ return { result: { status: "unreadable", reason: primary.reason ?? "connector unreachable" }, keys: [] };
4756
+ }
4757
+ const implicit = toolArgs._source === void 0;
4758
+ const base = {
4759
+ connector: link.connector,
4760
+ kind: link.kind,
4761
+ implicit,
4762
+ ...sourceConfig.title ? { title: asString(resolveFieldPath(primary.record, sourceConfig.title)) } : {},
4763
+ ...sourceConfig.author ? { author: asString(resolveFieldPath(primary.record, sourceConfig.author)) } : {},
4764
+ ...sourceConfig.at ? { at: asString(resolveFieldPath(primary.record, sourceConfig.at)) } : {},
4765
+ ...sourceConfig.body ? { body: asBody(resolveFieldPath(primary.record, sourceConfig.body)) } : {}
4766
+ };
4767
+ const chain = [];
4768
+ if (sourceConfig.chain) {
4769
+ let currentId = asString(getByDottedPath(primary.record, sourceConfig.chain.field));
4770
+ let hops = 0;
4771
+ const seen = /* @__PURE__ */ new Set([link.id]);
4772
+ while (currentId && hops < sourceConfig.chain.max && !seen.has(currentId)) {
4773
+ seen.add(currentId);
4774
+ const hopLink = { connector: link.connector, kind: link.kind, id: currentId };
4775
+ const hop = await readSourceOnce(integrationManager2, hopLink, sourceConfig);
4776
+ hops++;
4777
+ if ("status" in hop) break;
4778
+ chain.push({
4779
+ ...sourceConfig.author ? { author: asString(resolveFieldPath(hop.record, sourceConfig.author)) } : {},
4780
+ ...sourceConfig.at ? { at: asString(resolveFieldPath(hop.record, sourceConfig.at)) } : {},
4781
+ ...sourceConfig.title ? { title: asString(resolveFieldPath(hop.record, sourceConfig.title)) } : {},
4782
+ ...sourceConfig.body ? { body: asBody(resolveFieldPath(hop.record, sourceConfig.body)) } : {}
4783
+ });
4784
+ currentId = sourceConfig.chain ? asString(getByDottedPath(hop.record, sourceConfig.chain.field)) : void 0;
4785
+ }
4786
+ }
4787
+ return { result: { status: "ok", ...base, chain }, keys: primary.keys };
4788
+ }
4789
+ function asString(v) {
4790
+ return typeof v === "string" && v.length > 0 ? v : void 0;
4791
+ }
4792
+ var REQUEST_BODY_CAP = 2e4;
4793
+ function asBody(v) {
4794
+ const s = asString(v);
4795
+ return s === void 0 ? void 0 : s.length > REQUEST_BODY_CAP ? s.slice(0, REQUEST_BODY_CAP) : s;
4796
+ }
4797
+ async function resolveRecordKeyChain(integrationManager2, connectorId, manifest, recordKeyName, idValue, stepsLeft) {
4798
+ if (stepsLeft <= 0) return [];
4799
+ const recordKey = manifest.context?.recordKeys?.[recordKeyName];
4800
+ if (!recordKey) return [];
4801
+ const args = substitute(recordKey.read.args, { id: idValue });
4802
+ const read = await readContextRecord(integrationManager2, connectorId, recordKey.read.tool, args);
4803
+ if (read.status !== "ok") return [];
4804
+ const out = [];
4805
+ const emailField = recordKey.keys?.email;
4806
+ if (emailField) {
4807
+ const v = resolveFieldPath(read.record, emailField);
4808
+ if (typeof v === "string" && v) out.push(...deriveIdentityKeysFromEmail(v));
4809
+ }
4810
+ if (recordKey.next) {
4811
+ const nextId = asString(getByDottedPath(read.record, recordKey.next.field));
4812
+ if (nextId) {
4813
+ out.push(
4814
+ ...await resolveRecordKeyChain(
4815
+ integrationManager2,
4816
+ connectorId,
4817
+ manifest,
4818
+ recordKey.next.recordKey,
4819
+ nextId,
4820
+ stepsLeft - 1
4821
+ )
4822
+ );
4823
+ }
4824
+ }
4825
+ return out;
4826
+ }
4827
+ async function gatherContextKeysFromAction(integrationManager2, connectorId, contextKeys, toolArgs) {
4828
+ if (!contextKeys) return [];
4829
+ const manifest = getManifest(connectorId);
4830
+ const out = [];
4831
+ for (const [name, argName] of Object.entries(contextKeys)) {
4832
+ const raw = toolArgs[argName];
4833
+ if (raw === void 0 || raw === null) continue;
4834
+ if (name === "email") {
4835
+ out.push(...deriveIdentityKeysFromValue(raw));
4836
+ continue;
4837
+ }
4838
+ if (!manifest?.context?.recordKeys?.[name] || typeof raw !== "string" || !raw) continue;
4839
+ out.push(...await resolveRecordKeyChain(integrationManager2, connectorId, manifest, name, raw, MAX_RECORD_KEY_STEPS));
4840
+ }
4841
+ return out;
4842
+ }
4843
+ function mapHistoryItem(raw, mapping) {
4844
+ return {
4845
+ // `kind` is a literal label, copied as-is — never a path (doc §8.1).
4846
+ ...mapping.kind ? { kind: mapping.kind } : {},
4847
+ ...mapping.title ? { title: asString(resolveFieldPath(raw, mapping.title)) } : {},
4848
+ ...mapping.body ? { body: asBody(resolveFieldPath(raw, mapping.body)) } : {},
4849
+ ...mapping.at ? { at: asString(resolveFieldPath(raw, mapping.at)) } : {},
4850
+ ...mapping.author ? { author: asString(resolveFieldPath(raw, mapping.author)) } : {},
4851
+ ...mapping.type ? { type: asString(resolveFieldPath(raw, mapping.type)) } : {}
4852
+ };
4853
+ }
4854
+ function withinWindow(at, cutoffMs) {
4855
+ if (!at) return true;
4856
+ const ms = Date.parse(at);
4857
+ if (Number.isNaN(ms)) return true;
4858
+ return ms >= cutoffMs;
4859
+ }
4860
+ function chooseHistoryKey(byKey, keys) {
4861
+ const order = Array.isArray(byKey) ? byKey : [byKey];
4862
+ for (const type of order) {
4863
+ const found = keys.find((k) => k.type === type);
4864
+ if (found) return found;
4865
+ }
4866
+ return void 0;
4867
+ }
4868
+ function resolveHistoryArgsTemplate(template, keyType) {
4869
+ const out = {};
4870
+ for (const [argName, token] of Object.entries(template)) {
4871
+ out[argName === "$keyType" ? keyType : argName] = token;
3903
4872
  }
3904
- const stale = String(current) !== String(approved);
3905
- return ok({
3906
- version: {
3907
- field: preview.version.field,
3908
- approved,
3909
- current,
3910
- stale,
3911
- ...stale ? { currentBody: read2.body } : {}
3912
- }
4873
+ return out;
4874
+ }
4875
+ async function readConnectorHistory(integrationManager2, connectorId, history, keys, executionJournal) {
4876
+ const key = chooseHistoryKey(history.byKey, keys);
4877
+ if (!key) return { status: "no_match" };
4878
+ const sinceMs = Date.now() - history.maxAgeDays * 24 * 60 * 60 * 1e3;
4879
+ const args = substitute(resolveHistoryArgsTemplate(history.read.args, key.type), {
4880
+ key: key.value,
4881
+ since: new Date(sinceMs).toISOString(),
4882
+ limit: history.limit
3913
4883
  });
4884
+ const read = await readContextRecord(integrationManager2, connectorId, history.read.tool, args);
4885
+ if (read.status !== "ok") return { status: "unreachable" };
4886
+ const rows = Array.isArray(read.record) ? read.record : [];
4887
+ const items = [];
4888
+ for (const row of rows.slice(0, history.limit)) {
4889
+ const item = mapHistoryItem(row, history.item);
4890
+ if (!withinWindow(item.at, sinceMs)) continue;
4891
+ if (history.item.ticket) {
4892
+ const ticketId = asString(resolveFieldPath(row, history.item.ticket));
4893
+ if (ticketId) {
4894
+ const entry = executionJournal.get(ticketId);
4895
+ if (entry?.state === "done") {
4896
+ item.aiWritten = { at: entry.finishedAt ? entry.finishedAt * 1e3 : void 0 };
4897
+ }
4898
+ }
4899
+ }
4900
+ items.push(item);
4901
+ }
4902
+ return { status: "ok", match: { connectorId, matchedKeyType: key.type, matchedKeyValue: key.value, items } };
4903
+ }
4904
+ async function buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds) {
4905
+ if (keys.length === 0) return { status: "no_customer_link" };
4906
+ const connectors = getAllManifests().filter((m) => m.context?.history && configuredConnectorIds.has(m.id));
4907
+ if (connectors.length === 0) return { status: "no_match" };
4908
+ const matches = [];
4909
+ const unreachable = [];
4910
+ let anyMatchedType = false;
4911
+ for (const manifest of connectors) {
4912
+ const history = manifest.context.history;
4913
+ const outcome = await readConnectorHistory(integrationManager2, manifest.id, history, keys, executionJournal);
4914
+ if (outcome.status === "no_match") continue;
4915
+ anyMatchedType = true;
4916
+ if (outcome.status === "unreachable") {
4917
+ unreachable.push(manifest.id);
4918
+ continue;
4919
+ }
4920
+ matches.push(outcome.match);
4921
+ }
4922
+ if (!anyMatchedType) return { status: "no_match" };
4923
+ if (matches.length === 0 && unreachable.length > 0) return { status: "unreachable", connectorIds: unreachable };
4924
+ return { status: "ok", matches, unreachableConnectorIds: unreachable };
4925
+ }
4926
+ async function buildRequestContext(integrationManager2, executionJournal, actionConnectorId, toolArgs, actionGating, configuredConnectorIds) {
4927
+ const { result: request, keys: sourceKeys } = await resolveRequestContext(
4928
+ integrationManager2,
4929
+ toolArgs,
4930
+ actionConnectorId,
4931
+ actionGating?.sourceFromArg,
4932
+ configuredConnectorIds
4933
+ );
4934
+ const actionKeys = await gatherContextKeysFromAction(
4935
+ integrationManager2,
4936
+ actionConnectorId,
4937
+ actionGating?.contextKeys,
4938
+ toolArgs
4939
+ );
4940
+ const keys = dedupeKeys([...actionKeys, ...sourceKeys]);
4941
+ const history = await buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds);
4942
+ return { request, history };
3914
4943
  }
3915
4944
 
3916
4945
  // src/lib/ticket-verify.ts
@@ -3981,8 +5010,8 @@ async function verifyTicket(cache, receipt, expected) {
3981
5010
  if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
3982
5011
  throw new TicketBindingMismatchError("Ticket carries no valid timestamp \u2014 refusing to execute.");
3983
5012
  }
3984
- const nowSec3 = Math.floor(Date.now() / 1e3);
3985
- const ageSec = nowSec3 - timestamp;
5013
+ const nowSec4 = Math.floor(Date.now() / 1e3);
5014
+ const ageSec = nowSec4 - timestamp;
3986
5015
  if (ageSec > TICKET_MAX_AGE_SECONDS) {
3987
5016
  throw new TicketBindingMismatchError(
3988
5017
  `Ticket is ${ageSec}s old, older than the ${TICKET_MAX_AGE_SECONDS}s freshness window \u2014 refusing to execute.`
@@ -4116,191 +5145,34 @@ function normalizeIncomingArgs(tool, args) {
4116
5145
  return out ?? args;
4117
5146
  }
4118
5147
 
4119
- // src/lib/scope-specificity.ts
4120
- function tokenSet(value) {
4121
- if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
4122
- return new Set(
4123
- String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
4124
- );
4125
- }
4126
- function subsetOrEqual(a, b) {
4127
- if (b.size === 0) return true;
4128
- if (a.size === 0) return false;
4129
- for (const x of a) if (!b.has(x)) return false;
4130
- return true;
4131
- }
4132
- function atLeastAsSpecific(keys, a, b) {
4133
- for (const k of keys) {
4134
- if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
4135
- }
4136
- return true;
4137
- }
4138
- function strictlyMoreSpecific(keys, a, b) {
4139
- return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
4140
- }
4141
- function selectAuthorization(contextKeys, passers) {
4142
- if (passers.length === 1) {
4143
- return { chosen: passers[0], superseded: [], reason: "sole" };
4144
- }
4145
- const dominators = passers.filter(
4146
- (a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
4147
- );
4148
- if (dominators.length === 1) {
4149
- const chosen2 = dominators[0];
4150
- return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
4151
- }
4152
- const approvers = passers.filter((p) => p.requiresApproval);
4153
- const pool = approvers.length > 0 ? approvers : passers;
4154
- const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
4155
- return {
4156
- chosen,
4157
- superseded: passers.filter((p) => p !== chosen),
4158
- reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
4159
- };
4160
- }
4161
-
4162
- // src/lib/read-gate.ts
4163
- function boundsSatisfyReadGate(bounds, gate) {
4164
- if (!gate.boundField) return true;
4165
- const actual = bounds?.[gate.boundField];
4166
- return actual !== void 0 && String(actual) === gate.requiredValue;
4167
- }
4168
- function readToolIsGoverned(g) {
4169
- if (!g) return false;
4170
- return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
5148
+ // src/lib/numeric-execution.ts
5149
+ import { getProfile as getProfile3 } from "@hap/core";
5150
+ function parseNumericString(value) {
5151
+ if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
5152
+ const parsed = Number(value);
5153
+ return Number.isFinite(parsed) ? parsed : void 0;
4171
5154
  }
4172
- function allowedResources(contexts, boundField) {
5155
+ function declaredNumericFields(profile) {
4173
5156
  const out = /* @__PURE__ */ new Set();
4174
- for (const ctx of contexts) {
4175
- for (const v of tokenSet(ctx?.[boundField])) out.add(v);
4176
- }
4177
- return out;
4178
- }
4179
- function deniedResources(requested, allowed) {
4180
- return requested.filter((r) => !allowed.has(r));
4181
- }
4182
- function filterItemsByResource(items, idPath, allowed) {
4183
- return items.filter((it) => {
4184
- const id = getByDottedPath(it, idPath);
4185
- return typeof id === "string" && allowed.has(id);
4186
- });
4187
- }
4188
- function hasBlockedValue(item, valuesPath, blocked) {
4189
- const v = getByDottedPath(item, valuesPath);
4190
- const arr = Array.isArray(v) ? v : v != null ? [v] : [];
4191
- return arr.some((x) => typeof x === "string" && blocked.has(x));
4192
- }
4193
- var MS_PER_DAY = 24 * 60 * 60 * 1e3;
4194
- function parseMessageTimestamp(value) {
4195
- if (typeof value === "number" && Number.isFinite(value)) return value;
4196
- if (typeof value === "string" && value.trim() !== "") {
4197
- const n = Number(value);
4198
- if (Number.isFinite(n)) return n;
4199
- const iso = Date.parse(value);
4200
- if (Number.isFinite(iso)) return iso;
4201
- }
4202
- return null;
4203
- }
4204
- function ageInDays(timestampMs, nowMs) {
4205
- return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
4206
- }
4207
- function maxReadAgeDays(boundsList, field) {
4208
- let max = null;
4209
- for (const b of boundsList) {
4210
- const raw = b?.[field];
4211
- const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
4212
- if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
4213
- }
4214
- return max;
4215
- }
4216
- function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
4217
- if (timestampMs === null) return true;
4218
- return ageInDays(timestampMs, nowMs) > maxAgeDays;
4219
- }
4220
- var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
4221
- function parensBalanced(s) {
4222
- let depth = 0;
4223
- let inQuote = false;
4224
- for (const c of s) {
4225
- if (c === '"') {
4226
- inQuote = !inQuote;
4227
- continue;
4228
- }
4229
- if (inQuote) continue;
4230
- if (c === "(") depth++;
4231
- else if (c === ")" && --depth < 0) return false;
4232
- }
4233
- return !inQuote && depth === 0;
4234
- }
4235
- function composeReadQuery(agentQuery, clauses) {
4236
- const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
4237
- const base = (agentQuery ?? "").trim();
4238
- if (base === "") return { ok: true, query: injected.join(" ") };
4239
- if (!parensBalanced(base)) {
4240
- return { ok: false, reason: "the requested search query has unbalanced parentheses" };
4241
- }
4242
- if (TRAILING_OPERATOR.test(base)) {
4243
- return { ok: false, reason: "the requested search query ends in a dangling operator" };
4244
- }
4245
- if (injected.length === 0) return { ok: true, query: base };
4246
- return { ok: true, query: `(${base}) ${injected.join(" ")}` };
4247
- }
4248
- function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
4249
- if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
4250
- let re;
4251
- try {
4252
- re = new RegExp(conflictPattern, "gi");
4253
- } catch {
4254
- return null;
4255
- }
4256
- let worst = null;
4257
- for (const match of agentQuery.matchAll(re)) {
4258
- const days = Number(match[1]);
4259
- if (!Number.isFinite(days)) continue;
4260
- if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
4261
- }
4262
- return worst;
4263
- }
4264
- function resolveAgeBoundField(boundsSchema, ageExecField) {
4265
- const fields = boundsSchema?.fields;
4266
- if (!fields) return null;
5157
+ const fields = profile?.executionContextSchema?.fields;
5158
+ if (!fields) return out;
4267
5159
  for (const [name, def] of Object.entries(fields)) {
4268
- if (def?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
5160
+ if (def.source === "declared" && def.constraint?.type === "number") out.add(name);
4269
5161
  }
4270
- return null;
4271
- }
4272
- function getByDottedPath(obj, path) {
4273
- return path.split(".").reduce(
4274
- (o, k) => o == null ? void 0 : o[k],
4275
- obj
4276
- );
5162
+ return out;
4277
5163
  }
4278
- function firstParsableDate(obj, paths) {
4279
- for (const path of Array.isArray(paths) ? paths : [paths]) {
4280
- const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
4281
- if (parsed !== null) return parsed;
5164
+ function coerceDeclaredNumericFields(profileId, execution) {
5165
+ const fields = declaredNumericFields(getProfile3(profileId));
5166
+ for (const field of fields) {
5167
+ const value = execution[field];
5168
+ if (typeof value !== "string") continue;
5169
+ const parsed = parseNumericString(value);
5170
+ if (parsed !== void 0) execution[field] = parsed;
4282
5171
  }
4283
- return null;
4284
- }
4285
- function renderAgeConstraint(template, maxAgeDays, nowMs) {
4286
- const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
4287
- return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
4288
- }
4289
- function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
4290
- const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
4291
- if (format === "epoch_ms") return floorMs;
4292
- if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
4293
- return new Date(floorMs).toISOString();
4294
- }
4295
- function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
4296
- const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
4297
- const asked = parseMessageTimestamp(requested);
4298
- if (asked !== null && asked >= floorMs) return requested;
4299
- return ageFloorValue(maxAgeDays, nowMs, format);
4300
5172
  }
4301
5173
 
4302
5174
  // src/lib/tool-proxy.ts
4303
- import { getProfile as getProfile2, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
5175
+ import { getProfile as getProfile4, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
4304
5176
  import { readFile } from "fs/promises";
4305
5177
  import { extname } from "path";
4306
5178
  import { randomUUID } from "crypto";
@@ -4372,7 +5244,7 @@ function profileMatches(profileId, shortName2) {
4372
5244
  return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
4373
5245
  }
4374
5246
  function wireExecutionContext(profileId, execution) {
4375
- const profile = getProfile2(profileId);
5247
+ const profile = getProfile4(profileId);
4376
5248
  const allowed = /* @__PURE__ */ new Set(["action_type"]);
4377
5249
  const fields = profile?.boundsSchema?.fields;
4378
5250
  if (fields) {
@@ -4390,7 +5262,7 @@ function wireExecutionContext(profileId, execution) {
4390
5262
  return out;
4391
5263
  }
4392
5264
  function countBoundsFor(profileId, actionType) {
4393
- const fields = getProfile2(profileId)?.boundsSchema?.fields;
5265
+ const fields = getProfile4(profileId)?.boundsSchema?.fields;
4394
5266
  if (!fields) return null;
4395
5267
  return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
4396
5268
  const applies = boundActionTypes2(name, def);
@@ -4465,7 +5337,7 @@ function recordBlocked(state2, tool, rec) {
4465
5337
  function recordGatekeeperRefusal(state2, tool, refusals) {
4466
5338
  if (refusals.length === 0) return;
4467
5339
  const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
4468
- const scoped = pick.field ? Boolean(getProfile2(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
5340
+ const scoped = pick.field ? Boolean(getProfile4(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
4469
5341
  const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
4470
5342
  recordBlocked(state2, tool, {
4471
5343
  kind: scoped ? "scope" : "bound",
@@ -4565,7 +5437,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4565
5437
  };
4566
5438
  };
4567
5439
  }
4568
- const { profile, executionMapping, staticExecution, category } = tool.gating;
5440
+ const { profile, executionMapping, staticExecution, category, boundField: toolBoundField, requiredValue: toolRequiredValue } = tool.gating;
4569
5441
  const eligibleMandates = () => {
4570
5442
  const refusals = [];
4571
5443
  const matching = state2.getEnrichedAuthorizations().filter((a) => a.complete && profileMatches(a.profileId, profile)).filter((a) => {
@@ -4644,7 +5516,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4644
5516
  );
4645
5517
  }
4646
5518
  }
4647
- const readProfile = readAdapter ? getProfile2(matchingAuths[0].profileId) : void 0;
5519
+ const readProfile = readAdapter ? getProfile4(matchingAuths[0].profileId) : void 0;
4648
5520
  const boundsSchema = readProfile?.boundsSchema;
4649
5521
  const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
4650
5522
  const hasAgeDimension = Boolean(readAdapter?.ageField);
@@ -4789,6 +5661,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4789
5661
  }
4790
5662
  }
4791
5663
  }
5664
+ coerceDeclaredNumericFields(profile, execution);
4792
5665
  const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
4793
5666
  if (!declaredActionType) {
4794
5667
  recordBlocked(state2, tool, {
@@ -4807,7 +5680,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4807
5680
  };
4808
5681
  }
4809
5682
  {
4810
- const profileDef = getProfile2(profile);
5683
+ const profileDef = getProfile4(profile);
4811
5684
  const registry = profileDef?.boundsSchema?.actionTypes;
4812
5685
  if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
4813
5686
  recordBlocked(state2, tool, {
@@ -4845,6 +5718,25 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4845
5718
  const passers = [];
4846
5719
  const structuredRefusals = [];
4847
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
+ }
4848
5740
  const { result } = await state2.gatekeeper.verifyExecution(candidate.authorizationId, execution, {
4849
5741
  bounds: candidate.bounds,
4850
5742
  context: candidate.context
@@ -4892,7 +5784,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
4892
5784
  isError: true
4893
5785
  };
4894
5786
  }
4895
- const contextKeys = getProfile2(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
5787
+ const contextKeys = getProfile4(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
4896
5788
  return attemptWithCandidates(passers);
4897
5789
  async function attemptWithCandidates(candidates) {
4898
5790
  const selection = selectAuthorization(
@@ -5194,6 +6086,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
5194
6086
  let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
5195
6087
  if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
5196
6088
  outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
6089
+ outgoingArgs = stripSourceLink(outgoingArgs);
5197
6090
  if (receiptId) {
5198
6091
  const begun = state2.executionJournal.begin({
5199
6092
  ticketId: receiptId,
@@ -5270,7 +6163,8 @@ function agentVisibleAuthorizations(authorizations, integrationManager2) {
5270
6163
  // src/lib/agent-rules.ts
5271
6164
  var AGENT_RULES = [
5272
6165
  "Never open or operate the Suveren Gateway's web page \u2014 not in a browser you control, not with computer use. Never ask for, read or type the person's API key. Never approve a proposal: approvals are the person's alone.",
5273
- "Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`)."
6166
+ "Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`).",
6167
+ "When a tool offers an optional `_source` argument, set it to link the ONE record (e.g. an inbound email) you are acting on \u2014 just the reference ({connector, kind, id}), never a summary or quote; the gateway reads the record itself for the approver."
5274
6168
  ];
5275
6169
 
5276
6170
  // src/lib/mandate-brief.ts
@@ -5350,7 +6244,7 @@ function buildMandateBrief(opts) {
5350
6244
  const parts = [];
5351
6245
  if (boundsDesc) parts.push(boundsDesc);
5352
6246
  if (executionLog) {
5353
- const profile = getProfile3(auth.profileId) ?? getProfile3(shortName2);
6247
+ const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
5354
6248
  const consumption = getConsumptionState(auth, executionLog, profile);
5355
6249
  const compact = formatConsumptionCompact(consumption);
5356
6250
  if (compact) parts.push(compact);
@@ -5388,7 +6282,7 @@ function buildMandateBrief(opts) {
5388
6282
  }
5389
6283
 
5390
6284
  // src/tools/authorizations.ts
5391
- import { getProfile as getProfile4 } from "@hap/core";
6285
+ import { getProfile as getProfile6 } from "@hap/core";
5392
6286
  function shortProfileName2(profileId) {
5393
6287
  const withoutVersion = profileId.replace(/@.*$/, "");
5394
6288
  const parts = withoutVersion.split("/");
@@ -5506,7 +6400,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
5506
6400
  } catch {
5507
6401
  }
5508
6402
  const shortName2 = shortProfileName2(auth.profileId);
5509
- const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
6403
+ const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
5510
6404
  if (profile) {
5511
6405
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
5512
6406
  const consumptionText = formatConsumptionFull(consumption);
@@ -5566,7 +6460,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
5566
6460
  lines.push(" Mode: review (each action requires your approval)");
5567
6461
  }
5568
6462
  const shortName2 = shortProfileName2(auth.profileId);
5569
- const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
6463
+ const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
5570
6464
  if (profile) {
5571
6465
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
5572
6466
  const compact = formatConsumptionCompact(consumption);
@@ -5759,6 +6653,18 @@ async function decryptRejectionNote(by) {
5759
6653
  }
5760
6654
 
5761
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
+ }
5762
6668
  function buildSkippedProposalNote(proposalId) {
5763
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.`;
5764
6670
  }
@@ -6020,16 +6926,24 @@ async function executeCommitted(proposal, state2, integrationManager2) {
6020
6926
  outgoingArgs = attachTicketId(discovered, outgoingArgs, receiptId);
6021
6927
  outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
6022
6928
  }
6929
+ outgoingArgs = stripSourceLink(outgoingArgs);
6023
6930
  let result;
6024
6931
  try {
6025
6932
  result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
6026
6933
  } catch (err) {
6934
+ const msg = err instanceof Error ? err.message : String(err);
6027
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");
6028
6939
  throw err;
6029
6940
  }
6030
6941
  const resultText = result.content?.[0]?.text ?? JSON.stringify(result);
6031
6942
  if (result.isError) {
6032
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");
6033
6947
  return {
6034
6948
  text: `Proposal ${proposal.id} was approved, but ${integrationId} refused to run it \u2014 nothing was done.
6035
6949
  Reason: ${resultText}`,
@@ -6037,6 +6951,8 @@ Reason: ${resultText}`,
6037
6951
  };
6038
6952
  }
6039
6953
  state2.executionJournal.complete(receiptId, "done");
6954
+ recordExecutionOutcome(state2, proposal.id, receiptId, true, resultText, result.structuredContent);
6955
+ logExecutionOutcome(proposal.id, true);
6040
6956
  state2.executionLog.record({
6041
6957
  profileId: proposal.profileId,
6042
6958
  path: proposal.path,
@@ -6047,6 +6963,12 @@ Reason: ${resultText}`,
6047
6963
  Result: ${resultText}` };
6048
6964
  } catch (err) {
6049
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
+ }
6050
6972
  return {
6051
6973
  text: `Proposal ${proposal.id} receipt issued but tool execution failed: ${msg}`,
6052
6974
  isError: true
@@ -6055,11 +6977,22 @@ Result: ${resultText}` };
6055
6977
  }
6056
6978
  function localOutcomeOfExecuted(proposal, state2) {
6057
6979
  const entry = state2.executionJournal?.findByProposal?.(proposal.id);
6058
- if (!entry || entry.state === "done") return null;
6980
+ if (!entry) return null;
6059
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
+ }
6060
6991
  if (entry.state === "failed") {
6061
- const reasonSuffix = entry.detail ? `
6062
- Reason: ${entry.detail}` : "";
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}` : "";
6063
6996
  return {
6064
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}`,
6065
6998
  isError: true
@@ -6406,21 +7339,21 @@ function createMcpServer(state2, integrationManager2) {
6406
7339
  }
6407
7340
 
6408
7341
  // src/lib/agent-contact.ts
6409
- import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync16, renameSync as renameSync5, writeFileSync as writeFileSync12 } from "fs";
6410
- import { dirname as dirname12, join as join15 } from "path";
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";
6411
7344
  var FILE = "agent-contact.json";
6412
7345
  var REWRITE_AFTER_MS = 6e4;
6413
7346
  var AgentContactStore = class {
6414
7347
  path;
6415
7348
  current;
6416
7349
  constructor(dataDir2) {
6417
- this.path = join15(dataDir2, FILE);
7350
+ this.path = join16(dataDir2, FILE);
6418
7351
  this.current = this.load();
6419
7352
  }
6420
7353
  load() {
6421
- if (!existsSync15(this.path)) return null;
7354
+ if (!existsSync16(this.path)) return null;
6422
7355
  try {
6423
- const v = JSON.parse(readFileSync16(this.path, "utf8"));
7356
+ const v = JSON.parse(readFileSync17(this.path, "utf8"));
6424
7357
  if (typeof v.clientName !== "string" || typeof v.firstSeenAt !== "string" || typeof v.lastSeenAt !== "string") return null;
6425
7358
  return { clientName: v.clientName, clientVersion: v.clientVersion, firstSeenAt: v.firstSeenAt, lastSeenAt: v.lastSeenAt };
6426
7359
  } catch {
@@ -6443,9 +7376,9 @@ var AgentContactStore = class {
6443
7376
  };
6444
7377
  if (!due) return;
6445
7378
  try {
6446
- mkdirSync12(dirname12(this.path), { recursive: true });
7379
+ mkdirSync13(dirname13(this.path), { recursive: true });
6447
7380
  const tmp = `${this.path}.tmp`;
6448
- writeFileSync12(tmp, JSON.stringify(this.current, null, 2));
7381
+ writeFileSync13(tmp, JSON.stringify(this.current, null, 2));
6449
7382
  renameSync5(tmp, this.path);
6450
7383
  } catch (err) {
6451
7384
  console.error("[Suveren MCP] Could not save agent contact:", err);
@@ -6480,16 +7413,16 @@ function verifyGateContentHashes(content, auth) {
6480
7413
  }
6481
7414
 
6482
7415
  // src/lib/integration-registry.ts
6483
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync16 } from "fs";
6484
- import { homedir as homedir11 } from "os";
6485
- import { dirname as dirname13, join as join16 } from "path";
6486
- var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join16(homedir11(), ".suveren");
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");
6487
7420
  var IntegrationRegistry = class {
6488
7421
  integrations = /* @__PURE__ */ new Map();
6489
7422
  filePath;
6490
7423
  constructor(dataDir2) {
6491
7424
  const dir = dataDir2 ?? DEFAULT_DATA_DIR;
6492
- this.filePath = join16(dir, "integrations.json");
7425
+ this.filePath = join17(dir, "integrations.json");
6493
7426
  this.load();
6494
7427
  }
6495
7428
  // ─── CRUD ───────────────────────────────────────────────────────────────
@@ -6520,12 +7453,12 @@ var IntegrationRegistry = class {
6520
7453
  }
6521
7454
  // ─── Load / Save ────────────────────────────────────────────────────────
6522
7455
  load() {
6523
- if (!existsSync16(this.filePath)) {
6524
- mkdirSync13(dirname13(this.filePath), { recursive: true });
7456
+ if (!existsSync17(this.filePath)) {
7457
+ mkdirSync14(dirname14(this.filePath), { recursive: true });
6525
7458
  return;
6526
7459
  }
6527
7460
  try {
6528
- const raw = readFileSync17(this.filePath, "utf-8");
7461
+ const raw = readFileSync18(this.filePath, "utf-8");
6529
7462
  const data = JSON.parse(raw);
6530
7463
  const migrated = data.integrations.map((i) => {
6531
7464
  if ("toolGating" in i && !("profile" in i)) {
@@ -6554,15 +7487,15 @@ var IntegrationRegistry = class {
6554
7487
  version: 1,
6555
7488
  integrations: Array.from(this.integrations.values())
6556
7489
  };
6557
- mkdirSync13(dirname13(this.filePath), { recursive: true });
6558
- writeFileSync13(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
7490
+ mkdirSync14(dirname14(this.filePath), { recursive: true });
7491
+ writeFileSync14(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
6559
7492
  }
6560
7493
  };
6561
7494
 
6562
7495
  // src/lib/integration-manager.ts
6563
- import { homedir as homedir12 } from "os";
6564
- import { join as join17, delimiter } from "path";
6565
- import { existsSync as existsSync17, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync18, rmSync } from "fs";
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";
6566
7499
  import { execFile } from "child_process";
6567
7500
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6568
7501
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -6692,8 +7625,8 @@ function inheritedConnectorEnv(parent, passEnv = []) {
6692
7625
  }
6693
7626
 
6694
7627
  // src/lib/integration-manager.ts
6695
- var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join17(homedir12(), ".suveren");
6696
- var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join17(DEFAULT_DATA_DIR2, "integrations");
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");
6697
7630
  var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
6698
7631
  var installLock = Promise.resolve();
6699
7632
  function withInstallLock(task) {
@@ -6701,7 +7634,7 @@ function withInstallLock(task) {
6701
7634
  installLock = run.then(() => void 0, () => void 0);
6702
7635
  return run;
6703
7636
  }
6704
- var INTEGRATIONS_BIN = join17(INTEGRATIONS_DIR, "node_modules", ".bin");
7637
+ var INTEGRATIONS_BIN = join18(INTEGRATIONS_DIR, "node_modules", ".bin");
6705
7638
  function getIntegrationsBinDir() {
6706
7639
  return INTEGRATIONS_BIN;
6707
7640
  }
@@ -6710,12 +7643,12 @@ function buildPath() {
6710
7643
  return [INTEGRATIONS_BIN, base].join(delimiter);
6711
7644
  }
6712
7645
  function ensureIntegrationsDir() {
6713
- if (!existsSync17(INTEGRATIONS_DIR)) {
6714
- mkdirSync14(INTEGRATIONS_DIR, { recursive: true });
7646
+ if (!existsSync18(INTEGRATIONS_DIR)) {
7647
+ mkdirSync15(INTEGRATIONS_DIR, { recursive: true });
6715
7648
  }
6716
- const pkgPath = join17(INTEGRATIONS_DIR, "package.json");
6717
- if (!existsSync17(pkgPath)) {
6718
- writeFileSync14(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
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));
6719
7652
  }
6720
7653
  }
6721
7654
  function readAgeOf(config) {
@@ -6804,19 +7737,19 @@ var IntegrationManager = class {
6804
7737
  * exist, and treat anything else as not-installed (and reinstallable).
6805
7738
  */
6806
7739
  isUsableInstall(npmPackage) {
6807
- const pkgDir = join17(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
6808
- const pkgJson = join17(pkgDir, "package.json");
6809
- if (!existsSync17(pkgDir) || !existsSync17(pkgJson)) return false;
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;
6810
7743
  try {
6811
- const pkg = JSON.parse(readFileSync18(pkgJson, "utf8"));
7744
+ const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
6812
7745
  const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
6813
7746
  const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
6814
7747
  const binEntries = Object.entries(binMap);
6815
7748
  for (const [binName, target] of binEntries) {
6816
- if (!existsSync17(join17(pkgDir, target))) return false;
7749
+ if (!existsSync18(join18(pkgDir, target))) return false;
6817
7750
  if (!this.binShimExists(binName)) return false;
6818
7751
  }
6819
- if (binEntries.length === 0 && pkg.main && !existsSync17(join17(pkgDir, pkg.main))) return false;
7752
+ if (binEntries.length === 0 && pkg.main && !existsSync18(join18(pkgDir, pkg.main))) return false;
6820
7753
  return true;
6821
7754
  } catch {
6822
7755
  return false;
@@ -6831,9 +7764,9 @@ var IntegrationManager = class {
6831
7764
  */
6832
7765
  readInstalledVersion(npmPackage) {
6833
7766
  if (!this.isUsableInstall(npmPackage)) return null;
6834
- const pkgJson = join17(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
7767
+ const pkgJson = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
6835
7768
  try {
6836
- const pkg = JSON.parse(readFileSync18(pkgJson, "utf8"));
7769
+ const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
6837
7770
  return typeof pkg.version === "string" ? pkg.version : null;
6838
7771
  } catch {
6839
7772
  return null;
@@ -6847,7 +7780,7 @@ var IntegrationManager = class {
6847
7780
  binShimExists(binName) {
6848
7781
  if (!binName) return false;
6849
7782
  const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
6850
- return candidates.some((c) => existsSync17(join17(INTEGRATIONS_BIN, c)));
7783
+ return candidates.some((c) => existsSync18(join18(INTEGRATIONS_BIN, c)));
6851
7784
  }
6852
7785
  /**
6853
7786
  * The exact version THIS gateway's CURRENTLY LOADED manifest pins
@@ -6937,8 +7870,8 @@ var IntegrationManager = class {
6937
7870
  * nothing at all.
6938
7871
  */
6939
7872
  async installNow(npmPackage, exactVersion, cleanFirst = true) {
6940
- const pkgDir = join17(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
6941
- if (cleanFirst && existsSync17(pkgDir)) {
7873
+ const pkgDir = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
7874
+ if (cleanFirst && existsSync18(pkgDir)) {
6942
7875
  console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
6943
7876
  try {
6944
7877
  rmSync(pkgDir, { recursive: true, force: true });
@@ -7118,19 +8051,19 @@ var IntegrationManager = class {
7118
8051
  const profileGating = config.toolGating ?? null;
7119
8052
  const tools = (toolsResult.tools ?? []).map((tool) => {
7120
8053
  const gating = this.resolveToolGating(config.profile, profileGating, tool.name);
8054
+ let inputSchema = withoutBlockedArgs(
8055
+ tool.inputSchema ?? {},
8056
+ gating?.blockedArgs
8057
+ );
8058
+ if (gating && gating.category !== "read" && gating.category !== "disabled") {
8059
+ inputSchema = withSourceLink(inputSchema);
8060
+ }
7121
8061
  return {
7122
8062
  originalName: tool.name,
7123
8063
  namespacedName: `${config.id}__${tool.name}`,
7124
8064
  integrationId: config.id,
7125
8065
  description: tool.description ?? "",
7126
- // A downstream schema otherwise reaches the agent verbatim, so a
7127
- // connector can offer a control-bypassing argument and we would pass it
7128
- // straight on. Anything the manifest blocks is removed here, before the
7129
- // agent ever sees it exists.
7130
- inputSchema: withoutBlockedArgs(
7131
- tool.inputSchema ?? {},
7132
- gating?.blockedArgs
7133
- ),
8066
+ inputSchema,
7134
8067
  outputSchema: tool.outputSchema,
7135
8068
  gating
7136
8069
  };
@@ -7525,7 +8458,20 @@ var IntegrationManager = class {
7525
8458
  argNormalization: ext.argNormalization,
7526
8459
  hideUnlessAuthorized: ext.hideUnlessAuthorized,
7527
8460
  simulationOnly: ext.simulationOnly,
7528
- approvalView: ext.approvalView
8461
+ approvalView: ext.approvalView,
8462
+ contextKeys: ext.contextKeys,
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
7529
8475
  };
7530
8476
  }
7531
8477
  return {
@@ -7656,7 +8602,7 @@ async function buildTicketDetails(archive, ticketIds) {
7656
8602
  }
7657
8603
 
7658
8604
  // src/lib/report/window.ts
7659
- import { getProfile as getProfile5 } from "@hap/core";
8605
+ import { getProfile as getProfile7 } from "@hap/core";
7660
8606
  var REPORT_AGE_FIELD = "read_age_days";
7661
8607
  var REPORTING_PROFILE = "reporting";
7662
8608
  var END_SKEW_SECONDS = 120;
@@ -7670,7 +8616,7 @@ function windowLabel(start, days, loadedAt) {
7670
8616
  if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
7671
8617
  return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
7672
8618
  }
7673
- var defaultLookup = (id) => getProfile5(id);
8619
+ var defaultLookup = (id) => getProfile7(id);
7674
8620
  function reportingMandateRefusal(m, lookup = defaultLookup) {
7675
8621
  const schema = lookup(m.profileId)?.boundsSchema;
7676
8622
  const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
@@ -7880,28 +8826,28 @@ function agentAccess(token, opensSession) {
7880
8826
  }
7881
8827
 
7882
8828
  // src/lib/profile-loader.ts
7883
- import { readFileSync as readFileSync19, existsSync as existsSync18 } from "fs";
7884
- import { join as join18, resolve as resolve2 } from "path";
8829
+ import { readFileSync as readFileSync20, existsSync as existsSync19 } from "fs";
8830
+ import { join as join19, resolve as resolve2 } from "path";
7885
8831
  import { registerProfile } from "@hap/core";
7886
8832
  function loadProfiles(profilesDir) {
7887
- const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join18(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
7888
- const indexPath = join18(dir, "index.json");
7889
- if (!existsSync18(indexPath)) {
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)) {
7890
8836
  warnNoProfiles(`No index.json at ${indexPath}`);
7891
8837
  return 0;
7892
8838
  }
7893
8839
  let index;
7894
8840
  try {
7895
- index = JSON.parse(readFileSync19(indexPath, "utf-8"));
8841
+ index = JSON.parse(readFileSync20(indexPath, "utf-8"));
7896
8842
  } catch (err) {
7897
8843
  console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
7898
8844
  return 0;
7899
8845
  }
7900
8846
  let loaded = 0;
7901
8847
  for (const [profileId, relativePath] of Object.entries(index.profiles)) {
7902
- const profilePath = join18(dir, relativePath);
8848
+ const profilePath = join19(dir, relativePath);
7903
8849
  try {
7904
- const profile = JSON.parse(readFileSync19(profilePath, "utf-8"));
8850
+ const profile = JSON.parse(readFileSync20(profilePath, "utf-8"));
7905
8851
  registerProfile(profileId, profile);
7906
8852
  const shortName2 = profileId.split("/").pop()?.replace(/@.*$/, "");
7907
8853
  if (shortName2 && shortName2 !== profileId) {
@@ -8242,20 +9188,20 @@ async function controlPlaneMandate(dryRun, request) {
8242
9188
  }
8243
9189
 
8244
9190
  // src/lib/guides.ts
8245
- import { existsSync as existsSync19, readdirSync, readFileSync as readFileSync20 } from "fs";
8246
- import { dirname as dirname14, join as join19 } from "path";
9191
+ import { existsSync as existsSync20, readdirSync, readFileSync as readFileSync21 } from "fs";
9192
+ import { dirname as dirname15, join as join20 } from "path";
8247
9193
  var FILE_RE = /^(?:(\d+)-)?([a-z0-9][a-z0-9-]*)\.md$/;
8248
9194
  function builtinGuidesDir() {
8249
9195
  if (process.env.SUVEREN_BUILTIN_GUIDES_DIR) return process.env.SUVEREN_BUILTIN_GUIDES_DIR;
8250
9196
  let dir = import.meta.dirname ?? __dirname;
8251
9197
  for (let i = 0; i < 8; i++) {
8252
- const candidate = join19(dir, "content", "guides");
8253
- if (existsSync19(candidate)) return candidate;
8254
- const parent = dirname14(dir);
9198
+ const candidate = join20(dir, "content", "guides");
9199
+ if (existsSync20(candidate)) return candidate;
9200
+ const parent = dirname15(dir);
8255
9201
  if (parent === dir) break;
8256
9202
  dir = parent;
8257
9203
  }
8258
- return join19(import.meta.dirname ?? __dirname, "content", "guides");
9204
+ return join20(import.meta.dirname ?? __dirname, "content", "guides");
8259
9205
  }
8260
9206
  function parse(body) {
8261
9207
  const lines = body.split(/\r?\n/);
@@ -8266,14 +9212,14 @@ function parse(body) {
8266
9212
  return { title, summary };
8267
9213
  }
8268
9214
  function readDir(dir, source) {
8269
- if (!dir || !existsSync19(dir)) return [];
9215
+ if (!dir || !existsSync20(dir)) return [];
8270
9216
  const out = [];
8271
9217
  for (const file of readdirSync(dir)) {
8272
9218
  const m = FILE_RE.exec(file);
8273
9219
  if (!m) continue;
8274
9220
  let body;
8275
9221
  try {
8276
- body = readFileSync20(join19(dir, file), "utf8");
9222
+ body = readFileSync21(join20(dir, file), "utf8");
8277
9223
  } catch {
8278
9224
  continue;
8279
9225
  }
@@ -8408,7 +9354,7 @@ function unifiedLineDiff(before, after) {
8408
9354
  }
8409
9355
 
8410
9356
  // src/lib/builtins/setup.ts
8411
- import { getProfile as getProfile6 } from "@hap/core";
9357
+ import { getProfile as getProfile8 } from "@hap/core";
8412
9358
  var DELEGATION_PROFILE = "delegation";
8413
9359
  var DELEGATION_INTENT_HINT = [
8414
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.",
@@ -8460,7 +9406,7 @@ function delegationItselfRefusal(args) {
8460
9406
  }
8461
9407
  function delegationMandateRefusal(auth) {
8462
9408
  if (isSimulationMode()) return void 0;
8463
- const profile = getProfile6(auth.profileId);
9409
+ const profile = getProfile8(auth.profileId);
8464
9410
  if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
8465
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.`;
8466
9412
  }
@@ -8518,7 +9464,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
8518
9464
  );
8519
9465
  const first = governing[0];
8520
9466
  if (first) {
8521
- const delegationProfile = getProfile6(first.profileId);
9467
+ const delegationProfile = getProfile8(first.profileId);
8522
9468
  const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
8523
9469
  if (entry && entry.limit !== null) {
8524
9470
  checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
@@ -8528,7 +9474,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
8528
9474
  }
8529
9475
  function resolveTargetProfile(input) {
8530
9476
  if (typeof input !== "string" || !input.trim()) return void 0;
8531
- const profile = getProfile6(input.trim());
9477
+ const profile = getProfile8(input.trim());
8532
9478
  if (!profile) return void 0;
8533
9479
  return { profile, shortName: shortName(profile.id) };
8534
9480
  }
@@ -8679,7 +9625,7 @@ Read each topic with get_guide(topic) before doing that step.`);
8679
9625
  return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
8680
9626
  }
8681
9627
  const body = fillGenerated(guide.body.trim(), {
8682
- limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile6(s.profile) }))),
9628
+ limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile8(s.profile) }))),
8683
9629
  package: () => packageSection(
8684
9630
  deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
8685
9631
  )
@@ -8821,8 +9767,8 @@ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
8821
9767
  import { decodeMandateBlob as decodeMandateBlob3 } from "@hap/core";
8822
9768
 
8823
9769
  // src/lib/committed-executor.ts
8824
- import { existsSync as existsSync20, mkdirSync as mkdirSync15, readFileSync as readFileSync21, unlinkSync as unlinkSync7, writeFileSync as writeFileSync15 } from "fs";
8825
- import { dirname as dirname15, join as join20 } from "path";
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";
8826
9772
  function describeAutoExecutionLog(proposalId, tool, result) {
8827
9773
  if (result.skipped) return null;
8828
9774
  return result.isError ? `[Suveren MCP] Auto-exec proposal ${proposalId}: ${result.text}` : `[Suveren MCP] Auto-executed proposal ${proposalId}: ${tool}`;
@@ -8854,7 +9800,7 @@ var ExecutorLock = class {
8854
9800
  path;
8855
9801
  held = false;
8856
9802
  constructor(dataDir2) {
8857
- this.path = join20(dataDir2, "executor.lock");
9803
+ this.path = join21(dataDir2, "executor.lock");
8858
9804
  }
8859
9805
  /** Try to take the lock. Returns the holder's pid when someone else has it. */
8860
9806
  acquire() {
@@ -8862,9 +9808,9 @@ var ExecutorLock = class {
8862
9808
  if (current && current.pid !== process.pid && isAlive(current.pid)) {
8863
9809
  return { held: false, holderPid: current.pid };
8864
9810
  }
8865
- mkdirSync15(dirname15(this.path), { recursive: true, mode: 448 });
9811
+ mkdirSync16(dirname16(this.path), { recursive: true, mode: 448 });
8866
9812
  const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
8867
- writeFileSync15(this.path, JSON.stringify(lock), { mode: 384 });
9813
+ writeFileSync16(this.path, JSON.stringify(lock), { mode: 384 });
8868
9814
  this.held = true;
8869
9815
  return { held: true };
8870
9816
  }
@@ -8880,15 +9826,15 @@ var ExecutorLock = class {
8880
9826
  const current = this.read();
8881
9827
  if (current?.pid === process.pid) {
8882
9828
  try {
8883
- unlinkSync7(this.path);
9829
+ unlinkSync8(this.path);
8884
9830
  } catch {
8885
9831
  }
8886
9832
  }
8887
9833
  }
8888
9834
  read() {
8889
- if (!existsSync20(this.path)) return null;
9835
+ if (!existsSync21(this.path)) return null;
8890
9836
  try {
8891
- const parsed = JSON.parse(readFileSync21(this.path, "utf8"));
9837
+ const parsed = JSON.parse(readFileSync22(this.path, "utf8"));
8892
9838
  return typeof parsed?.pid === "number" ? parsed : null;
8893
9839
  } catch {
8894
9840
  return null;
@@ -8905,20 +9851,20 @@ function isAlive(pid) {
8905
9851
  }
8906
9852
 
8907
9853
  // bin/http.ts
8908
- import { homedir as homedir13 } from "os";
8909
- import { join as join22 } from "path";
9854
+ import { homedir as homedir14 } from "os";
9855
+ import { join as join23 } from "path";
8910
9856
 
8911
9857
  // src/lib/mcp-as-tracker.ts
8912
- import { existsSync as existsSync21, mkdirSync as mkdirSync16, readFileSync as readFileSync22, writeFileSync as writeFileSync16 } from "fs";
8913
- import { dirname as dirname16, join as join21 } from "path";
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";
8914
9860
  function trackerPath(dataDir2) {
8915
- return join21(dataDir2, "mcp-as-pairing.json");
9861
+ return join22(dataDir2, "mcp-as-pairing.json");
8916
9862
  }
8917
9863
  function readMcpPairedAsUrl(dataDir2) {
8918
9864
  const path = trackerPath(dataDir2);
8919
- if (!existsSync21(path)) return null;
9865
+ if (!existsSync22(path)) return null;
8920
9866
  try {
8921
- const data = JSON.parse(readFileSync22(path, "utf-8"));
9867
+ const data = JSON.parse(readFileSync23(path, "utf-8"));
8922
9868
  return typeof data.asUrl === "string" ? data.asUrl : null;
8923
9869
  } catch {
8924
9870
  return null;
@@ -8926,12 +9872,12 @@ function readMcpPairedAsUrl(dataDir2) {
8926
9872
  }
8927
9873
  function writeMcpPairedAsUrl(dataDir2, asUrl) {
8928
9874
  const path = trackerPath(dataDir2);
8929
- mkdirSync16(dirname16(path), { recursive: true, mode: 448 });
8930
- writeFileSync16(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
9875
+ mkdirSync17(dirname17(path), { recursive: true, mode: 448 });
9876
+ writeFileSync17(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
8931
9877
  }
8932
9878
 
8933
9879
  // bin/http.ts
8934
- var dataDir = process.env.SUVEREN_DATA_DIR ?? join22(homedir13(), ".suveren");
9880
+ var dataDir = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
8935
9881
  var spUrl = resolveAsUrl(dataDir);
8936
9882
  validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
8937
9883
  var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
@@ -9017,7 +9963,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
9017
9963
  state.receiptArchive.setVaultKey(key);
9018
9964
  state.reportStore.setVaultKey(key);
9019
9965
  state.outcomeOutbox.setVaultKey(key);
9020
- console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive + report store + outcome outbox encryption active");
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");
9021
9968
  }
9022
9969
  if (apiKey) {
9023
9970
  state.spClient.setApiKey(apiKey);
@@ -9166,6 +10113,32 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
9166
10113
  });
9167
10114
  res.json(result);
9168
10115
  });
10116
+ app.post("/internal/request-context", internalOnly, async (req, res) => {
10117
+ const { tool, toolArgs } = req.body;
10118
+ if (!tool || typeof toolArgs !== "object" || toolArgs === null) {
10119
+ res.status(400).json({ error: "Missing tool or toolArgs" });
10120
+ return;
10121
+ }
10122
+ const sep = tool.indexOf("__");
10123
+ if (sep < 0) {
10124
+ res.status(400).json({ error: `Invalid tool name: ${tool}` });
10125
+ return;
10126
+ }
10127
+ const integrationId = tool.slice(0, sep);
10128
+ const toolName = tool.slice(sep + 2);
10129
+ const manifest = getManifest(integrationId);
10130
+ const actionGating = manifest?.toolGating?.overrides?.[toolName];
10131
+ const configuredConnectorIds = new Set(integrationRegistry.getAll().map((c) => c.id));
10132
+ const result = await buildRequestContext(
10133
+ integrationManager,
10134
+ state.executionJournal,
10135
+ integrationId,
10136
+ toolArgs,
10137
+ actionGating ?? void 0,
10138
+ configuredConnectorIds
10139
+ );
10140
+ res.json(result);
10141
+ });
9169
10142
  app.post("/internal/add-integration", internalOnly, async (req, res) => {
9170
10143
  try {
9171
10144
  const config = req.body;
@@ -9659,7 +10632,7 @@ app.listen(port, bindHost, () => {
9659
10632
  (proposal) => executeCommitted(proposal, state, integrationManager)
9660
10633
  );
9661
10634
  installCommittedExecutor(committedExecutor);
9662
- const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join22(homedir13(), ".suveren");
10635
+ const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
9663
10636
  const executorLock = new ExecutorLock(dataDir2);
9664
10637
  const lock = executorLock.acquire();
9665
10638
  if (!lock.held) {