@suveren/gateway 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/content/integrations/gmail.json +13 -2
  2. package/content/integrations/mail.json +12 -2
  3. package/content/integrations/mollie.json +10 -0
  4. package/dist/control-plane/index.mjs +125 -37
  5. package/dist/mcp-server/http.mjs +772 -420
  6. package/dist/ui/assets/index-C2BnlNib.js +123 -0
  7. package/dist/ui/assets/{index-DUIlMVlP.css → index-DPgewsLG.css} +1 -1
  8. package/dist/ui/index.html +2 -2
  9. package/node_modules/qs/.github/THREAT_MODEL.md +4 -2
  10. package/node_modules/qs/CHANGELOG.md +38 -14
  11. package/node_modules/qs/README.md +31 -3
  12. package/node_modules/qs/dist/qs.js +20 -20
  13. package/node_modules/qs/lib/parse.js +94 -4
  14. package/node_modules/qs/lib/stringify.js +4 -4
  15. package/node_modules/qs/lib/utils.js +130 -6
  16. package/node_modules/qs/package.json +4 -4
  17. package/node_modules/qs/test/parse.js +174 -0
  18. package/node_modules/qs/test/stringify.js +263 -33
  19. package/node_modules/qs/test/utils.js +184 -0
  20. package/node_modules/sanitize-html/index.js +47 -10
  21. package/node_modules/sanitize-html/package.json +2 -3
  22. package/package.json +1 -1
  23. package/dist/ui/assets/index-C3vTF2Ye.js +0 -123
  24. package/node_modules/deepmerge/.editorconfig +0 -7
  25. package/node_modules/deepmerge/.eslintcache +0 -1
  26. package/node_modules/deepmerge/changelog.md +0 -167
  27. package/node_modules/deepmerge/dist/cjs.js +0 -133
  28. package/node_modules/deepmerge/dist/umd.js +0 -139
  29. package/node_modules/deepmerge/index.d.ts +0 -20
  30. package/node_modules/deepmerge/index.js +0 -106
  31. package/node_modules/deepmerge/license.txt +0 -21
  32. package/node_modules/deepmerge/package.json +0 -42
  33. package/node_modules/deepmerge/readme.md +0 -264
  34. package/node_modules/deepmerge/rollup.config.js +0 -22
@@ -145,6 +145,8 @@
145
145
  },
146
146
  "list_messages": {
147
147
  "category": "read",
148
+ "boundField": "read_access",
149
+ "requiredValue": "unlimited",
148
150
  "read": {
149
151
  "ageField": "read_age_days",
150
152
  "queryArg": "q",
@@ -158,6 +160,8 @@
158
160
  },
159
161
  "get_message": {
160
162
  "category": "read",
163
+ "boundField": "read_access",
164
+ "requiredValue": "unlimited",
161
165
  "read": {
162
166
  "ageField": "read_age_days",
163
167
  "resultDatePath": "internalDate",
@@ -175,6 +179,8 @@
175
179
  },
176
180
  "list_drafts": {
177
181
  "category": "read",
182
+ "boundField": "read_access",
183
+ "requiredValue": "unlimited",
178
184
  "read": {
179
185
  "ageField": "read_age_days",
180
186
  "queryArg": "q",
@@ -187,6 +193,8 @@
187
193
  },
188
194
  "get_draft": {
189
195
  "category": "read",
196
+ "boundField": "read_access",
197
+ "requiredValue": "unlimited",
190
198
  "read": {
191
199
  "ageField": "read_age_days",
192
200
  "resultDatePath": "message.internalDate",
@@ -207,8 +215,11 @@
207
215
  },
208
216
  "get_attachment": {
209
217
  "category": "read",
210
- "readGovernance": "none",
211
- "readGovernanceReason": "Transitively age-gated: an attachmentId is only obtainable from get_message, which enforces the read-age window. No independently addressable content."
218
+ "boundField": "read_access",
219
+ "requiredValue": "unlimited",
220
+ "read": {
221
+ "ageField": "read_age_days"
222
+ }
212
223
  },
213
224
  "list_labels": {
214
225
  "category": "read",
@@ -7,6 +7,9 @@
7
7
  "profile": "email",
8
8
  "npmPackage": "@humanagencyp/email-mcp",
9
9
  "npmVersion": "0.3.0",
10
+ "readPolicy": {
11
+ "defaultAgeDays": 30
12
+ },
10
13
  "mcp": {
11
14
  "command": "email-mcp",
12
15
  "args": []
@@ -61,12 +64,19 @@
61
64
  "list_messages": {
62
65
  "category": "read",
63
66
  "boundField": "read_access",
64
- "requiredValue": "unlimited"
67
+ "requiredValue": "unlimited",
68
+ "read": {
69
+ "ageField": "read_age_days"
70
+ }
65
71
  },
66
72
  "get_message": {
67
73
  "category": "read",
68
74
  "boundField": "read_access",
69
- "requiredValue": "unlimited"
75
+ "requiredValue": "unlimited",
76
+ "read": {
77
+ "ageField": "read_age_days",
78
+ "resultDatePath": "received_at"
79
+ }
70
80
  },
71
81
  "send_message": {
72
82
  "executionMapping": {
@@ -129,6 +129,16 @@
129
129
  "staticExecution": {
130
130
  "action_type": "charge"
131
131
  }
132
+ },
133
+ "ListProfiles": {
134
+ "category": "read",
135
+ "readGovernance": "none",
136
+ "readGovernanceReason": "Returns the organisation's own payment profile configuration (names, websites, status) — no customer or payment data."
137
+ },
138
+ "GetOrganizationDetails": {
139
+ "category": "read",
140
+ "readGovernance": "none",
141
+ "readGovernanceReason": "Returns the connected Mollie organisation's own name/registration details — no customer or payment data."
132
142
  }
133
143
  }
134
144
  },
@@ -5,9 +5,9 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import { randomBytes as randomBytes3 } from "crypto";
8
- import { existsSync as existsSync13, readFileSync as readFileSync15, renameSync as renameSync3 } from "fs";
9
- import { dirname as dirname10, join as join15 } from "path";
10
- import { homedir as homedir8 } from "os";
8
+ import { existsSync as existsSync13, readFileSync as readFileSync16, renameSync as renameSync4 } from "fs";
9
+ import { dirname as dirname11, join as join16 } from "path";
10
+ import { homedir as homedir9 } from "os";
11
11
  import express from "express";
12
12
  import { createProxyMiddleware } from "http-proxy-middleware";
13
13
 
@@ -643,6 +643,13 @@ async function getSkippedCommitments() {
643
643
  if (!res.ok) throw new Error("Failed to fetch skipped commitments");
644
644
  return res.json();
645
645
  }
646
+ async function getMyPendingProposals() {
647
+ const res = await fetch(`${MCP_BASE}/internal/my-pending-proposals`, {
648
+ headers: internalHeaders()
649
+ });
650
+ if (!res.ok) throw new Error("Failed to fetch my pending proposals");
651
+ return res.json();
652
+ }
646
653
  async function getLocalEvidence() {
647
654
  const res = await fetch(`${MCP_BASE}/internal/evidence`, {
648
655
  headers: internalHeaders()
@@ -2258,6 +2265,7 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablis
2258
2265
  }
2259
2266
  vault2.wipe();
2260
2267
  await vault2.deriveAndSetKey(apiKey);
2268
+ vault2.setSpCookie(sessionCookie);
2261
2269
  }
2262
2270
  const sessionExpiresAt = typeof data.sessionExpiresAt === "number" ? data.sessionExpiresAt : null;
2263
2271
  vault2.setSessionExpiresAt(sessionExpiresAt);
@@ -3376,15 +3384,15 @@ import { promisify } from "util";
3376
3384
  var run = promisify(execFile);
3377
3385
  var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
3378
3386
  var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
3379
- function findCli(dirname11 = MODULE_DIR) {
3387
+ function findCli(dirname12 = MODULE_DIR) {
3380
3388
  const candidates = [
3381
3389
  // Bundled (the shipped layout): dist/control-plane → dist → root → bin
3382
- resolve(dirname11, "..", "..", "bin", "suveren-gateway.js"),
3390
+ resolve(dirname12, "..", "..", "bin", "suveren-gateway.js"),
3383
3391
  // Bundled, were the routes/ directory ever preserved.
3384
- resolve(dirname11, "..", "..", "..", "bin", "suveren-gateway.js"),
3392
+ resolve(dirname12, "..", "..", "..", "bin", "suveren-gateway.js"),
3385
3393
  // Dev, running from source: the repo's own bundle directory.
3386
- resolve(dirname11, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
3387
- resolve(dirname11, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
3394
+ resolve(dirname12, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
3395
+ resolve(dirname12, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
3388
3396
  // Deliberately NO process.cwd() fallback: it makes the result depend on
3389
3397
  // where the process happened to be launched from, so the same install
3390
3398
  // resolves differently between a shell and a login service — and it would
@@ -4622,10 +4630,62 @@ function createArchivedMandatesRouter(dataDir) {
4622
4630
  return router;
4623
4631
  }
4624
4632
 
4625
- // src/routes/internal-events.ts
4633
+ // src/routes/blocked-seen.ts
4626
4634
  import { Router as Router19 } from "express";
4635
+
4636
+ // src/lib/blocked-seen-store.ts
4637
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, renameSync as renameSync3 } from "fs";
4638
+ import { homedir as homedir8 } from "os";
4639
+ import { join as join14, dirname as dirname10 } from "path";
4640
+ function storePath2(dataDir) {
4641
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join14(homedir8(), ".suveren");
4642
+ return join14(dir, "blocked-seen.json");
4643
+ }
4644
+ function readLastSeenBlockedAt(dataDir) {
4645
+ try {
4646
+ const raw = JSON.parse(readFileSync14(storePath2(dataDir), "utf8"));
4647
+ return typeof raw.lastSeenBlockedAt === "number" && Number.isFinite(raw.lastSeenBlockedAt) ? raw.lastSeenBlockedAt : 0;
4648
+ } catch {
4649
+ return 0;
4650
+ }
4651
+ }
4652
+ function markBlockedSeen(ts, dataDir) {
4653
+ const safeTs = typeof ts === "number" && Number.isFinite(ts) ? ts : Date.now();
4654
+ const path = storePath2(dataDir);
4655
+ mkdirSync9(dirname10(path), { recursive: true });
4656
+ const tmp = `${path}.tmp`;
4657
+ writeFileSync9(tmp, JSON.stringify({ lastSeenBlockedAt: safeTs }, null, 2) + "\n", "utf8");
4658
+ renameSync3(tmp, path);
4659
+ return safeTs;
4660
+ }
4661
+
4662
+ // src/routes/blocked-seen.ts
4663
+ function createBlockedSeenRouter(dataDir) {
4664
+ const router = Router19();
4665
+ router.get("/", (_req, res) => {
4666
+ res.json({ lastSeenBlockedAt: readLastSeenBlockedAt(dataDir) });
4667
+ });
4668
+ router.post("/", (req, res) => {
4669
+ const body = req.body;
4670
+ const ts = typeof body?.ts === "number" && Number.isFinite(body.ts) ? body.ts : Date.now();
4671
+ try {
4672
+ res.json({ lastSeenBlockedAt: markBlockedSeen(ts, dataDir) });
4673
+ } catch (err) {
4674
+ res.status(500).json({ error: `Could not save: ${err.message}` });
4675
+ }
4676
+ });
4677
+ return router;
4678
+ }
4679
+
4680
+ // src/routes/internal-events.ts
4681
+ import { Router as Router20 } from "express";
4627
4682
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
4628
- var ALLOWED = ["proposal-added", "action-approval-needed"];
4683
+ var ALLOWED = [
4684
+ "proposal-added",
4685
+ "action-approval-needed",
4686
+ "execution-failed",
4687
+ "denial-recorded"
4688
+ ];
4629
4689
  var SESSION_EXPIRED = "session-expired";
4630
4690
  var AS_KEY_MISMATCH = "as-key-mismatch";
4631
4691
  var AS_TLS_MISMATCH = "as-tls-mismatch";
@@ -4638,7 +4698,7 @@ function secretMatches2(provided, expected) {
4638
4698
  return timingSafeEqual3(a, b);
4639
4699
  }
4640
4700
  function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch, onAsTlsMismatch) {
4641
- const router = Router19();
4701
+ const router = Router20();
4642
4702
  router.post("/event", (req, res) => {
4643
4703
  if (!secretMatches2(req.headers["x-internal-secret"], getSecret())) {
4644
4704
  res.status(401).json({ error: "Unauthorized" });
@@ -4672,7 +4732,7 @@ function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch
4672
4732
  }
4673
4733
 
4674
4734
  // src/routes/internal-mandate.ts
4675
- import { Router as Router20 } from "express";
4735
+ import { Router as Router21 } from "express";
4676
4736
  import { timingSafeEqual as timingSafeEqual4, randomUUID } from "crypto";
4677
4737
 
4678
4738
  // src/lib/mandate-ceremony.ts
@@ -4854,7 +4914,7 @@ function secretMatches3(provided, expected) {
4854
4914
  return a.length === b.length && timingSafeEqual4(a, b);
4855
4915
  }
4856
4916
  function createInternalMandateRouter(getSecret, deps) {
4857
- const router = Router20();
4917
+ const router = Router21();
4858
4918
  router.post("/mandate", async (req, res) => {
4859
4919
  if (!secretMatches3(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
4860
4920
  res.status(401).json({ error: "unauthorized" });
@@ -4887,9 +4947,13 @@ function createInternalMandateRouter(getSecret, deps) {
4887
4947
  }
4888
4948
 
4889
4949
  // src/lib/notification-dispatcher.ts
4890
- var TRIGGER_EVENTS = ["proposal-added", "action-approval-needed"];
4950
+ var REVIEW_EVENTS = ["proposal-added", "action-approval-needed"];
4951
+ var FAILURE_EVENTS = ["execution-failed"];
4952
+ var TRIGGER_EVENTS = [...REVIEW_EVENTS, ...FAILURE_EVENTS];
4891
4953
  var TITLE = "Suveren";
4892
4954
  var WAITING_MESSAGE = "Something is waiting for your review.";
4955
+ var FAILURE_MESSAGE = "An approved action failed.";
4956
+ var MIXED_MESSAGE = "Something needs your attention.";
4893
4957
  var FLOOD_THRESHOLD = 10;
4894
4958
  var floodMessage = (n) => `Unusual volume: ${n} requests in the last minute.`;
4895
4959
  var DEBOUNCE_MS = 5e3;
@@ -4900,6 +4964,9 @@ var NotificationDispatcher = class {
4900
4964
  pendingWhileCoolingDown = false;
4901
4965
  /** Events observed since the current window opened — the flood signal only. */
4902
4966
  observedInWindow = 0;
4967
+ /** Which kinds were observed since the current window opened — selects the message. */
4968
+ reviewSeenInWindow = false;
4969
+ failureSeenInWindow = false;
4903
4970
  unsubscribe = null;
4904
4971
  now;
4905
4972
  setTimeoutFn;
@@ -4917,7 +4984,8 @@ var NotificationDispatcher = class {
4917
4984
  start() {
4918
4985
  if (this.unsubscribe) return this.unsubscribe;
4919
4986
  const off = eventBus.subscribe((event) => {
4920
- if (TRIGGER_EVENTS.includes(event.type)) this.onTrigger();
4987
+ if (FAILURE_EVENTS.includes(event.type)) this.onTrigger("failure");
4988
+ else if (REVIEW_EVENTS.includes(event.type)) this.onTrigger("review");
4921
4989
  });
4922
4990
  this.unsubscribe = () => {
4923
4991
  off();
@@ -4929,9 +4997,15 @@ var NotificationDispatcher = class {
4929
4997
  };
4930
4998
  return this.unsubscribe;
4931
4999
  }
4932
- /** Exposed for tests; the bus subscription calls this. */
4933
- onTrigger() {
5000
+ /**
5001
+ * Exposed for tests; the bus subscription calls this. `kind` defaults to
5002
+ * 'review' so every existing call site/test (written before FAILURE_EVENTS
5003
+ * existed) keeps its exact behaviour.
5004
+ */
5005
+ onTrigger(kind = "review") {
4934
5006
  this.observedInWindow += 1;
5007
+ if (kind === "failure") this.failureSeenInWindow = true;
5008
+ else this.reviewSeenInWindow = true;
4935
5009
  if (this.cooldownTimer) {
4936
5010
  this.pendingWhileCoolingDown = true;
4937
5011
  return;
@@ -4944,7 +5018,11 @@ var NotificationDispatcher = class {
4944
5018
  }
4945
5019
  fire() {
4946
5020
  const count = this.observedInWindow;
5021
+ const sawReview = this.reviewSeenInWindow;
5022
+ const sawFailure = this.failureSeenInWindow;
4947
5023
  this.observedInWindow = 0;
5024
+ this.reviewSeenInWindow = false;
5025
+ this.failureSeenInWindow = false;
4948
5026
  let enabled = true;
4949
5027
  try {
4950
5028
  enabled = this.settingsFn().desktopNotifications;
@@ -4955,7 +5033,7 @@ var NotificationDispatcher = class {
4955
5033
  `[Control Plane] Review pending \u2014 desktop notification ${enabled ? "sent" : "suppressed (turned off)"}`
4956
5034
  );
4957
5035
  if (enabled) {
4958
- const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : WAITING_MESSAGE;
5036
+ const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : sawReview && sawFailure ? MIXED_MESSAGE : sawFailure ? FAILURE_MESSAGE : WAITING_MESSAGE;
4959
5037
  this.notifyFn(TITLE, message, process.platform, this.url);
4960
5038
  }
4961
5039
  this.cooldownTimer = this.setTimeoutFn(() => {
@@ -5039,17 +5117,17 @@ function buildSessionHealth(vault2) {
5039
5117
  }
5040
5118
 
5041
5119
  // src/lib/denials-reader.ts
5042
- import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
5043
- import { join as join14 } from "path";
5120
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
5121
+ import { join as join15 } from "path";
5044
5122
  function loadDenials(dataDir, decrypt) {
5045
- const encPath = join14(dataDir, "denials.enc.json");
5046
- const plainPath = join14(dataDir, "denials.json");
5123
+ const encPath = join15(dataDir, "denials.enc.json");
5124
+ const plainPath = join15(dataDir, "denials.json");
5047
5125
  if (existsSync12(encPath)) {
5048
- const { blob } = JSON.parse(readFileSync14(encPath, "utf-8"));
5126
+ const { blob } = JSON.parse(readFileSync15(encPath, "utf-8"));
5049
5127
  return JSON.parse(decrypt(blob)).records ?? [];
5050
5128
  }
5051
5129
  if (existsSync12(plainPath)) {
5052
- return JSON.parse(readFileSync14(plainPath, "utf-8")).records ?? [];
5130
+ return JSON.parse(readFileSync15(plainPath, "utf-8")).records ?? [];
5053
5131
  }
5054
5132
  return [];
5055
5133
  }
@@ -5073,11 +5151,11 @@ function isSimulationMode() {
5073
5151
  // src/index.ts
5074
5152
  (function migrateDataDir() {
5075
5153
  if (process.env.SUVEREN_DATA_DIR) return;
5076
- const oldDir = join15(homedir8(), ".hap");
5077
- const newDir = join15(homedir8(), ".suveren");
5154
+ const oldDir = join16(homedir9(), ".hap");
5155
+ const newDir = join16(homedir9(), ".suveren");
5078
5156
  if (!existsSync13(newDir) && existsSync13(oldDir)) {
5079
5157
  try {
5080
- renameSync3(oldDir, newDir);
5158
+ renameSync4(oldDir, newDir);
5081
5159
  console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
5082
5160
  } catch (err) {
5083
5161
  console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
@@ -5085,7 +5163,7 @@ function isSimulationMode() {
5085
5163
  }
5086
5164
  }
5087
5165
  })();
5088
- var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
5166
+ var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join16(homedir9(), ".suveren");
5089
5167
  var SP_URL = resolveAsUrl(DATA_DIR);
5090
5168
  validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
5091
5169
  var POLICY_LOCKED = [...readPolicy().locked];
@@ -5093,7 +5171,7 @@ var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
5093
5171
  var POLICY_LOCKED_TEAM = readPolicy().policy.team;
5094
5172
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
5095
5173
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
5096
- var UI_DIST = process.env.HAP_UI_DIST ?? join15(import.meta.dirname ?? __dirname, "../../ui/dist");
5174
+ var UI_DIST = process.env.HAP_UI_DIST ?? join16(import.meta.dirname ?? __dirname, "../../ui/dist");
5097
5175
  var vault = new Vault(DATA_DIR);
5098
5176
  var shownSnapshotStore = new ShownSnapshotStore(DATA_DIR);
5099
5177
  (function repairIfAsUrlChanged() {
@@ -5402,6 +5480,7 @@ app.use("/mcp", jsonParser, authGuard, createMCPRouter());
5402
5480
  app.use("/autostart", jsonParser, authGuard, createAutostartRouter());
5403
5481
  app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRouter());
5404
5482
  app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
5483
+ app.use("/api/blocked-seen", jsonParser, authGuard, createBlockedSeenRouter());
5405
5484
  app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
5406
5485
  app.use("/api/evidence-export", authGuard, createEvidenceExportRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch));
5407
5486
  app.use("/proposal-status", jsonParser, authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch, shownSnapshotStore));
@@ -5546,6 +5625,15 @@ app.get("/skipped-commitments", authGuard, async (_req, res) => {
5546
5625
  res.status(500).json({ error: "Failed to fetch skipped commitments from MCP server" });
5547
5626
  }
5548
5627
  });
5628
+ app.get("/my-pending-proposals", authGuard, async (_req, res) => {
5629
+ try {
5630
+ const data = await getMyPendingProposals();
5631
+ res.json(data);
5632
+ } catch (err) {
5633
+ console.error("[Control Plane] My-pending-proposals retrieval error:", err);
5634
+ res.status(500).json({ error: "Failed to fetch pending proposals from MCP server" });
5635
+ }
5636
+ });
5549
5637
  app.get("/as-pairing", authGuard, (_req, res) => {
5550
5638
  const pairing = readPairing(DATA_DIR);
5551
5639
  const paired = pairing !== null && pairing.asUrl === SP_URL;
@@ -5569,7 +5657,7 @@ app.get("/as-pairing", authGuard, (_req, res) => {
5569
5657
  });
5570
5658
  app.get("/denials", authGuard, (req, res) => {
5571
5659
  try {
5572
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
5660
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join16(homedir9(), ".suveren");
5573
5661
  const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
5574
5662
  const since = Number(req.query.since);
5575
5663
  const limit = Number(req.query.limit);
@@ -5628,12 +5716,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
5628
5716
  res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
5629
5717
  return;
5630
5718
  }
5631
- const { mkdirSync: mkdirSync9, writeFileSync: writeFileSync9, renameSync: renameSync4 } = await import("fs");
5719
+ const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10, renameSync: renameSync5 } = await import("fs");
5632
5720
  const filePath = agentBriefPath();
5633
- mkdirSync9(dirname10(filePath), { recursive: true });
5721
+ mkdirSync10(dirname11(filePath), { recursive: true });
5634
5722
  const tmpPath = `${filePath}.tmp`;
5635
- writeFileSync9(tmpPath, content, "utf-8");
5636
- renameSync4(tmpPath, filePath);
5723
+ writeFileSync10(tmpPath, content, "utf-8");
5724
+ renameSync5(tmpPath, filePath);
5637
5725
  res.json({ ok: true });
5638
5726
  } catch (err) {
5639
5727
  console.error("[Control Plane] agent-brief/context PUT failed:", err);
@@ -5725,10 +5813,10 @@ function detectRunningVersion() {
5725
5813
  return process.env.HAP_BUILD_SHA ?? "dev";
5726
5814
  }
5727
5815
  const dir = import.meta.dirname ?? __dirname;
5728
- const bundlePkg = join15(dir, "..", "..", "package.json");
5816
+ const bundlePkg = join16(dir, "..", "..", "package.json");
5729
5817
  if (existsSync13(bundlePkg)) {
5730
5818
  try {
5731
- const pkg = JSON.parse(readFileSync15(bundlePkg, "utf8"));
5819
+ const pkg = JSON.parse(readFileSync16(bundlePkg, "utf8"));
5732
5820
  if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
5733
5821
  return pkg.version;
5734
5822
  }
@@ -5780,7 +5868,7 @@ if (existsSync13(UI_DIST)) {
5780
5868
  }));
5781
5869
  app.get("*", (_req, res) => {
5782
5870
  res.setHeader("Cache-Control", "no-store, must-revalidate");
5783
- res.sendFile(join15(UI_DIST, "index.html"));
5871
+ res.sendFile(join16(UI_DIST, "index.html"));
5784
5872
  });
5785
5873
  } else {
5786
5874
  app.get("/", (_req, res) => {