@intutic/mcp-governance-proxy 1.10.0 → 1.10.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 (123) hide show
  1. package/README.md +28 -0
  2. package/dist/anomaly/detectors.d.ts +129 -0
  3. package/dist/anomaly/detectors.d.ts.map +1 -0
  4. package/dist/anomaly/detectors.js +368 -0
  5. package/dist/anomaly/detectors.js.map +1 -0
  6. package/dist/anomaly/index.d.ts +52 -0
  7. package/dist/anomaly/index.d.ts.map +1 -0
  8. package/dist/anomaly/index.js +71 -0
  9. package/dist/anomaly/index.js.map +1 -0
  10. package/dist/config.d.ts +107 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +129 -6
  13. package/dist/config.js.map +1 -1
  14. package/dist/daemon/healthMonitor.d.ts.map +1 -1
  15. package/dist/daemon/healthMonitor.js +45 -46
  16. package/dist/daemon/healthMonitor.js.map +1 -1
  17. package/dist/daemon/index.d.ts +15 -1
  18. package/dist/daemon/index.d.ts.map +1 -1
  19. package/dist/daemon/index.js +78 -9
  20. package/dist/daemon/index.js.map +1 -1
  21. package/dist/daemon/instrumentation.d.ts +20 -0
  22. package/dist/daemon/instrumentation.d.ts.map +1 -0
  23. package/dist/daemon/instrumentation.js +61 -0
  24. package/dist/daemon/instrumentation.js.map +1 -0
  25. package/dist/daemon/policyCache.d.ts +83 -0
  26. package/dist/daemon/policyCache.d.ts.map +1 -1
  27. package/dist/daemon/policyCache.js +280 -11
  28. package/dist/daemon/policyCache.js.map +1 -1
  29. package/dist/daemon/statusReporter.d.ts +12 -0
  30. package/dist/daemon/statusReporter.d.ts.map +1 -0
  31. package/dist/daemon/statusReporter.js +130 -0
  32. package/dist/daemon/statusReporter.js.map +1 -0
  33. package/dist/daemon/telemetryBatcher.d.ts +3 -1
  34. package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
  35. package/dist/daemon/telemetryBatcher.js +56 -13
  36. package/dist/daemon/telemetryBatcher.js.map +1 -1
  37. package/dist/daemonClient.d.ts +1 -1
  38. package/dist/daemonClient.d.ts.map +1 -1
  39. package/dist/daemonClient.js +0 -2
  40. package/dist/daemonClient.js.map +1 -1
  41. package/dist/dlp.d.ts +19 -0
  42. package/dist/dlp.d.ts.map +1 -1
  43. package/dist/dlp.js +95 -18
  44. package/dist/dlp.js.map +1 -1
  45. package/dist/emitter.d.ts +40 -2
  46. package/dist/emitter.d.ts.map +1 -1
  47. package/dist/emitter.js +27 -5
  48. package/dist/emitter.js.map +1 -1
  49. package/dist/index.js +13 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/injection.d.ts +46 -0
  52. package/dist/injection.d.ts.map +1 -0
  53. package/dist/injection.js +147 -0
  54. package/dist/injection.js.map +1 -0
  55. package/dist/interceptor.d.ts +131 -1
  56. package/dist/interceptor.d.ts.map +1 -1
  57. package/dist/interceptor.js +301 -5
  58. package/dist/interceptor.js.map +1 -1
  59. package/dist/policy.d.ts +110 -2
  60. package/dist/policy.d.ts.map +1 -1
  61. package/dist/policy.js +206 -5
  62. package/dist/policy.js.map +1 -1
  63. package/dist/proxy.d.ts +205 -0
  64. package/dist/proxy.d.ts.map +1 -1
  65. package/dist/proxy.js +572 -65
  66. package/dist/proxy.js.map +1 -1
  67. package/dist/remoteBridge.d.ts +51 -0
  68. package/dist/remoteBridge.d.ts.map +1 -0
  69. package/dist/remoteBridge.js +182 -0
  70. package/dist/remoteBridge.js.map +1 -0
  71. package/dist/session.d.ts +158 -0
  72. package/dist/session.d.ts.map +1 -0
  73. package/dist/session.js +206 -0
  74. package/dist/session.js.map +1 -0
  75. package/dist/sessionScope.d.ts +51 -0
  76. package/dist/sessionScope.d.ts.map +1 -0
  77. package/dist/sessionScope.js +94 -0
  78. package/dist/sessionScope.js.map +1 -0
  79. package/dist/sessionStore.d.ts +69 -0
  80. package/dist/sessionStore.d.ts.map +1 -0
  81. package/dist/sessionStore.js +143 -0
  82. package/dist/sessionStore.js.map +1 -0
  83. package/dist/tofu.d.ts +112 -0
  84. package/dist/tofu.d.ts.map +1 -0
  85. package/dist/tofu.js +180 -0
  86. package/dist/tofu.js.map +1 -0
  87. package/dist/toolPoison.d.ts +32 -0
  88. package/dist/toolPoison.d.ts.map +1 -0
  89. package/dist/toolPoison.js +81 -0
  90. package/dist/toolPoison.js.map +1 -0
  91. package/dist/valkeyErrors.d.ts +17 -0
  92. package/dist/valkeyErrors.d.ts.map +1 -0
  93. package/dist/valkeyErrors.js +24 -0
  94. package/dist/valkeyErrors.js.map +1 -0
  95. package/dist/wasm/context.d.ts +57 -0
  96. package/dist/wasm/context.d.ts.map +1 -0
  97. package/dist/wasm/context.js +59 -0
  98. package/dist/wasm/context.js.map +1 -0
  99. package/dist/wasm/hostImports.d.ts +43 -0
  100. package/dist/wasm/hostImports.d.ts.map +1 -0
  101. package/dist/wasm/hostImports.js +158 -0
  102. package/dist/wasm/hostImports.js.map +1 -0
  103. package/dist/wasm/loader.d.ts +95 -0
  104. package/dist/wasm/loader.d.ts.map +1 -0
  105. package/dist/wasm/loader.js +198 -0
  106. package/dist/wasm/loader.js.map +1 -0
  107. package/dist/wasm/referencedFiles.d.ts +115 -0
  108. package/dist/wasm/referencedFiles.d.ts.map +1 -0
  109. package/dist/wasm/referencedFiles.js +366 -0
  110. package/dist/wasm/referencedFiles.js.map +1 -0
  111. package/dist/wasm/runner.d.ts +91 -0
  112. package/dist/wasm/runner.d.ts.map +1 -0
  113. package/dist/wasm/runner.js +308 -0
  114. package/dist/wasm/runner.js.map +1 -0
  115. package/dist/wasm/watch.d.ts +5 -0
  116. package/dist/wasm/watch.d.ts.map +1 -0
  117. package/dist/wasm/watch.js +54 -0
  118. package/dist/wasm/watch.js.map +1 -0
  119. package/dist/wasm/worker.d.ts +39 -0
  120. package/dist/wasm/worker.d.ts.map +1 -0
  121. package/dist/wasm/worker.js +245 -0
  122. package/dist/wasm/worker.js.map +1 -0
  123. package/package.json +14 -4
@@ -10,6 +10,7 @@
10
10
  import https from 'node:https';
11
11
  import http from 'node:http';
12
12
  import { Redis } from 'ioredis';
13
+ import { describeConnectionError } from '../valkeyErrors.js';
13
14
  import { createLogger } from '@intutic/logger';
14
15
  const logger = createLogger('mcp-proxy.policyCache');
15
16
  const getPolicyTtlMs = () => parseInt(process.env['MCP_DAEMON_POLICY_TTL_MS'] ?? '300000', 10);
@@ -22,8 +23,30 @@ const valkey = new Redis(VALKEY_URL, {
22
23
  maxRetriesPerRequest: 3,
23
24
  });
24
25
  valkey.on('error', (err) => {
25
- logger.warn({ err: err.message }, 'policyCache Valkey connection error');
26
+ logger.warn({ err: describeConnectionError(err) }, 'policyCache Valkey connection error');
26
27
  });
28
+ /** Same key `packages/db`'s `configVersionKey` builds; this package does not depend on it. */
29
+ const configVersionKey = (workspaceId) => `v2:sync:config_version:${workspaceId}`;
30
+ async function readConfigVersion(workspaceId) {
31
+ // Only a connected client is asked. With Valkey down, ioredis queues the GET
32
+ // and rejects it after `maxRetriesPerRequest` reconnect attempts — hundreds
33
+ // of milliseconds on every cache hit, on the tool-call path. Unknown version
34
+ // means "serve the cached entry", which is what the cache did before the
35
+ // version existed. The miss path below still connects lazily, so a reachable
36
+ // Valkey reaches `ready` on the first fetch.
37
+ if (valkey.status !== 'ready')
38
+ return undefined;
39
+ try {
40
+ const raw = await valkey.get(configVersionKey(workspaceId));
41
+ if (raw === null)
42
+ return undefined;
43
+ const n = Number(raw);
44
+ return Number.isFinite(n) ? n : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
27
50
  // Simple LRU map (insertion-order eviction)
28
51
  const lru = new Map();
29
52
  function evictIfFull() {
@@ -35,24 +58,127 @@ function evictIfFull() {
35
58
  function isStale(entry) {
36
59
  return Date.now() - entry.cachedAt > getPolicyTtlMs();
37
60
  }
61
+ function isRecord(value) {
62
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
63
+ }
64
+ /**
65
+ * Validates a `GET /api/v1/policy/resolve` body. The response is `unknown` at
66
+ * this boundary — it crosses the network — so every field is checked rather
67
+ * than asserted. Returns null when the body is not JSON or not an object;
68
+ * individual fields fall back to their empty value.
69
+ *
70
+ * `allowedTools`/`toolDescriptionOverrides`/`allowedServers` default to
71
+ * `[]`/`{}`/`[]` when absent — both because an older control plane may not
72
+ * send them yet, and because absent is this policy's existing convention for
73
+ * "unrestricted" (see `packages/mcp-proxy/src/policy.ts`'s `absorbCuration`).
74
+ */
75
+ function parsePolicyResponse(raw) {
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(raw);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (!isRecord(parsed))
84
+ return null;
85
+ const sopRules = parsed['sopRules'];
86
+ const dlpPatterns = parsed['dlpPatterns'];
87
+ const interventionMode = parsed['interventionMode'];
88
+ const allowedTools = parsed['allowedTools'];
89
+ const toolDescriptionOverrides = parsed['toolDescriptionOverrides'];
90
+ const allowedServers = parsed['allowedServers'];
91
+ const mcpInjectionAction = parsed['mcpInjectionAction'];
92
+ const mcpInjectionPatterns = parsed['mcpInjectionPatterns'];
93
+ const mcpAnomalyMode = parsed['mcpAnomalyMode'];
94
+ const mcpAnomalyOverridesRaw = parsed['mcpAnomalyOverrides'];
95
+ return {
96
+ sopRules: Array.isArray(sopRules) ? sopRules.filter(isRecord) : [],
97
+ dlpPatterns: Array.isArray(dlpPatterns)
98
+ ? dlpPatterns.filter((p) => typeof p === 'string')
99
+ : [],
100
+ // 'TRANSPARENT', matching every other default in this pipeline: the
101
+ // resolve route's own fallback (services/control-plane/src/routes/
102
+ // evaluate.ts), the schema default on pcas_policies.intervention_mode,
103
+ // and this module's snapshot-seed path below. The old default here was
104
+ // 'BYPASS', which is not an intervention_mode_type value at all — a
105
+ // fabricated mode no other component recognises.
106
+ interventionMode: typeof interventionMode === 'string' ? interventionMode : 'TRANSPARENT',
107
+ allowedTools: Array.isArray(allowedTools)
108
+ ? allowedTools.filter((t) => typeof t === 'string')
109
+ : [],
110
+ toolDescriptionOverrides: isRecord(toolDescriptionOverrides)
111
+ ? Object.fromEntries(Object.entries(toolDescriptionOverrides).filter((entry) => typeof entry[1] === 'string'))
112
+ : {},
113
+ allowedServers: Array.isArray(allowedServers)
114
+ ? allowedServers.filter((s) => typeof s === 'string')
115
+ : [],
116
+ mcpInjectionAction: mcpInjectionAction === 'warn' || mcpInjectionAction === 'block' ? mcpInjectionAction : undefined,
117
+ mcpInjectionPatterns: Array.isArray(mcpInjectionPatterns)
118
+ ? mcpInjectionPatterns.filter((p) => typeof p === 'string')
119
+ : [],
120
+ mcpAnomalyMode: mcpAnomalyMode === 'enforce' || mcpAnomalyMode === 'warn' || mcpAnomalyMode === 'off'
121
+ ? mcpAnomalyMode
122
+ : undefined,
123
+ mcpAnomalyOverrides: isRecord(mcpAnomalyOverridesRaw)
124
+ ? Object.fromEntries(Object.entries(mcpAnomalyOverridesRaw).filter((entry) => entry[1] === 'steer' || entry[1] === 'reask' || entry[1] === 'kill' || entry[1] === 'off'))
125
+ : {},
126
+ };
127
+ }
38
128
  async function fetchFromControlPlane(workspaceId) {
129
+ // Read BEFORE the fetch: a bump that lands during the request is then seen
130
+ // as "changed" on the next hit and refetched, never missed.
131
+ const versionAtFetch = await readConfigVersion(workspaceId);
39
132
  return new Promise((resolve) => {
40
133
  const path = `/api/v1/policy/resolve?workspaceId=${encodeURIComponent(workspaceId)}`;
41
134
  const url = new URL(path, getCpUrl());
42
135
  const lib = url.protocol === 'https:' ? https : http;
43
136
  const req = lib.request({ hostname: url.hostname, port: url.port, path: url.pathname + url.search, method: 'GET',
44
137
  headers: { 'Authorization': `Bearer ${getDaemonApiKey()}`, 'Accept': 'application/json' } }, (res) => {
138
+ // Without an encoding the stream yields Buffers, and `data += chunk`
139
+ // then stringifies each one in isolation — a multi-byte UTF-8 character
140
+ // split across a chunk boundary becomes U+FFFD and the JSON.parse below
141
+ // fails. setEncoding makes the decoder span chunks, which is also what
142
+ // makes the `chunk: string` annotation true rather than wishful.
143
+ res.setEncoding('utf8');
144
+ const statusCode = res.statusCode ?? 0;
45
145
  let data = '';
46
- res.on('data', (c) => { data += c; });
146
+ res.on('data', (chunk) => { data += chunk; });
47
147
  res.on('end', () => {
48
- try {
49
- const parsed = JSON.parse(data);
50
- resolve({ workspaceId, sopRules: parsed.sopRules ?? [], dlpPatterns: parsed.dlpPatterns ?? [],
51
- interventionMode: parsed.interventionMode ?? 'BYPASS', cachedAt: Date.now() });
148
+ // An error body parses as JSON too. `{"error":"unauthorized"}` used to
149
+ // become a policy with no sopRules and the parser's fallback
150
+ // interventionMode (historically the fabricated 'BYPASS', now
151
+ // 'TRANSPARENT'), which resolvePolicy then cached for the full TTL
152
+ // and socketServer read as `allowed: true` — an expired daemon key
153
+ // silently turned enforcement off. A non-2xx is a failure to
154
+ // resolve, so it returns null, the same as the transport failures
155
+ // below.
156
+ if (statusCode < 200 || statusCode >= 300) {
157
+ logger.warn({ workspaceId, statusCode }, 'policy_cache.fetch_http_error');
158
+ resolve(null);
159
+ return;
52
160
  }
53
- catch {
161
+ const parsed = parsePolicyResponse(data);
162
+ if (!parsed) {
163
+ logger.warn({ workspaceId }, 'policy_cache.fetch_unparseable_body');
54
164
  resolve(null);
165
+ return;
55
166
  }
167
+ resolve({
168
+ workspaceId,
169
+ sopRules: parsed.sopRules,
170
+ dlpPatterns: parsed.dlpPatterns,
171
+ interventionMode: parsed.interventionMode,
172
+ allowedTools: parsed.allowedTools,
173
+ toolDescriptionOverrides: parsed.toolDescriptionOverrides,
174
+ allowedServers: parsed.allowedServers,
175
+ mcpInjectionAction: parsed.mcpInjectionAction,
176
+ mcpInjectionPatterns: parsed.mcpInjectionPatterns,
177
+ mcpAnomalyMode: parsed.mcpAnomalyMode,
178
+ mcpAnomalyOverrides: parsed.mcpAnomalyOverrides,
179
+ cachedAt: Date.now(),
180
+ configVersion: versionAtFetch,
181
+ });
56
182
  });
57
183
  });
58
184
  req.on('error', () => resolve(null));
@@ -60,6 +186,97 @@ async function fetchFromControlPlane(workspaceId) {
60
186
  req.end();
61
187
  });
62
188
  }
189
+ /**
190
+ * Seeds the LRU from the policy snapshot the sync daemon writes locally.
191
+ *
192
+ * # What this removes
193
+ *
194
+ * `resolvePolicy`'s cold path is a blocking HTTP GET with a 5s socket timeout,
195
+ * taken on the daemon's first request for a workspace after every restart. The
196
+ * sync daemon already writes the same policy to
197
+ * `~/.intutic/hooks/policy-snapshot.json` every cycle, so on the machine's own
198
+ * workspace that round trip is avoidable.
199
+ *
200
+ * # Two things that make the obvious version wrong
201
+ *
202
+ * **It reads `sopRules`, not `rules`.** The snapshot carries both. `rules` is the
203
+ * *gate* projection — patterns rewritten as space-padded EREs, `{source,
204
+ * severity, subject}`, `warn` rules already dropped. Those pass this module's
205
+ * `parsePolicyResponse` (it only checks that entries are objects) and are then
206
+ * rejected wholesale by `isSopRule` in `../policy.ts`, which requires
207
+ * `toolPattern` and `action`. The result would be a cache that reports entries,
208
+ * logs nothing above `debug`, and enforces **nothing** — strictly worse than the
209
+ * blocking fetch, and invisible.
210
+ *
211
+ * **`cachedAt` comes from the snapshot, not from now.** Stamping `Date.now()` on
212
+ * a file written days ago (the sync daemon may be stopped — the snapshot writer
213
+ * anticipates exactly that) would present stale policy as fresh and suppress the
214
+ * refresh for a full TTL. Carrying the real write time means a stale snapshot
215
+ * seeds the cache *and* is immediately recognised as stale, so the first request
216
+ * returns instantly and triggers a background refresh — which is the actual goal.
217
+ *
218
+ * Never throws. This runs inside the daemon's `main()`, whose rejections become
219
+ * `process.exit(1)`, and the daemon is a KeepAlive LaunchAgent — so a throw here
220
+ * would be a restart loop rather than an error.
221
+ *
222
+ * @returns the workspaceId seeded, or null if nothing usable was found.
223
+ */
224
+ export async function seedFromSnapshot(snapshotPath) {
225
+ try {
226
+ const { readFile } = await import('node:fs/promises');
227
+ const os = await import('node:os');
228
+ const path = await import('node:path');
229
+ const file = snapshotPath ??
230
+ process.env['INTUTIC_POLICY_SNAPSHOT'] ??
231
+ path.join(os.homedir(), '.intutic', 'hooks', 'policy-snapshot.json');
232
+ const parsed = JSON.parse(await readFile(file, 'utf-8'));
233
+ if (!isRecord(parsed))
234
+ return null;
235
+ const workspaceId = parsed['workspaceId'];
236
+ if (typeof workspaceId !== 'string' || !workspaceId)
237
+ return null;
238
+ // Absent `sopRules` means an older snapshot that carries only the gate
239
+ // projection. Seed nothing rather than seed an empty policy: an empty
240
+ // policy is indistinguishable from "no rules configured" downstream, and
241
+ // this path exists to avoid a slow answer, never to invent a wrong one.
242
+ const raw = parsed['sopRules'];
243
+ if (!Array.isArray(raw)) {
244
+ logger.debug({ file }, 'policy_cache.seed_skipped_no_sop_rules');
245
+ return null;
246
+ }
247
+ const generatedAt = parsed['generatedAt'];
248
+ const cachedAt = typeof generatedAt === 'string' && !Number.isNaN(Date.parse(generatedAt))
249
+ ? Date.parse(generatedAt)
250
+ : 0; // unknown age reads as maximally stale, never as fresh
251
+ const entry = {
252
+ workspaceId,
253
+ sopRules: raw.filter(isRecord),
254
+ dlpPatterns: [],
255
+ interventionMode: typeof parsed['interventionMode'] === 'string' ? parsed['interventionMode'] : 'TRANSPARENT',
256
+ // The sync daemon's snapshot (services/sync-daemon/src/lib/policySnapshot.ts)
257
+ // does not carry MCP curation today — it predates this field and is a
258
+ // separate `.rules`-gate mechanism, not this module's HTTP/Valkey path.
259
+ // Default to unrestricted rather than invent a value; the background
260
+ // HTTP refresh this seed exists to avoid delaying will fill these in on
261
+ // the next cycle.
262
+ mcpInjectionPatterns: [],
263
+ allowedTools: [],
264
+ toolDescriptionOverrides: {},
265
+ allowedServers: [],
266
+ mcpAnomalyOverrides: {},
267
+ cachedAt,
268
+ };
269
+ evictIfFull();
270
+ lru.set(workspaceId, entry);
271
+ logger.info({ workspaceId, ruleCount: entry.sopRules.length, ageMs: cachedAt ? Date.now() - cachedAt : null }, 'policy_cache.seeded_from_snapshot');
272
+ return workspaceId;
273
+ }
274
+ catch {
275
+ // No snapshot, unreadable, or malformed. The cold HTTP path still works;
276
+ // this is an optimisation, and an optimisation must never be load-bearing.
277
+ return null;
278
+ }
279
+ }
63
280
  /**
64
281
  * Resolves policy for a workspace.
65
282
  * Order: LRU (fresh) → LRU (stale, triggers async refresh) → Valkey → HTTP fetch
@@ -67,6 +284,28 @@ async function fetchFromControlPlane(workspaceId) {
67
284
  export async function resolvePolicy(workspaceId) {
68
285
  const cached = lru.get(workspaceId);
69
286
  if (cached && !isStale(cached)) {
287
+ // A fresh entry is still refetched when the workspace's config version
288
+ // moved since it was fetched (a guardrail promote/retire bumps it) — one
289
+ // Valkey GET per hit, the same class of read as the Valkey-backed miss
290
+ // path below. A failed refetch keeps serving the cached entry.
291
+ if (cached.configVersion !== undefined) {
292
+ const current = await readConfigVersion(workspaceId);
293
+ if (current !== undefined && current !== cached.configVersion) {
294
+ const fresh = await fetchFromControlPlane(workspaceId);
295
+ if (fresh) {
296
+ lru.delete(workspaceId);
297
+ evictIfFull();
298
+ lru.set(workspaceId, fresh);
299
+ try {
300
+ await valkey.set(`mcp_daemon:policy:${workspaceId}`, JSON.stringify(fresh), 'PX', getPolicyTtlMs());
301
+ }
302
+ catch {
303
+ // Same reasoning as the stale-refresh branch below.
304
+ }
305
+ return fresh;
306
+ }
307
+ }
308
+ }
70
309
  // Touch for LRU recency
71
310
  lru.delete(workspaceId);
72
311
  lru.set(workspaceId, cached);
@@ -82,7 +321,14 @@ export async function resolvePolicy(workspaceId) {
82
321
  try {
83
322
  await valkey.set(`mcp_daemon:policy:${workspaceId}`, JSON.stringify(fresh), 'PX', getPolicyTtlMs());
84
323
  }
85
- catch { }
324
+ catch {
325
+ // Valkey unreachable. This write only shares the refreshed policy
326
+ // with sibling daemons; the in-process LRU above already holds it,
327
+ // so resolution stays correct and the peers simply take an HTTP miss.
328
+ // Deliberately not logged — this runs on a background refresh every
329
+ // TTL per workspace and would flood the log while Valkey is down.
330
+ // The connection-level 'error' handler above reports the outage once.
331
+ }
86
332
  }
87
333
  });
88
334
  return cached;
@@ -91,13 +337,31 @@ export async function resolvePolicy(workspaceId) {
91
337
  try {
92
338
  const valkeyCached = await valkey.get(`mcp_daemon:policy:${workspaceId}`);
93
339
  if (valkeyCached) {
94
- const parsed = JSON.parse(valkeyCached);
340
+ // Cast, not validated — this is our own prior write, not a network
341
+ // boundary. But an entry written before this field existed is still a
342
+ // valid prior write of an older shape, so the three curation fields
343
+ // are normalized rather than trusted blindly off the cast.
344
+ const raw = JSON.parse(valkeyCached);
345
+ const parsed = {
346
+ ...raw,
347
+ allowedTools: Array.isArray(raw.allowedTools) ? raw.allowedTools : [],
348
+ toolDescriptionOverrides: isRecord(raw.toolDescriptionOverrides) ? raw.toolDescriptionOverrides : {},
349
+ allowedServers: Array.isArray(raw.allowedServers) ? raw.allowedServers : [],
350
+ mcpInjectionPatterns: Array.isArray(raw.mcpInjectionPatterns) ? raw.mcpInjectionPatterns : [],
351
+ mcpAnomalyOverrides: isRecord(raw.mcpAnomalyOverrides) ? raw.mcpAnomalyOverrides : {},
352
+ };
95
353
  evictIfFull();
96
354
  lru.set(workspaceId, parsed);
97
355
  return parsed;
98
356
  }
99
357
  }
100
- catch { }
358
+ catch {
359
+ // Valkey unreachable, or the cached value is not parseable JSON (written
360
+ // by an older ResolvedPolicy shape). Either way this is only a cache tier:
361
+ // falling through to the control-plane fetch below yields the same answer,
362
+ // just slower. Swallowing here is what keeps the daemon serving policy
363
+ // during a Valkey outage.
364
+ }
101
365
  // Cache miss in Valkey too — fetch synchronously
102
366
  logger.debug({ workspaceId }, 'policy_cache.miss');
103
367
  const fresh = await fetchFromControlPlane(workspaceId);
@@ -107,7 +371,12 @@ export async function resolvePolicy(workspaceId) {
107
371
  try {
108
372
  await valkey.set(`mcp_daemon:policy:${workspaceId}`, JSON.stringify(fresh), 'PX', getPolicyTtlMs());
109
373
  }
110
- catch { }
374
+ catch {
375
+ // Valkey unreachable. Write-through to the shared tier is an
376
+ // optimisation, not a correctness step: `fresh` is already in the LRU
377
+ // and is returned below. Failing the caller's policy resolution because
378
+ // a cache write failed would take the daemon down with Valkey.
379
+ }
111
380
  }
112
381
  return fresh;
113
382
  }
@@ -1 +1 @@
1
- {"version":3,"file":"policyCache.js","sourceRoot":"","sources":["../../src/daemon/policyCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,MAAM,YAAY,CAAA;AAC9B,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,MAAM,MAAM,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAA;AAEpD,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,QAAQ,EAAE,EAAE,CAAC,CAAA;AAC9F,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,IAAI,KAAK,EAAK,EAAE,CAAC,CAAA;AACjG,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,uBAAuB,CAAA;AAClF,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAM,EAAE,CAAA;AAEpE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,wBAAwB,CAAA;AACpG,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE;IACnC,WAAW,EAAE,IAAI;IACjB,oBAAoB,EAAE,CAAC;CACxB,CAAC,CAAA;AAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAQ,EAAE,EAAE;IAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,qCAAqC,CAAC,CAAA;AAC1E,CAAC,CAAC,CAAA;AAUF,4CAA4C;AAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,EAA0B,CAAA;AAE7C,SAAS,WAAW;IAClB,IAAI,GAAG,CAAC,IAAI,IAAI,aAAa,EAAE,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAA;QAClD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACtB,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,KAAqB;IACpC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,cAAc,EAAE,CAAA;AACvD,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,sCAAsC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAA;QACpF,MAAM,GAAG,GAAI,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QACtC,MAAM,GAAG,GAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;QACrD,MAAM,GAAG,GAAI,GAAG,CAAC,OAAO,CACtB,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK;YACtF,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,eAAe,EAAE,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,EAAE,EAC7F,CAAC,GAAQ,EAAE,EAAE;YACX,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,IAAI,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;YAC5C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAA;oBAC1D,OAAO,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE;wBAC3F,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;gBAClF,CAAC;gBAAC,MAAM,CAAC;oBAAC,OAAO,CAAC,IAAI,CAAC,CAAA;gBAAC,CAAC;YAC3B,CAAC,CAAC,CAAA;QACJ,CAAC,CACF,CAAA;QACD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QACpC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAC5D,GAAG,CAAC,GAAG,EAAE,CAAA;IACX,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,WAAmB;IACrD,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAEnC,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,wBAAwB;QACxB,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;QACvB,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QAC5B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,sCAAsC;QACtC,KAAK,qBAAqB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC3D,IAAI,KAAK,EAAE,CAAC;gBACV,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;gBACvB,WAAW,EAAE,CAAA;gBACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;gBAC3B,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,GAAG,CAAC,qBAAqB,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAA;gBACrG,CAAC;gBAAC,MAAM,CAAC,CAAA,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAA;QACF,OAAO,MAAM,CAAA;IACf,CAAC;IAED,mCAAmC;IACnC,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,qBAAqB,WAAW,EAAE,CAAC,CAAA;QACzE,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAmB,CAAA;YACzD,WAAW,EAAE,CAAA;YACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;YAC5B,OAAO,MAAM,CAAA;QACf,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,iDAAiD;IACjD,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,mBAAmB,CAAC,CAAA;IAClD,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,IAAI,KAAK,EAAE,CAAC;QACV,WAAW,EAAE,CAAA;QACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,GAAG,CAAC,qBAAqB,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAA;QACrG,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvB,MAAM,CAAC,GAAG,CAAC,qBAAqB,WAAW,EAAE,EAAE,wBAAwB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACrG,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,EAAE,0BAA0B,CAAC,CAAA;AAC1D,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAA,CAAC,4CAA4C;AACvF,CAAC"}
1
+ {"version":3,"file":"policyCache.js","sourceRoot":"","sources":["../../src/daemon/policyCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Starts periodic status reporting. A daemon with no workspace credential
3
+ * stays silent — an unauthenticated report would only ever 401, which is the
4
+ * 404-heartbeat failure mode this module exists to not repeat.
5
+ */
6
+ export declare function startStatusReporter(): void;
7
+ /**
8
+ * Stops the timer and sends a final `running: false` snapshot so the dashboard
9
+ * flips promptly on clean shutdown instead of waiting out the TTL.
10
+ */
11
+ export declare function stopStatusReporter(): Promise<void>;
12
+ //# sourceMappingURL=statusReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusReporter.d.ts","sourceRoot":"","sources":["../../src/daemon/statusReporter.ts"],"names":[],"mappings":"AAoGA;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAiB1C;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CASxD"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * MCP Daemon Status Reporter — periodic upload to the control plane.
3
+ *
4
+ * The predecessor of this module (uploadSnapshots in healthMonitor.ts, removed
5
+ * in 514eff7c) POSTed to /api/v1/mcp-daemon/health-snapshot, a route that never
6
+ * existed in the control plane, so every heartbeat 404'd. Its removal note said
7
+ * "restore an ingest route before re-adding an upload" — the ingest route now
8
+ * exists (`POST /api/v1/mcp-daemon/report`), and this is the upload half.
9
+ *
10
+ * The workspace credential is attached ONLY to the control-plane request, never
11
+ * to the per-server probes in healthMonitor.ts — those hit third-party MCP
12
+ * servers named in the user's local harness config, and forwarding the Intutic
13
+ * key there would send it to hosts Intutic does not control.
14
+ *
15
+ * Reporting is best-effort and quiet: a control plane that is briefly down
16
+ * costs one snapshot (TTL on the stored key makes staleness self-describing —
17
+ * the dashboard reads an expired key as "not running"). No retry ladder, no
18
+ * disk buffer: unlike hook events, a status snapshot is worthless once the next
19
+ * one exists.
20
+ *
21
+ * LLD #28: MCP Daemon Mode, WS-5MCP
22
+ * @module
23
+ */
24
+ import https from 'node:https';
25
+ import http from 'node:http';
26
+ import { createLogger } from '@intutic/logger';
27
+ import { getHealthSnapshot } from './healthMonitor.js';
28
+ import { getCacheStats } from './policyCache.js';
29
+ import { pendingCount } from './telemetryBatcher.js';
30
+ import { loadPin } from '../tofu.js';
31
+ const logger = createLogger('mcp-proxy.statusReporter');
32
+ const CP_URL = process.env['CONTROL_PLANE_URL'] ?? 'http://localhost:3001';
33
+ const DAEMON_API_KEY = process.env['INTUTIC_API_KEY'] ?? '';
34
+ // Same env var config.ts's loadConfig reads as a fallback for workspaceId
35
+ // (INTUTIC_WORKSPACE_ID, written to ~/.intutic/env/runtime.env by the
36
+ // sync-daemon). A daemon process is scoped to one workspace, so this module
37
+ // reads it directly rather than threading it through every call.
38
+ const WORKSPACE_ID = process.env['INTUTIC_WORKSPACE_ID'] ?? '';
39
+ const REPORT_MS = parseInt(process.env['MCP_DAEMON_STATUS_REPORT_MS'] ?? '60000', 10);
40
+ const startedAt = new Date().toISOString();
41
+ let timer = null;
42
+ /**
43
+ * Enriches each health-probed server with its current TOFU pin fingerprint
44
+ * (tofu.ts), read from the same local `~/.intutic/mcp-pins/` files the proxy
45
+ * itself pins to — so `mcp_servers.pin_fingerprint` (Part 3's ingest upsert)
46
+ * can eventually be cross-referenced with fingerprint history, even though
47
+ * building that cross-referencing view is explicitly out of scope for this
48
+ * phase. `harness`/`transport` are NOT attached here: healthMonitor.ts's
49
+ * `discoverServers` merges servers across harness config paths without
50
+ * recording which one a given entry came from, so there is no honest value
51
+ * to report yet — omitted (optional on the wire schema) rather than guessed.
52
+ */
53
+ async function buildMcpServersPayload() {
54
+ const health = getHealthSnapshot();
55
+ if (!WORKSPACE_ID)
56
+ return health;
57
+ return Promise.all(health.map(async (server) => {
58
+ const pin = await loadPin(WORKSPACE_ID, server.serverName).catch(() => null);
59
+ return pin ? { ...server, pinFingerprint: pin.fingerprint } : server;
60
+ }));
61
+ }
62
+ async function buildSnapshot(running) {
63
+ return JSON.stringify({
64
+ running,
65
+ policyCache: getCacheStats(),
66
+ telemetryBuffer: { pending: pendingCount() },
67
+ mcpServers: await buildMcpServersPayload(),
68
+ startedAt,
69
+ });
70
+ }
71
+ function postReport(body) {
72
+ const url = new URL('/api/v1/mcp-daemon/report', CP_URL);
73
+ const lib = url.protocol === 'https:' ? https : http;
74
+ return new Promise((resolve, reject) => {
75
+ const req = lib.request({ hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
76
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body),
77
+ 'Authorization': `Bearer ${DAEMON_API_KEY}` } }, (res) => {
78
+ res.resume();
79
+ if (res.statusCode && res.statusCode < 400)
80
+ resolve();
81
+ else
82
+ reject(new Error(`HTTP ${res.statusCode}`));
83
+ });
84
+ req.on('error', reject);
85
+ req.setTimeout(5000, () => { req.destroy(); reject(new Error('timeout')); });
86
+ req.write(body);
87
+ req.end();
88
+ });
89
+ }
90
+ /**
91
+ * Starts periodic status reporting. A daemon with no workspace credential
92
+ * stays silent — an unauthenticated report would only ever 401, which is the
93
+ * 404-heartbeat failure mode this module exists to not repeat.
94
+ */
95
+ export function startStatusReporter() {
96
+ if (DAEMON_API_KEY.length === 0) {
97
+ logger.info('status_reporter.disabled_no_api_key');
98
+ return;
99
+ }
100
+ const report = () => {
101
+ buildSnapshot(true)
102
+ .then((body) => postReport(body))
103
+ .catch((err) => {
104
+ logger.debug({ err: err instanceof Error ? err.message : String(err) }, 'status_reporter.report_failed');
105
+ });
106
+ };
107
+ report(); // First snapshot immediately — the panel should not wait a full interval.
108
+ timer = setInterval(report, REPORT_MS);
109
+ timer.unref();
110
+ }
111
+ /**
112
+ * Stops the timer and sends a final `running: false` snapshot so the dashboard
113
+ * flips promptly on clean shutdown instead of waiting out the TTL.
114
+ */
115
+ export async function stopStatusReporter() {
116
+ if (timer) {
117
+ clearInterval(timer);
118
+ timer = null;
119
+ }
120
+ if (DAEMON_API_KEY.length === 0)
121
+ return;
122
+ try {
123
+ const body = await buildSnapshot(false);
124
+ await postReport(body);
125
+ }
126
+ catch {
127
+ // Shutdown path — the TTL covers an unreachable control plane.
128
+ }
129
+ }
130
+ //# sourceMappingURL=statusReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusReporter.js","sourceRoot":"","sources":["../../src/daemon/statusReporter.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,10 @@ export interface HookEvent {
8
8
  }
9
9
  /** Enqueues a hook event. Evicts oldest on overflow. */
10
10
  export declare function enqueueEvent(event: HookEvent): void;
11
+ /** Events awaiting the next flush. Reported upstream by the status reporter. */
12
+ export declare function pendingCount(): number;
11
13
  /** Drains any Valkey or disk buffer from a previous crash, then starts the flush timer. */
12
14
  export declare function startBatcher(): void;
13
- /** Flushes remaining events and stops the timer. */
15
+ /** Flushes remaining events and stops the timer. Never rejects. */
14
16
  export declare function stopBatcher(): Promise<void>;
15
17
  //# sourceMappingURL=telemetryBatcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetryBatcher.d.ts","sourceRoot":"","sources":["../../src/daemon/telemetryBatcher.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAQ,MAAM,CAAA;IACnB,QAAQ,EAAK,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAI,MAAM,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAGnD;AA0DD,2FAA2F;AAC3F,wBAAgB,YAAY,IAAI,IAAI,CA2BnC;AAED,oDAAoD;AACpD,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD"}
1
+ {"version":3,"file":"telemetryBatcher.d.ts","sourceRoot":"","sources":["../../src/daemon/telemetryBatcher.ts"],"names":[],"mappings":"AAuCA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAQ,MAAM,CAAA;IACnB,QAAQ,EAAK,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAI,MAAM,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAGnD;AAED,gFAAgF;AAChF,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAwED,2FAA2F;AAC3F,wBAAgB,YAAY,IAAI,IAAI,CAoDnC;AAED,mEAAmE;AACnE,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjD"}