@intutic/mcp-governance-proxy 1.10.0 → 2.0.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.
- package/README.md +28 -0
- package/dist/anomaly/detectors.d.ts +129 -0
- package/dist/anomaly/detectors.d.ts.map +1 -0
- package/dist/anomaly/detectors.js +368 -0
- package/dist/anomaly/detectors.js.map +1 -0
- package/dist/anomaly/index.d.ts +52 -0
- package/dist/anomaly/index.d.ts.map +1 -0
- package/dist/anomaly/index.js +71 -0
- package/dist/anomaly/index.js.map +1 -0
- package/dist/config.d.ts +107 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +129 -6
- package/dist/config.js.map +1 -1
- package/dist/daemon/healthMonitor.d.ts.map +1 -1
- package/dist/daemon/healthMonitor.js +45 -46
- package/dist/daemon/healthMonitor.js.map +1 -1
- package/dist/daemon/index.d.ts +15 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +78 -9
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/instrumentation.d.ts +20 -0
- package/dist/daemon/instrumentation.d.ts.map +1 -0
- package/dist/daemon/instrumentation.js +61 -0
- package/dist/daemon/instrumentation.js.map +1 -0
- package/dist/daemon/policyCache.d.ts +83 -0
- package/dist/daemon/policyCache.d.ts.map +1 -1
- package/dist/daemon/policyCache.js +280 -11
- package/dist/daemon/policyCache.js.map +1 -1
- package/dist/daemon/statusReporter.d.ts +12 -0
- package/dist/daemon/statusReporter.d.ts.map +1 -0
- package/dist/daemon/statusReporter.js +130 -0
- package/dist/daemon/statusReporter.js.map +1 -0
- package/dist/daemon/telemetryBatcher.d.ts +3 -1
- package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
- package/dist/daemon/telemetryBatcher.js +56 -13
- package/dist/daemon/telemetryBatcher.js.map +1 -1
- package/dist/daemonClient.d.ts +1 -1
- package/dist/daemonClient.d.ts.map +1 -1
- package/dist/daemonClient.js +0 -2
- package/dist/daemonClient.js.map +1 -1
- package/dist/dlp.d.ts +19 -0
- package/dist/dlp.d.ts.map +1 -1
- package/dist/dlp.js +95 -18
- package/dist/dlp.js.map +1 -1
- package/dist/emitter.d.ts +40 -2
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +27 -5
- package/dist/emitter.js.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/injection.d.ts +46 -0
- package/dist/injection.d.ts.map +1 -0
- package/dist/injection.js +147 -0
- package/dist/injection.js.map +1 -0
- package/dist/interceptor.d.ts +131 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +301 -5
- package/dist/interceptor.js.map +1 -1
- package/dist/policy.d.ts +110 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +206 -5
- package/dist/policy.js.map +1 -1
- package/dist/proxy.d.ts +205 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +572 -65
- package/dist/proxy.js.map +1 -1
- package/dist/remoteBridge.d.ts +51 -0
- package/dist/remoteBridge.d.ts.map +1 -0
- package/dist/remoteBridge.js +182 -0
- package/dist/remoteBridge.js.map +1 -0
- package/dist/session.d.ts +158 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +206 -0
- package/dist/session.js.map +1 -0
- package/dist/sessionScope.d.ts +51 -0
- package/dist/sessionScope.d.ts.map +1 -0
- package/dist/sessionScope.js +94 -0
- package/dist/sessionScope.js.map +1 -0
- package/dist/sessionStore.d.ts +69 -0
- package/dist/sessionStore.d.ts.map +1 -0
- package/dist/sessionStore.js +143 -0
- package/dist/sessionStore.js.map +1 -0
- package/dist/tofu.d.ts +112 -0
- package/dist/tofu.d.ts.map +1 -0
- package/dist/tofu.js +180 -0
- package/dist/tofu.js.map +1 -0
- package/dist/toolPoison.d.ts +32 -0
- package/dist/toolPoison.d.ts.map +1 -0
- package/dist/toolPoison.js +81 -0
- package/dist/toolPoison.js.map +1 -0
- package/dist/valkeyErrors.d.ts +17 -0
- package/dist/valkeyErrors.d.ts.map +1 -0
- package/dist/valkeyErrors.js +24 -0
- package/dist/valkeyErrors.js.map +1 -0
- package/dist/wasm/context.d.ts +57 -0
- package/dist/wasm/context.d.ts.map +1 -0
- package/dist/wasm/context.js +59 -0
- package/dist/wasm/context.js.map +1 -0
- package/dist/wasm/hostImports.d.ts +43 -0
- package/dist/wasm/hostImports.d.ts.map +1 -0
- package/dist/wasm/hostImports.js +158 -0
- package/dist/wasm/hostImports.js.map +1 -0
- package/dist/wasm/loader.d.ts +95 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +198 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/referencedFiles.d.ts +115 -0
- package/dist/wasm/referencedFiles.d.ts.map +1 -0
- package/dist/wasm/referencedFiles.js +366 -0
- package/dist/wasm/referencedFiles.js.map +1 -0
- package/dist/wasm/runner.d.ts +91 -0
- package/dist/wasm/runner.d.ts.map +1 -0
- package/dist/wasm/runner.js +308 -0
- package/dist/wasm/runner.js.map +1 -0
- package/dist/wasm/watch.d.ts +5 -0
- package/dist/wasm/watch.d.ts.map +1 -0
- package/dist/wasm/watch.js +54 -0
- package/dist/wasm/watch.js.map +1 -0
- package/dist/wasm/worker.d.ts +39 -0
- package/dist/wasm/worker.d.ts.map +1 -0
- package/dist/wasm/worker.js +245 -0
- package/dist/wasm/worker.js.map +1 -0
- 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
|
|
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', (
|
|
146
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
47
147
|
res.on('end', () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,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;AAGF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;IAChC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,uBAAuB,CAAC,GAAG,CAAC,EAAE,EAAE,qCAAqC,CAAC,CAAA;AAC3F,CAAC,CAAC,CAAA;AAyDF,8FAA8F;AAC9F,MAAM,gBAAgB,GAAG,CAAC,WAAmB,EAAE,EAAE,CAAC,0BAA0B,WAAW,EAAE,CAAA;AAEzF,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IAClD,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,yEAAyE;IACzE,6EAA6E;IAC7E,6CAA6C;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,SAAS,CAAA;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAA;QAC3D,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,SAAS,CAAA;QAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QACrB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,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,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAiBD;;;;;;;;;;GAUG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACtC,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,MAAM,QAAQ,GAAoB,MAAM,CAAC,UAAU,CAAC,CAAA;IACpD,MAAM,WAAW,GAAiB,MAAM,CAAC,aAAa,CAAC,CAAA;IACvD,MAAM,gBAAgB,GAAY,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAC5D,MAAM,YAAY,GAAgB,MAAM,CAAC,cAAc,CAAC,CAAA;IACxD,MAAM,wBAAwB,GAAI,MAAM,CAAC,0BAA0B,CAAC,CAAA;IACpE,MAAM,cAAc,GAAc,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAC1D,MAAM,kBAAkB,GAAU,MAAM,CAAC,oBAAoB,CAAC,CAAA;IAC9D,MAAM,oBAAoB,GAAQ,MAAM,CAAC,sBAAsB,CAAC,CAAA;IAChE,MAAM,cAAc,GAAc,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAC1D,MAAM,sBAAsB,GAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;IAE/D,OAAO;QACL,QAAQ,EAAK,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QACrE,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;YACrC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YAC/D,CAAC,CAAC,EAAE;QACN,oEAAoE;QACpE,mEAAmE;QACnE,uEAAuE;QACvE,uEAAuE;QACvE,oEAAoE;QACpE,iDAAiD;QACjD,gBAAgB,EAAE,OAAO,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,aAAa;QACzF,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;YACvC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YAChE,CAAC,CAAC,EAAE;QACN,wBAAwB,EAAE,QAAQ,CAAC,wBAAwB,CAAC;YAC1D,CAAC,CAAC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,MAAM,CAC7C,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CACnE,CACF;YACH,CAAC,CAAC,EAAE;QACN,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;YAC3C,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YAClE,CAAC,CAAC,EAAE;QACN,kBAAkB,EAChB,kBAAkB,KAAK,MAAM,IAAI,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS;QAClG,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,oBAAoB,CAAC;YACvD,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YACxE,CAAC,CAAC,EAAE;QACN,cAAc,EACZ,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,MAAM,IAAI,cAAc,KAAK,KAAK;YACnF,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,SAAS;QACf,mBAAmB,EAAE,QAAQ,CAAC,sBAAsB,CAAC;YACnD,CAAC,CAAC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,MAAM,CAC3C,CAAC,KAAK,EAAyD,EAAE,CAC/D,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAC5F,CACF;YACH,CAAC,CAAC,EAAE;KACP,CAAA;AACH,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IACtD,2EAA2E;IAC3E,4DAA4D;IAC5D,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAA;IAC3D,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,GAAyB,EAAE,EAAE;YAC5B,qEAAqE;YACrE,wEAAwE;YACxE,wEAAwE;YACxE,uEAAuE;YACvE,iEAAiE;YACjE,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YACvB,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAA;YACtC,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAA,CAAC,CAAC,CAAC,CAAA;YACpD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,uEAAuE;gBACvE,6DAA6D;gBAC7D,8DAA8D;gBAC9D,mEAAmE;gBACnE,mEAAmE;gBACnE,6DAA6D;gBAC7D,kEAAkE;gBAClE,SAAS;gBACT,IAAI,UAAU,GAAG,GAAG,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;oBAC1C,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,+BAA+B,CAAC,CAAA;oBACzE,OAAO,CAAC,IAAI,CAAC,CAAA;oBACb,OAAM;gBACR,CAAC;gBACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;gBACxC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,EAAE,qCAAqC,CAAC,CAAA;oBACnE,OAAO,CAAC,IAAI,CAAC,CAAA;oBACb,OAAM;gBACR,CAAC;gBACD,OAAO,CAAC;oBACN,WAAW;oBACX,QAAQ,EAAU,MAAM,CAAC,QAAQ;oBACjC,WAAW,EAAO,MAAM,CAAC,WAAW;oBACpC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;oBACzC,YAAY,EAAM,MAAM,CAAC,YAAY;oBACrC,wBAAwB,EAAE,MAAM,CAAC,wBAAwB;oBACzD,cAAc,EAAI,MAAM,CAAC,cAAc;oBACvC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;oBAC7C,oBAAoB,EAAE,MAAM,CAAC,oBAAoB;oBACjD,cAAc,EAAI,MAAM,CAAC,cAAc;oBACvC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;oBAC/C,QAAQ,EAAU,IAAI,CAAC,GAAG,EAAE;oBAC5B,aAAa,EAAK,cAAc;iBACjC,CAAC,CAAA;YACJ,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,YAAqB;IAC1D,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACrD,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;QACtC,MAAM,IAAI,GACR,YAAY;YACZ,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAA;QAEtE,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;QACjE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAA;QAElC,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;QACzC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAA;QAEhE,uEAAuE;QACvE,sEAAsE;QACtE,yEAAyE;QACzE,wEAAwE;QACxE,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,wCAAwC,CAAC,CAAA;YAChE,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;QACzC,MAAM,QAAQ,GACZ,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACvE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;YACzB,CAAC,CAAC,CAAC,CAAA,CAAC,uDAAuD;QAE/D,MAAM,KAAK,GAAmB;YAC5B,WAAW;YACX,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC9B,WAAW,EAAE,EAAE;YACf,gBAAgB,EACd,OAAO,MAAM,CAAC,kBAAkB,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,aAAa;YAC7F,8EAA8E;YAC9E,sEAAsE;YACtE,wEAAwE;YACxE,qEAAqE;YACrE,wEAAwE;YACxE,kBAAkB;YAClB,oBAAoB,EAAE,EAAE;YACxB,YAAY,EAAE,EAAE;YAChB,wBAAwB,EAAE,EAAE;YAC5B,cAAc,EAAE,EAAE;YAClB,mBAAmB,EAAE,EAAE;YACvB,QAAQ;SACT,CAAA;QAED,WAAW,EAAE,CAAA;QACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QAC3B,MAAM,CAAC,IAAI,CACT,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EACjG,mCAAmC,CACpC,CAAA;QACD,OAAO,WAAW,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,2EAA2E;QAC3E,OAAO,IAAI,CAAA;IACb,CAAC;AACH,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,uEAAuE;QACvE,yEAAyE;QACzE,uEAAuE;QACvE,+DAA+D;QAC/D,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAA;YACpD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM,CAAC,aAAa,EAAE,CAAC;gBAC9D,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAA;gBACtD,IAAI,KAAK,EAAE,CAAC;oBACV,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;oBACvB,WAAW,EAAE,CAAA;oBACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;oBAC3B,IAAI,CAAC;wBACH,MAAM,MAAM,CAAC,GAAG,CAAC,qBAAqB,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAA;oBACrG,CAAC;oBAAC,MAAM,CAAC;wBACP,oDAAoD;oBACtD,CAAC;oBACD,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;QACH,CAAC;QACD,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;oBACP,kEAAkE;oBAClE,mEAAmE;oBACnE,sEAAsE;oBACtE,oEAAoE;oBACpE,kEAAkE;oBAClE,sEAAsE;gBACxE,CAAC;YACH,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,mEAAmE;YACnE,sEAAsE;YACtE,oEAAoE;YACpE,2DAA2D;YAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CACiE,CAAA;YACpG,MAAM,MAAM,GAAmB;gBAC7B,GAAG,GAAG;gBACN,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;gBACrE,wBAAwB,EAAE,QAAQ,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE;gBACpG,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE;gBAC3E,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE;gBAC7F,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE;aACtF,CAAA;YACD,WAAW,EAAE,CAAA;YACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;YAC5B,OAAO,MAAM,CAAA;QACf,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,2EAA2E;QAC3E,2EAA2E;QAC3E,uEAAuE;QACvE,0BAA0B;IAC5B,CAAC;IAED,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;YACP,6DAA6D;YAC7D,sEAAsE;YACtE,wEAAwE;YACxE,+DAA+D;QACjE,CAAC;IACH,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"}
|
|
@@ -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":"
|
|
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"}
|