@suveren/gateway 0.30.0 → 0.31.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.
Files changed (40) hide show
  1. package/content/integrations/calendar.json +3 -0
  2. package/content/integrations/crm.json +10 -0
  3. package/content/integrations/deploy-github.json +1 -0
  4. package/content/integrations/erp.json +4 -0
  5. package/content/integrations/gmail.json +4 -0
  6. package/content/integrations/linkedin.json +4 -0
  7. package/content/integrations/mail.json +1 -0
  8. package/content/integrations/mollie.json +5 -0
  9. package/content/integrations/records.json +4 -0
  10. package/dist/control-plane/index.mjs +81 -32
  11. package/dist/mcp-server/http.mjs +51 -5
  12. package/dist/ui/assets/index-CSC5vFza.css +1 -0
  13. package/dist/ui/assets/index-CoAHzend.js +123 -0
  14. package/dist/ui/index.html +2 -2
  15. package/node_modules/undici/docs/docs/api/Client.md +2 -0
  16. package/node_modules/undici/lib/core/connect.js +10 -4
  17. package/node_modules/undici/lib/core/symbols.js +1 -0
  18. package/node_modules/undici/lib/dispatcher/agent.js +6 -2
  19. package/node_modules/undici/lib/dispatcher/client-h1.js +0 -2
  20. package/node_modules/undici/lib/dispatcher/client-h2.js +0 -4
  21. package/node_modules/undici/lib/dispatcher/client.js +3 -2
  22. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +12 -0
  23. package/node_modules/undici/lib/handler/cache-handler.js +48 -0
  24. package/node_modules/undici/lib/handler/deduplication-handler.js +57 -13
  25. package/node_modules/undici/lib/handler/redirect-handler.js +8 -1
  26. package/node_modules/undici/lib/interceptor/decompress.js +15 -5
  27. package/node_modules/undici/lib/web/cookies/index.js +3 -1
  28. package/node_modules/undici/lib/web/cookies/parse.js +138 -137
  29. package/node_modules/undici/lib/web/cookies/util.js +21 -2
  30. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +90 -9
  31. package/node_modules/undici/lib/web/eventsource/eventsource.js +4 -1
  32. package/node_modules/undici/lib/web/fetch/index.js +3 -3
  33. package/node_modules/undici/lib/web/infra/index.js +14 -0
  34. package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +5 -5
  35. package/node_modules/undici/lib/web/websocket/receiver.js +13 -0
  36. package/node_modules/undici/package.json +1 -1
  37. package/node_modules/undici/types/client.d.ts +14 -0
  38. package/package.json +1 -1
  39. package/dist/ui/assets/index-C2BnlNib.js +0 -123
  40. package/dist/ui/assets/index-DPgewsLG.css +0 -1
@@ -122,6 +122,7 @@
122
122
  }
123
123
  },
124
124
  "create_event": {
125
+ "summary": "Event: {summary|short}[ on {start.dateTime,start.date|date}]",
125
126
  "executionMapping": {
126
127
  "duration_minutes": "duration",
127
128
  "lookahead_days": "lookahead",
@@ -143,6 +144,7 @@
143
144
  "actionLabel": "Event created"
144
145
  },
145
146
  "update_event": {
147
+ "summary": "Update event[: {summary|short}]",
146
148
  "executionMapping": {
147
149
  "attendees": [
148
150
  {
@@ -164,6 +166,7 @@
164
166
  "actionLabel": "Event updated"
165
167
  },
166
168
  "delete_event": {
169
+ "summary": "Delete an event",
167
170
  "executionMapping": {
168
171
  "calendarId": "allowed_calendars"
169
172
  },
@@ -74,6 +74,7 @@
74
74
  },
75
75
  "overrides": {
76
76
  "create_contact": {
77
+ "summary": "Create contact {name}[ ({company})]",
77
78
  "executionMapping": {
78
79
  "type": "contact_type"
79
80
  },
@@ -94,6 +95,7 @@
94
95
  }
95
96
  },
96
97
  "update_contact": {
98
+ "summary": "Update contact {name,id}",
97
99
  "executionMapping": {
98
100
  "contact_type": "contact_type"
99
101
  },
@@ -123,6 +125,7 @@
123
125
  }
124
126
  },
125
127
  "delete_contact": {
128
+ "summary": "Archive a {contact_type} contact",
126
129
  "executionMapping": {
127
130
  "contact_type": "contact_type"
128
131
  },
@@ -152,6 +155,7 @@
152
155
  }
153
156
  },
154
157
  "restore_contact": {
158
+ "summary": "Restore a {contact_type} contact",
155
159
  "executionMapping": {
156
160
  "contact_type": "contact_type"
157
161
  },
@@ -181,6 +185,7 @@
181
185
  }
182
186
  },
183
187
  "convert_contact": {
188
+ "summary": "Convert a contact to {to_type}",
184
189
  "executionMapping": {
185
190
  "contact_type": "contact_type"
186
191
  },
@@ -210,6 +215,7 @@
210
215
  }
211
216
  },
212
217
  "log_activity": {
218
+ "summary": "Log {type}: {summary|short}",
213
219
  "executionMapping": {
214
220
  "contact_type": "contact_type"
215
221
  },
@@ -229,6 +235,7 @@
229
235
  }
230
236
  },
231
237
  "create_deal": {
238
+ "summary": "Create deal: {title|short}[ — {value|money}]",
232
239
  "executionMapping": {
233
240
  "contact_type": "contact_type"
234
241
  },
@@ -248,6 +255,7 @@
248
255
  }
249
256
  },
250
257
  "update_deal": {
258
+ "summary": "Update deal[: {title|short}][ — {value|money}]",
251
259
  "executionMapping": {
252
260
  "contact_type": "contact_type"
253
261
  },
@@ -276,6 +284,7 @@
276
284
  }
277
285
  },
278
286
  "create_task": {
287
+ "summary": "Create task: {title|short}[ (due {due_date|date})]",
279
288
  "executionMapping": {
280
289
  "contact_type": "contact_type"
281
290
  },
@@ -295,6 +304,7 @@
295
304
  }
296
305
  },
297
306
  "complete_task": {
307
+ "summary": "Complete a task",
298
308
  "executionMapping": {
299
309
  "contact_type": "contact_type"
300
310
  },
@@ -78,6 +78,7 @@
78
78
  }
79
79
  },
80
80
  "release": {
81
+ "summary": "Release {repo} to {environment}",
81
82
  "executionMapping": {
82
83
  "repo": "allowed_repos",
83
84
  "environment": "allowed_environments",
@@ -99,6 +99,7 @@
99
99
  "requiredValue": "unlimited"
100
100
  },
101
101
  "create_quote": {
102
+ "summary": "Quote of {value|money} net[ — {discount_pct|percent} discount]",
102
103
  "executionMapping": {
103
104
  "value": "value",
104
105
  "discount_pct": "discount_pct",
@@ -133,6 +134,7 @@
133
134
  }
134
135
  },
135
136
  "update_quote": {
137
+ "summary": "Update quote to {value|money} net[ — {discount_pct|percent} discount]",
136
138
  "executionMapping": {
137
139
  "value": "value",
138
140
  "discount_pct": "discount_pct",
@@ -161,6 +163,7 @@
161
163
  }
162
164
  },
163
165
  "send_quote": {
166
+ "summary": "Send quote of {value|money} net[ — {discount_pct|percent} discount]",
164
167
  "executionMapping": {
165
168
  "value": "value",
166
169
  "discount_pct": "discount_pct",
@@ -179,6 +182,7 @@
179
182
  }
180
183
  },
181
184
  "convert_quote_to_order": {
185
+ "summary": "Order of {value|money} net from a quote",
182
186
  "executionMapping": {
183
187
  "value": "value",
184
188
  "discount_pct": "discount_pct",
@@ -80,6 +80,7 @@
80
80
  },
81
81
  "overrides": {
82
82
  "send_message": {
83
+ "summary": "Email to {to|list}: {subject|short}",
83
84
  "executionMapping": {
84
85
  "to": [
85
86
  {
@@ -108,6 +109,7 @@
108
109
  "actionLabel": "Email sent"
109
110
  },
110
111
  "create_draft": {
112
+ "summary": "Draft email to {to|list}[: {subject|short}]",
111
113
  "executionMapping": {
112
114
  "to": [
113
115
  {
@@ -136,6 +138,7 @@
136
138
  "actionLabel": "Email drafted"
137
139
  },
138
140
  "send_draft": {
141
+ "summary": "Send a draft email",
139
142
  "executionMapping": {},
140
143
  "staticExecution": {
141
144
  "recipient_count": 1,
@@ -206,6 +209,7 @@
206
209
  }
207
210
  },
208
211
  "delete_draft": {
212
+ "summary": "Delete a draft email",
209
213
  "executionMapping": {},
210
214
  "staticExecution": {
211
215
  "recipient_count": 0,
@@ -62,6 +62,7 @@
62
62
  },
63
63
  "overrides": {
64
64
  "create_post": {
65
+ "summary": "LinkedIn post: {text|short}",
65
66
  "executionMapping": {
66
67
  "visibility": "audience"
67
68
  },
@@ -73,6 +74,7 @@
73
74
  "actionLabel": "Post published"
74
75
  },
75
76
  "create_image_post": {
77
+ "summary": "LinkedIn image post: {text|short}",
76
78
  "executionMapping": {
77
79
  "visibility": "audience"
78
80
  },
@@ -84,6 +86,7 @@
84
86
  "actionLabel": "Image post published"
85
87
  },
86
88
  "create_article_post": {
89
+ "summary": "LinkedIn article: {title,articleUrl|short}",
87
90
  "executionMapping": {
88
91
  "visibility": "audience"
89
92
  },
@@ -95,6 +98,7 @@
95
98
  "actionLabel": "Article published"
96
99
  },
97
100
  "delete_post": {
101
+ "summary": "Delete a LinkedIn post",
98
102
  "executionMapping": {},
99
103
  "staticExecution": {
100
104
  "allowed_platforms": "linkedin",
@@ -79,6 +79,7 @@
79
79
  }
80
80
  },
81
81
  "send_message": {
82
+ "summary": "Email to {to|list}: {subject|short}",
82
83
  "executionMapping": {
83
84
  "to": [
84
85
  {
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "overrides": {
40
40
  "CreatePayment": {
41
+ "summary": "Payment of {amount|money}: {description|short}",
41
42
  "executionMapping": {
42
43
  "amount": "amount",
43
44
  "currency": "currency"
@@ -56,6 +57,7 @@
56
57
  }
57
58
  },
58
59
  "CreatePaymentLink": {
60
+ "summary": "Payment link[ of {amount|money}]: {description|short}",
59
61
  "executionMapping": {
60
62
  "amount": "amount",
61
63
  "currency": "currency"
@@ -89,12 +91,14 @@
89
91
  }
90
92
  },
91
93
  "CancelSubscription": {
94
+ "summary": "Cancel a subscription",
92
95
  "executionMapping": {},
93
96
  "staticExecution": {
94
97
  "action_type": "subscribe"
95
98
  }
96
99
  },
97
100
  "CreateCustomer": {
101
+ "summary": "Create payment customer[ {name}]",
98
102
  "executionMapping": {},
99
103
  "staticExecution": {
100
104
  "action_type": "charge"
@@ -113,6 +117,7 @@
113
117
  }
114
118
  },
115
119
  "RevokeMandate": {
120
+ "summary": "Revoke a payment mandate",
116
121
  "executionMapping": {},
117
122
  "staticExecution": {
118
123
  "action_type": "charge"
@@ -52,6 +52,7 @@
52
52
  "category": "disabled"
53
53
  },
54
54
  "create_record": {
55
+ "summary": "Create {type}: {title|short}",
55
56
  "executionMapping": {},
56
57
  "staticExecution": {
57
58
  "action_type": "write"
@@ -59,6 +60,7 @@
59
60
  "actionLabel": "Record created"
60
61
  },
61
62
  "archive_record": {
63
+ "summary": "Archive a record",
62
64
  "boundField": "archive_access",
63
65
  "requiredValue": "allowed",
64
66
  "executionMapping": {},
@@ -68,10 +70,12 @@
68
70
  "actionLabel": "Record archived"
69
71
  },
70
72
  "update_record": {
73
+ "summary": "Update record[: {title|short}]",
71
74
  "category": "disabled",
72
75
  "actionLabel": "Record updated"
73
76
  },
74
77
  "delete_record": {
78
+ "summary": "Delete a record",
75
79
  "boundField": "delete_access",
76
80
  "requiredValue": "allowed",
77
81
  "executionMapping": {},
@@ -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 readFileSync16, renameSync as renameSync4 } from "fs";
9
- import { dirname as dirname11, join as join16 } from "path";
10
- import { homedir as homedir9 } from "os";
8
+ import { existsSync as existsSync13, readFileSync as readFileSync17, renameSync as renameSync5 } from "fs";
9
+ import { dirname as dirname12, join as join17 } from "path";
10
+ import { homedir as homedir10 } from "os";
11
11
  import express from "express";
12
12
  import { createProxyMiddleware } from "http-proxy-middleware";
13
13
 
@@ -3384,15 +3384,15 @@ import { promisify } from "util";
3384
3384
  var run = promisify(execFile);
3385
3385
  var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
3386
3386
  var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
3387
- function findCli(dirname12 = MODULE_DIR) {
3387
+ function findCli(dirname13 = MODULE_DIR) {
3388
3388
  const candidates = [
3389
3389
  // Bundled (the shipped layout): dist/control-plane → dist → root → bin
3390
- resolve(dirname12, "..", "..", "bin", "suveren-gateway.js"),
3390
+ resolve(dirname13, "..", "..", "bin", "suveren-gateway.js"),
3391
3391
  // Bundled, were the routes/ directory ever preserved.
3392
- resolve(dirname12, "..", "..", "..", "bin", "suveren-gateway.js"),
3392
+ resolve(dirname13, "..", "..", "..", "bin", "suveren-gateway.js"),
3393
3393
  // Dev, running from source: the repo's own bundle directory.
3394
- resolve(dirname12, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
3395
- resolve(dirname12, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
3394
+ resolve(dirname13, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
3395
+ resolve(dirname13, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
3396
3396
  // Deliberately NO process.cwd() fallback: it makes the result depend on
3397
3397
  // where the process happened to be launched from, so the same install
3398
3398
  // resolves differently between a shell and a login service — and it would
@@ -4677,14 +4677,62 @@ function createBlockedSeenRouter(dataDir) {
4677
4677
  return router;
4678
4678
  }
4679
4679
 
4680
- // src/routes/internal-events.ts
4680
+ // src/routes/tickets-seen.ts
4681
4681
  import { Router as Router20 } from "express";
4682
+
4683
+ // src/lib/tickets-seen-store.ts
4684
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, renameSync as renameSync4 } from "fs";
4685
+ import { homedir as homedir9 } from "os";
4686
+ import { join as join15, dirname as dirname11 } from "path";
4687
+ function storePath3(dataDir) {
4688
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join15(homedir9(), ".suveren");
4689
+ return join15(dir, "tickets-seen.json");
4690
+ }
4691
+ function readLastSeenTicketsAt(dataDir) {
4692
+ try {
4693
+ const raw = JSON.parse(readFileSync15(storePath3(dataDir), "utf8"));
4694
+ return typeof raw.lastSeenTicketsAt === "number" && Number.isFinite(raw.lastSeenTicketsAt) ? raw.lastSeenTicketsAt : 0;
4695
+ } catch {
4696
+ return 0;
4697
+ }
4698
+ }
4699
+ function markTicketsSeen(ts, dataDir) {
4700
+ const safeTs = typeof ts === "number" && Number.isFinite(ts) ? ts : Date.now();
4701
+ const path = storePath3(dataDir);
4702
+ mkdirSync10(dirname11(path), { recursive: true });
4703
+ const tmp = `${path}.tmp`;
4704
+ writeFileSync10(tmp, JSON.stringify({ lastSeenTicketsAt: safeTs }, null, 2) + "\n", "utf8");
4705
+ renameSync4(tmp, path);
4706
+ return safeTs;
4707
+ }
4708
+
4709
+ // src/routes/tickets-seen.ts
4710
+ function createTicketsSeenRouter(dataDir) {
4711
+ const router = Router20();
4712
+ router.get("/", (_req, res) => {
4713
+ res.json({ lastSeenTicketsAt: readLastSeenTicketsAt(dataDir) });
4714
+ });
4715
+ router.post("/", (req, res) => {
4716
+ const body = req.body;
4717
+ const ts = typeof body?.ts === "number" && Number.isFinite(body.ts) ? body.ts : Date.now();
4718
+ try {
4719
+ res.json({ lastSeenTicketsAt: markTicketsSeen(ts, dataDir) });
4720
+ } catch (err) {
4721
+ res.status(500).json({ error: `Could not save: ${err.message}` });
4722
+ }
4723
+ });
4724
+ return router;
4725
+ }
4726
+
4727
+ // src/routes/internal-events.ts
4728
+ import { Router as Router21 } from "express";
4682
4729
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
4683
4730
  var ALLOWED = [
4684
4731
  "proposal-added",
4685
4732
  "action-approval-needed",
4686
4733
  "execution-failed",
4687
- "denial-recorded"
4734
+ "denial-recorded",
4735
+ "ticket-recorded"
4688
4736
  ];
4689
4737
  var SESSION_EXPIRED = "session-expired";
4690
4738
  var AS_KEY_MISMATCH = "as-key-mismatch";
@@ -4698,7 +4746,7 @@ function secretMatches2(provided, expected) {
4698
4746
  return timingSafeEqual3(a, b);
4699
4747
  }
4700
4748
  function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch, onAsTlsMismatch) {
4701
- const router = Router20();
4749
+ const router = Router21();
4702
4750
  router.post("/event", (req, res) => {
4703
4751
  if (!secretMatches2(req.headers["x-internal-secret"], getSecret())) {
4704
4752
  res.status(401).json({ error: "Unauthorized" });
@@ -4732,7 +4780,7 @@ function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch
4732
4780
  }
4733
4781
 
4734
4782
  // src/routes/internal-mandate.ts
4735
- import { Router as Router21 } from "express";
4783
+ import { Router as Router22 } from "express";
4736
4784
  import { timingSafeEqual as timingSafeEqual4, randomUUID } from "crypto";
4737
4785
 
4738
4786
  // src/lib/mandate-ceremony.ts
@@ -4914,7 +4962,7 @@ function secretMatches3(provided, expected) {
4914
4962
  return a.length === b.length && timingSafeEqual4(a, b);
4915
4963
  }
4916
4964
  function createInternalMandateRouter(getSecret, deps) {
4917
- const router = Router21();
4965
+ const router = Router22();
4918
4966
  router.post("/mandate", async (req, res) => {
4919
4967
  if (!secretMatches3(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
4920
4968
  res.status(401).json({ error: "unauthorized" });
@@ -5117,17 +5165,17 @@ function buildSessionHealth(vault2) {
5117
5165
  }
5118
5166
 
5119
5167
  // src/lib/denials-reader.ts
5120
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
5121
- import { join as join15 } from "path";
5168
+ import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
5169
+ import { join as join16 } from "path";
5122
5170
  function loadDenials(dataDir, decrypt) {
5123
- const encPath = join15(dataDir, "denials.enc.json");
5124
- const plainPath = join15(dataDir, "denials.json");
5171
+ const encPath = join16(dataDir, "denials.enc.json");
5172
+ const plainPath = join16(dataDir, "denials.json");
5125
5173
  if (existsSync12(encPath)) {
5126
- const { blob } = JSON.parse(readFileSync15(encPath, "utf-8"));
5174
+ const { blob } = JSON.parse(readFileSync16(encPath, "utf-8"));
5127
5175
  return JSON.parse(decrypt(blob)).records ?? [];
5128
5176
  }
5129
5177
  if (existsSync12(plainPath)) {
5130
- return JSON.parse(readFileSync15(plainPath, "utf-8")).records ?? [];
5178
+ return JSON.parse(readFileSync16(plainPath, "utf-8")).records ?? [];
5131
5179
  }
5132
5180
  return [];
5133
5181
  }
@@ -5151,11 +5199,11 @@ function isSimulationMode() {
5151
5199
  // src/index.ts
5152
5200
  (function migrateDataDir() {
5153
5201
  if (process.env.SUVEREN_DATA_DIR) return;
5154
- const oldDir = join16(homedir9(), ".hap");
5155
- const newDir = join16(homedir9(), ".suveren");
5202
+ const oldDir = join17(homedir10(), ".hap");
5203
+ const newDir = join17(homedir10(), ".suveren");
5156
5204
  if (!existsSync13(newDir) && existsSync13(oldDir)) {
5157
5205
  try {
5158
- renameSync4(oldDir, newDir);
5206
+ renameSync5(oldDir, newDir);
5159
5207
  console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
5160
5208
  } catch (err) {
5161
5209
  console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
@@ -5163,7 +5211,7 @@ function isSimulationMode() {
5163
5211
  }
5164
5212
  }
5165
5213
  })();
5166
- var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join16(homedir9(), ".suveren");
5214
+ var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join17(homedir10(), ".suveren");
5167
5215
  var SP_URL = resolveAsUrl(DATA_DIR);
5168
5216
  validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
5169
5217
  var POLICY_LOCKED = [...readPolicy().locked];
@@ -5171,7 +5219,7 @@ var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
5171
5219
  var POLICY_LOCKED_TEAM = readPolicy().policy.team;
5172
5220
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
5173
5221
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
5174
- var UI_DIST = process.env.HAP_UI_DIST ?? join16(import.meta.dirname ?? __dirname, "../../ui/dist");
5222
+ var UI_DIST = process.env.HAP_UI_DIST ?? join17(import.meta.dirname ?? __dirname, "../../ui/dist");
5175
5223
  var vault = new Vault(DATA_DIR);
5176
5224
  var shownSnapshotStore = new ShownSnapshotStore(DATA_DIR);
5177
5225
  (function repairIfAsUrlChanged() {
@@ -5481,6 +5529,7 @@ app.use("/autostart", jsonParser, authGuard, createAutostartRouter());
5481
5529
  app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRouter());
5482
5530
  app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
5483
5531
  app.use("/api/blocked-seen", jsonParser, authGuard, createBlockedSeenRouter());
5532
+ app.use("/api/tickets-seen", jsonParser, authGuard, createTicketsSeenRouter());
5484
5533
  app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
5485
5534
  app.use("/api/evidence-export", authGuard, createEvidenceExportRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch));
5486
5535
  app.use("/proposal-status", jsonParser, authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch, shownSnapshotStore));
@@ -5657,7 +5706,7 @@ app.get("/as-pairing", authGuard, (_req, res) => {
5657
5706
  });
5658
5707
  app.get("/denials", authGuard, (req, res) => {
5659
5708
  try {
5660
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join16(homedir9(), ".suveren");
5709
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join17(homedir10(), ".suveren");
5661
5710
  const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
5662
5711
  const since = Number(req.query.since);
5663
5712
  const limit = Number(req.query.limit);
@@ -5716,12 +5765,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
5716
5765
  res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
5717
5766
  return;
5718
5767
  }
5719
- const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10, renameSync: renameSync5 } = await import("fs");
5768
+ const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync11, renameSync: renameSync6 } = await import("fs");
5720
5769
  const filePath = agentBriefPath();
5721
- mkdirSync10(dirname11(filePath), { recursive: true });
5770
+ mkdirSync11(dirname12(filePath), { recursive: true });
5722
5771
  const tmpPath = `${filePath}.tmp`;
5723
- writeFileSync10(tmpPath, content, "utf-8");
5724
- renameSync5(tmpPath, filePath);
5772
+ writeFileSync11(tmpPath, content, "utf-8");
5773
+ renameSync6(tmpPath, filePath);
5725
5774
  res.json({ ok: true });
5726
5775
  } catch (err) {
5727
5776
  console.error("[Control Plane] agent-brief/context PUT failed:", err);
@@ -5813,10 +5862,10 @@ function detectRunningVersion() {
5813
5862
  return process.env.HAP_BUILD_SHA ?? "dev";
5814
5863
  }
5815
5864
  const dir = import.meta.dirname ?? __dirname;
5816
- const bundlePkg = join16(dir, "..", "..", "package.json");
5865
+ const bundlePkg = join17(dir, "..", "..", "package.json");
5817
5866
  if (existsSync13(bundlePkg)) {
5818
5867
  try {
5819
- const pkg = JSON.parse(readFileSync16(bundlePkg, "utf8"));
5868
+ const pkg = JSON.parse(readFileSync17(bundlePkg, "utf8"));
5820
5869
  if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
5821
5870
  return pkg.version;
5822
5871
  }
@@ -5868,7 +5917,7 @@ if (existsSync13(UI_DIST)) {
5868
5917
  }));
5869
5918
  app.get("*", (_req, res) => {
5870
5919
  res.setHeader("Cache-Control", "no-store, must-revalidate");
5871
- res.sendFile(join16(UI_DIST, "index.html"));
5920
+ res.sendFile(join17(UI_DIST, "index.html"));
5872
5921
  });
5873
5922
  } else {
5874
5923
  app.get("/", (_req, res) => {
@@ -3348,6 +3348,7 @@ var SharedState = class {
3348
3348
  } catch (err) {
3349
3349
  throw new ArchiveWriteError(`Receipt archive write failed: ${err instanceof Error ? err.message : String(err)}`);
3350
3350
  }
3351
+ void notifyControlPlane("ticket-recorded");
3351
3352
  }
3352
3353
  setGateContent(path, authorizationId, profileId, content, opts) {
3353
3354
  this.gateStore.set(authorizationId, {
@@ -4020,10 +4021,44 @@ function invalidDisplayHintsReason(toolName, entry) {
4020
4021
  }
4021
4022
  return null;
4022
4023
  }
4024
+ var SUMMARY_FORMATS = ["money", "percent", "list", "short", "date"];
4025
+ function invalidSummaryReason(toolName, entry) {
4026
+ if (entry === null || typeof entry !== "object") return null;
4027
+ const summary = entry.summary;
4028
+ if (summary === void 0) return null;
4029
+ const at = `override "${toolName}".summary`;
4030
+ if (typeof summary !== "string" || summary.trim().length === 0) return `${at} must be a non-empty string`;
4031
+ if (summary.length > 200) return `${at} must be at most 200 characters`;
4032
+ const balanced = (open, close) => {
4033
+ let d = 0;
4034
+ for (const ch of summary) {
4035
+ if (ch === open) {
4036
+ d += 1;
4037
+ if (d > 1) return false;
4038
+ }
4039
+ if (ch === close) {
4040
+ d -= 1;
4041
+ if (d < 0) return false;
4042
+ }
4043
+ }
4044
+ return d === 0;
4045
+ };
4046
+ if (!balanced("{", "}")) return `${at} has unbalanced or nested { }`;
4047
+ if (!balanced("[", "]")) return `${at} has unbalanced or nested [ ]`;
4048
+ for (const m of summary.matchAll(/\{([^{}]+)\}/g)) {
4049
+ const [paths, fmt, extra] = m[1].split("|").map((x) => x.trim());
4050
+ if (extra !== void 0) return `${at} {${m[1]}} has more than one format`;
4051
+ if (!paths.split(",").every((p) => /^[A-Za-z_][\w-]*(\.[A-Za-z_][\w-]*)*$/.test(p.trim()))) {
4052
+ return `${at} {${m[1]}} has an invalid field path`;
4053
+ }
4054
+ if (fmt !== void 0 && !SUMMARY_FORMATS.includes(fmt)) return `${at} {${m[1]}} has unknown format "${fmt}"`;
4055
+ }
4056
+ return null;
4057
+ }
4023
4058
  function invalidManifestPreviewReason(manifest) {
4024
4059
  const overrides = manifest.toolGating?.overrides ?? {};
4025
4060
  for (const [toolName, entry] of Object.entries(overrides)) {
4026
- const reason = invalidPreviewReason(toolName, entry) ?? invalidDisplayHintsReason(toolName, entry);
4061
+ const reason = invalidPreviewReason(toolName, entry) ?? invalidDisplayHintsReason(toolName, entry) ?? invalidSummaryReason(toolName, entry);
4027
4062
  if (reason) return reason;
4028
4063
  }
4029
4064
  return null;
@@ -5338,10 +5373,10 @@ function zeroCappedBound(profileId, bounds, actionType) {
5338
5373
  }
5339
5374
  return null;
5340
5375
  }
5341
- function toolAlwaysListed(tool, isBuiltin) {
5376
+ function toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed = () => false) {
5342
5377
  if (tool.internal || !tool.gating?.profile) return false;
5343
5378
  if (tool.gating.category === "disabled") return false;
5344
- if (isBuiltin(tool.integrationId)) return false;
5379
+ if (isBuiltin(tool.integrationId) && !builtinAlwaysListed(tool.integrationId)) return false;
5345
5380
  if (!tool.gating.hideUnlessAuthorized) return true;
5346
5381
  return Boolean(tool.gating.simulationOnly) && isSimulationMode();
5347
5382
  }
@@ -6406,9 +6441,10 @@ function buildCapabilityMap(profileId, integrationManager2) {
6406
6441
  function listedWithoutMandate(authorizations, integrationManager2) {
6407
6442
  if (!integrationManager2) return [];
6408
6443
  const isBuiltin = (id) => integrationManager2.isBuiltin?.(id) ?? false;
6444
+ const builtinAlwaysListed = (id) => integrationManager2.builtinAlwaysListed?.(id) ?? false;
6409
6445
  const byProfile = /* @__PURE__ */ new Map();
6410
6446
  for (const tool of integrationManager2.getAllTools()) {
6411
- if (!toolAlwaysListed(tool, isBuiltin)) continue;
6447
+ if (!toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed)) continue;
6412
6448
  const profile = tool.gating.profile;
6413
6449
  if (authorizations.some((a) => a.complete && profileMatches(a.profileId, profile))) continue;
6414
6450
  const short = shortProfileName2(profile);
@@ -7383,8 +7419,9 @@ function createMcpServer(state2, integrationManager2) {
7383
7419
  function refreshTools() {
7384
7420
  const auths = state2.getEnrichedAuthorizations();
7385
7421
  const isBuiltin = (id) => integrationManager2?.isBuiltin?.(id) ?? false;
7422
+ const builtinAlwaysListed = (id) => integrationManager2?.builtinAlwaysListed?.(id) ?? false;
7386
7423
  for (const [, { tool, registered }] of proxiedTools) {
7387
- if (toolAlwaysListed(tool, isBuiltin)) {
7424
+ if (toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed)) {
7388
7425
  registered.enable();
7389
7426
  } else if (tool.gating?.profile) {
7390
7427
  const matchingAuths = auths.filter(
@@ -8233,6 +8270,10 @@ var IntegrationManager = class {
8233
8270
  isBuiltin(id) {
8234
8271
  return this.builtins.has(id);
8235
8272
  }
8273
+ /** A built-in that declares `alwaysListed` (see builtin-integration.ts). */
8274
+ builtinAlwaysListed(id) {
8275
+ return this.builtins.get(id)?.def.alwaysListed === true;
8276
+ }
8236
8277
  /**
8237
8278
  * Whether `id` may run while simulation mode is on: a connector whose manifest
8238
8279
  * carries the `simulation` marker, or a built-in that declares `simulation: true`.
@@ -9209,6 +9250,11 @@ var reportBuiltin = (deps) => ({
9209
9250
  // read-only export CLI; writes only the local, vault-encrypted report file)
9210
9251
  // — safe during a simulation-mode test.
9211
9252
  simulation: true,
9253
+ // Listed without a mandate, like connector tools: an AI client that ignores
9254
+ // tools/list_changed (ChatGPT) otherwise never sees report__* after the
9255
+ // reporting mandate is signed mid-chat (simulation finding, 2026-10-16).
9256
+ // Listing authorizes nothing — the gate still refuses a call without one.
9257
+ alwaysListed: true,
9212
9258
  // The same predicate the reporting window uses to refuse a mandate
9213
9259
  // (reporting@0.1, or no window set): the gate never selects such a mandate
9214
9260
  // for ANY report tool — so write_report is never ticketed under (and never