@lensmcp/cluster 1.18.4 → 1.18.6

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 (64) hide show
  1. package/basic-ssl.js +1 -241
  2. package/build-scope-patterns.js +1 -40
  3. package/create-webpack-dev.js +1 -186
  4. package/create-webpack-prod.js +1 -169
  5. package/executors/build/build.impl.js +1 -98
  6. package/executors/gateway/gateway-errors.js +1 -43
  7. package/executors/gateway/gateway.impl.js +1 -53
  8. package/executors/gateway/gateway.lib.js +1 -29
  9. package/executors/gateway/health-check.js +1 -66
  10. package/executors/gateway/jwks-verify.js +1 -121
  11. package/executors/gateway/main.prod-gateway.js +2 -573
  12. package/executors/gateway/main.rollout.js +11 -117
  13. package/executors/gateway/manifest.js +1 -374
  14. package/executors/gateway/metrics.js +1 -56
  15. package/executors/gateway/otel-tracing.js +1 -74
  16. package/executors/gateway/prod-gateway.lib.js +1 -22
  17. package/executors/gateway/prod-runtime/access-log.js +1 -24
  18. package/executors/gateway/prod-runtime/app.js +1 -123
  19. package/executors/gateway/prod-runtime/auth.js +1 -51
  20. package/executors/gateway/prod-runtime/cors.js +1 -40
  21. package/executors/gateway/prod-runtime/edge.js +1 -65
  22. package/executors/gateway/prod-runtime/handler.js +1 -226
  23. package/executors/gateway/prod-runtime/hooks.js +1 -42
  24. package/executors/gateway/prod-runtime/observability.js +1 -125
  25. package/executors/gateway/prod-runtime/rollout.js +1 -103
  26. package/executors/gateway/prod-runtime/routing.js +1 -40
  27. package/executors/gateway/prod-runtime/server.js +1 -79
  28. package/executors/gateway/prod-runtime/trust.js +1 -32
  29. package/executors/gateway/prod-runtime/types.js +1 -2
  30. package/executors/gateway/prod-runtime/upgrade.js +4 -116
  31. package/executors/gateway/prod-runtime/upstream.js +1 -21
  32. package/executors/gateway/providers-prod.js +1 -232
  33. package/executors/gateway/rate-limit.js +2 -75
  34. package/executors/gateway/registry-source.js +1 -131
  35. package/executors/gateway/rollout-ops.js +2 -167
  36. package/executors/gateway/runtime/auth.js +1 -64
  37. package/executors/gateway/runtime/chooser.js +12 -45
  38. package/executors/gateway/runtime/control.js +1 -128
  39. package/executors/gateway/runtime/dev-auth.js +1 -108
  40. package/executors/gateway/runtime/discovery.js +1 -123
  41. package/executors/gateway/runtime/edge.js +1 -47
  42. package/executors/gateway/runtime/handler.js +1 -183
  43. package/executors/gateway/runtime/hooks.js +1 -55
  44. package/executors/gateway/runtime/lens-children.js +1 -651
  45. package/executors/gateway/runtime/lifecycle.js +3 -842
  46. package/executors/gateway/runtime/observability.js +2 -148
  47. package/executors/gateway/runtime/pod-env.js +2 -89
  48. package/executors/gateway/runtime/proxy.js +1 -457
  49. package/executors/gateway/runtime/route-registry.js +1 -72
  50. package/executors/gateway/runtime/scope.js +1 -117
  51. package/executors/gateway/runtime/server.js +3 -487
  52. package/executors/gateway/runtime/service-keys.js +1 -49
  53. package/executors/gateway/runtime/types.js +1 -151
  54. package/executors/gateway/runtime/upgrade.js +1 -71
  55. package/executors/gateway/runtime/workspace-registry.js +1 -99
  56. package/executors/gateway/ssrf-guard.js +1 -190
  57. package/executors/serve/serve.impl.js +1 -280
  58. package/executors/trust/trust.impl.js +4 -162
  59. package/gateway.js +1 -35
  60. package/index.js +1 -16
  61. package/main.devserver.js +10 -1117
  62. package/package.json +4 -3
  63. package/tsgo-check-plugin.js +4 -364
  64. package/typecheck-bus.js +4 -256
@@ -1,148 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LENS_DASHBOARD_PROJECT = void 0;
4
- exports.createObservability = createObservability;
5
- const tslib_1 = require("tslib");
6
- /**
7
- * The observability layer: everything the gateway writes to the LensMCP bus.
8
- * Bus (`emit`/`eid`), per-request mechanism trace (`beginTrace`/`traceStep`/
9
- * `finishTrace`), and aggregated host→service traffic edges (`recordEdge` +
10
- * `startEdgeFlusher`). Merged because they only ever vary together — trace and
11
- * edges both emit; both need `eid`.
12
- *
13
- * Appends BaseEvent-shaped lines to the shared event file the MCP tails (same
14
- * rendezvous the vite plugin and nest instrumentation use), so the lens sees
15
- * the cluster itself: services up/down, cold starts, autoscale, idle kills, and
16
- * aggregated traffic edges for the topology.
17
- */
18
- const fs = tslib_1.__importStar(require("node:fs"));
19
- const path = tslib_1.__importStar(require("node:path"));
20
- const busHygiene = (() => {
21
- try {
22
- // eslint-disable-next-line @typescript-eslint/no-require-imports
23
- return require('@lensmcp/node-instrumentation');
24
- }
25
- catch {
26
- return undefined;
27
- }
28
- })();
29
- /**
30
- * The lens's OWN dashboard route project (the value `lens-children.ts` gives the `lensmcp.local/<key>/`
31
- * route). Traffic to it is the lens UI polling its own control-plane API (`/<key>/api/cluster|logs|snapshot`)
32
- * ~1×/s — pure self-observation with NO app-diagnostic value. Its SUCCESSFUL request narration + traffic
33
- * edges are suppressed from the bus (the same "don't bloat the bus" rationale as header-less traffic); a 5xx
34
- * still surfaces so a genuine lens-dashboard failure isn't hidden. One home for the literal (imported by
35
- * `lens-children.ts` for the route + this layer for the filter, so they can't drift).
36
- */
37
- exports.LENS_DASHBOARD_PROJECT = '(lens-dashboard)';
38
- const hdr = (v) => typeof v === 'string' && v !== '' ? v : Array.isArray(v) ? v[0] : undefined;
39
- function createObservability(eventFile, opts) {
40
- const eid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 12);
41
- // The gateway narrates OTHER apps' traffic — an app stuck in a retry loop must not turn that
42
- // narration into a bus flood (2026-07-28: `gateway-request:<project>` alone wrote thousands of
43
- // lines/min). Per-fingerprint token bucket: first burst passes, then a trickle carrying
44
- // `floodSuppressed` counts. Errors stay loud — the burst re-arms once the storm subsides.
45
- const floodGuard = busHygiene?.createEventFloodGuard({ burst: 30, perSec: 5 });
46
- const emit = (severity, title, fingerprint, raw, flowCtx, busFile) => {
47
- const target = busFile ?? eventFile; // a registered workspace's service events go to ITS bus, not the daemon's
48
- try {
49
- const admit = floodGuard ? floodGuard.admit(fingerprint) : { ok: true, suppressed: 0 };
50
- if (!admit.ok)
51
- return;
52
- fs.mkdirSync(path.dirname(target), { recursive: true });
53
- const line = JSON.stringify({
54
- id: eid(), sessionId: 'pending', timestamp: Date.now(),
55
- source: 'gateway', category: 'cluster', severity,
56
- context: { sessionId: 'pending', ...flowCtx }, fingerprint, title,
57
- raw: admit.suppressed > 0 ? { ...raw, floodSuppressed: admit.suppressed } : raw,
58
- }) + '\n';
59
- // The gateway was the one bus writer with NO size cap — enforce the same rotation contract
60
- // as every other producer (emit.ts / capture-runner), per target file.
61
- busHygiene?.maybeRotateEventFile(target, line.length);
62
- fs.appendFileSync(target, line);
63
- }
64
- catch { /* lens offline — never block traffic */ }
65
- };
66
- // --- per-request mechanism trace (flows) ----------------------------------
67
- // When a request carries lens flow headers (stamped by the browser client
68
- // runtime or an instrumented upstream), the gateway narrates its own steps —
69
- // received → route-match → auth → pod-select (+ cold-start) → response — as
70
- // ONE `gateway-request` event correlated by the same flowId/requestId.
71
- // Header-less traffic (curl, health checks) stays un-narrated so the bus
72
- // doesn't bloat.
73
- const beginTrace = (req) => {
74
- const flowId = hdr(req.headers['x-lensmcp-flow-id']);
75
- let requestId = hdr(req.headers['x-request-id']);
76
- if (!flowId && !requestId && !hdr(req.headers['traceparent']))
77
- return undefined;
78
- if (!requestId) {
79
- // Mint the request id AT THE EDGE and inject it downstream, so the
80
- // gateway trace and the pod's server-request/spans share one id —
81
- // that's the join key that attributes pod events to the routed service.
82
- requestId = eid();
83
- req.headers['x-request-id'] = requestId;
84
- }
85
- return {
86
- startedAt: Date.now(),
87
- method: (req.method ?? 'GET').toUpperCase(),
88
- host: (req.headers.host || '').split(':')[0],
89
- url: req.url ?? '/',
90
- steps: [],
91
- ...(flowId ? { flowId } : {}),
92
- ...(requestId ? { requestId } : {}),
93
- ...(hdr(req.headers['x-lensmcp-origin-node-id']) ? { originNodeId: hdr(req.headers['x-lensmcp-origin-node-id']) } : {}),
94
- };
95
- };
96
- const traceStep = (t, stepName, info) => {
97
- if (!t)
98
- return;
99
- t.steps.push({ step: stepName, atMs: Date.now() - t.startedAt, ...info });
100
- };
101
- const finishTrace = (t, project, status, extra) => {
102
- if (!t || t.done)
103
- return;
104
- t.done = true;
105
- // The lens dashboard's own control-plane self-polling (1 req/s) would spam the bus with no
106
- // app-diagnostic value — suppress its successful narration, but let a 5xx through (a real lens bug).
107
- if (project === exports.LENS_DASHBOARD_PROJECT && status < 500)
108
- return;
109
- const durationMs = Date.now() - t.startedAt;
110
- emit(status >= 500 ? 'error' : 'info', `gateway ${t.method} ${t.host}${t.url} → ${status} (${durationMs}ms)`, `gateway-request:${project}`, {
111
- kind: 'gateway-request', project, host: t.host, method: t.method, url: t.url,
112
- status, durationMs, startedAt: t.startedAt, steps: t.steps, ...extra,
113
- }, {
114
- ...(t.flowId ? { flowId: t.flowId } : {}),
115
- ...(t.requestId ? { requestId: t.requestId } : {}),
116
- ...(t.originNodeId ? { originNodeId: t.originNodeId } : {}),
117
- });
118
- };
119
- // --- aggregated host→service traffic edges --------------------------------
120
- // The request-derived topology (nodes/edges appear from observed traffic).
121
- const edges = new Map();
122
- const recordEdge = (route, ms, status, caller) => {
123
- // Don't accumulate a traffic edge for the lens dashboard's own self-polling (kills the
124
- // `traffic lensmcp.local → (lens-dashboard)` spam); an error edge (5xx / connect-fail) still records.
125
- if (route.project === exports.LENS_DASHBOARD_PROJECT && status < 500 && status !== 0)
126
- return;
127
- const host = route.host ?? '(default)';
128
- const key = (caller ? caller + '⇒' : '') + host + '→' + route.project;
129
- const e = edges.get(key) ?? { host, project: route.project, caller, count: 0, totalMs: 0, errors: 0 };
130
- e.count += 1;
131
- e.totalMs += ms;
132
- if (status >= 500 || status === 0)
133
- e.errors += 1;
134
- edges.set(key, e);
135
- };
136
- const startEdgeFlusher = () => {
137
- const t = setInterval(() => {
138
- for (const e of edges.values()) {
139
- const avgMs = Math.round(e.totalMs / e.count);
140
- emit(e.errors ? 'warning' : 'info', `traffic ${e.host} → ${e.project}: ${e.count} req, ~${avgMs}ms${e.errors ? `, ${e.errors} errors` : ''}`, `gateway-traffic:${e.host}->${e.project}`, { kind: 'gateway-traffic', host: e.host, project: e.project, caller: e.caller, count: e.count, avgMs, errors: e.errors });
141
- }
142
- edges.clear();
143
- }, opts.trafficFlushMs);
144
- t.unref();
145
- return t;
146
- };
147
- return { emit, eid, beginTrace, traceStep, finishTrace, recordEdge, startEdgeFlusher };
148
- }
1
+ "use strict";var v=Object.defineProperty;var f=(o,d)=>v(o,"name",{value:d,configurable:!0});var _=Object.defineProperty,s=f((o,d)=>_(o,"name",{value:d,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)";const hdr=s(o=>typeof o=="string"&&o!==""?o:Array.isArray(o)?o[0]:void 0,"hdr");function createObservability(o,d){const p=s(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),g=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),l=s((e,t,r,a,i,u)=>{const n=u??o;try{const h=g?g.admit(r):{ok:!0,suppressed:0};if(!h.ok)return;fs.mkdirSync(path.dirname(n),{recursive:!0});const m=a&&(0,redact_1.redactDeep)(a),y=JSON.stringify({id:p(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...i}),fingerprint:r,title:(0,redact_1.safeTitle)(t),raw:h.suppressed>0?{...m,floodSuppressed:h.suppressed}:m})+`
2
+ `;busHygiene?.maybeRotateEventFile(n,y.length),fs.appendFileSync(n,y)}catch{}},"emit"),w=s(e=>{const t=hdr(e.headers["x-lensmcp-flow-id"]);let r=hdr(e.headers["x-request-id"]);if(!(!t&&!r&&!hdr(e.headers.traceparent)))return r||(r=p(),e.headers["x-request-id"]=r),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...t?{flowId:t}:{},...r?{requestId:r}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),S=s((e,t,r)=>{e&&e.steps.push({step:t,atMs:Date.now()-e.startedAt,...r})},"traceStep"),b=s((e,t,r,a)=>{if(!e||e.done||(e.done=!0,t===exports.LENS_DASHBOARD_PROJECT&&r<500))return;const i=Date.now()-e.startedAt;l(r>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${r} (${i}ms)`,`gateway-request:${t}`,{kind:"gateway-request",project:t,host:e.host,method:e.method,url:e.url,status:r,durationMs:i,startedAt:e.startedAt,steps:e.steps,...a},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}})},"finishTrace"),c=new Map;return{emit:l,eid:p,beginTrace:w,traceStep:S,finishTrace:b,recordEdge:s((e,t,r,a)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&r<500&&r!==0)return;const i=e.host??"(default)",u=(a?a+"\u21D2":"")+i+"\u2192"+e.project,n=c.get(u)??{host:i,project:e.project,caller:a,count:0,totalMs:0,errors:0};n.count+=1,n.totalMs+=t,(r>=500||r===0)&&(n.errors+=1),c.set(u,n)},"recordEdge"),startEdgeFlusher:s(()=>{const e=setInterval(()=>{for(const t of c.values()){const r=Math.round(t.totalMs/t.count);l(t.errors?"warning":"info",`traffic ${t.host} \u2192 ${t.project}: ${t.count} req, ~${r}ms${t.errors?`, ${t.errors} errors`:""}`,`gateway-traffic:${t.host}->${t.project}`,{kind:"gateway-traffic",host:t.host,project:t.project,caller:t.caller,count:t.count,avgMs:r,errors:t.errors})}c.clear()},d.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}f(createObservability,"createObservability"),s(createObservability,"createObservability");
@@ -1,89 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FOREIGN_POD_ENV_DENYLIST = exports.WORKSPACE_DOTENV_FILES = void 0;
4
- exports.parseDotenvKeys = parseDotenvKeys;
5
- exports.workspaceDotenvKeys = workspaceDotenvKeys;
6
- exports.scrubEnv = scrubEnv;
7
- exports.podBaseEnv = podBaseEnv;
8
- const tslib_1 = require("tslib");
9
- /**
10
- * FOREIGN-POD ENV SCRUB (the cross-workspace store bleed, foodguard↔tetros 2026-07-26).
11
- *
12
- * `nx run gateway:serve` auto-loads the DAEMON workspace's root dotenv (`.env`, `.env.local`) into the
13
- * daemon's own process env, and the spawn sites' `...process.env` handed that to EVERY child — including a
14
- * REGISTERED (foreign) workspace's pods. nx's dotenv load in the child never overrides an already-set var,
15
- * so the daemon host's values beat the foreign workspace's own `.env.local`: with the daemon hosted from
16
- * foodguard (whose root `.env.local` declares `PG_URL`/`REDIS_URL`), tetros's auth+iam pods booted onto
17
- * foodguard's Postgres/Valkey — foodguard tenants rendered inside the tetros app, and tetros's boot seed
18
- * wrote rows into foodguard's database.
19
- *
20
- * The rule: a FOREIGN workspace's pod gets the inherited env MINUS (every key the daemon workspace's root
21
- * dotenv files declare ∪ a hard denylist of data-store pointers). The daemon's OWN workspace pods keep the
22
- * full inherit — their nx/vite reload the exact same files at spawn (so the scrub would change nothing),
23
- * and a deliberate shell override (`PG_URL=… lensmcp gateway start`) staying live for one's own pods is
24
- * ordinary env semantics. Hosting order must never matter for anyone else's pods.
25
- */
26
- const fs = tslib_1.__importStar(require("node:fs"));
27
- const path = tslib_1.__importStar(require("node:path"));
28
- /** The root-level dotenv files nx (and vite) auto-load for any invocation at a workspace root. */
29
- exports.WORKSPACE_DOTENV_FILES = ['.env', '.env.local', '.local.env'];
30
- /**
31
- * Data-store pointers that must never cross a workspace boundary, even when they arrived in the daemon's
32
- * env some way other than the host's dotenv (an exported shell var at gateway start). Belt-and-suspenders
33
- * under the dotenv-derived scrub — kept to the class that caused the incident.
34
- */
35
- exports.FOREIGN_POD_ENV_DENYLIST = ['PG_URL', 'REDIS_URL', 'DATABASE_URL', 'POSTGRES_URL'];
36
- /**
37
- * The keys a dotenv file DECLARES — assignment lines only (`KEY=…`, optional `export ` prefix), comments
38
- * and malformed lines ignored. We only need the key names (what to scrub), never the values.
39
- */
40
- function parseDotenvKeys(content) {
41
- const keys = new Set();
42
- for (const raw of content.split('\n')) {
43
- const line = raw.trim();
44
- if (!line || line.startsWith('#'))
45
- continue;
46
- const m = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
47
- if (m)
48
- keys.add(m[1]);
49
- }
50
- return keys;
51
- }
52
- /** Every key declared across a workspace root's dotenv files (missing/unreadable files contribute nothing). */
53
- function workspaceDotenvKeys(root) {
54
- const keys = new Set();
55
- for (const file of exports.WORKSPACE_DOTENV_FILES) {
56
- let content;
57
- try {
58
- content = fs.readFileSync(path.join(root, file), 'utf8');
59
- }
60
- catch {
61
- continue;
62
- }
63
- for (const k of parseDotenvKeys(content))
64
- keys.add(k);
65
- }
66
- return keys;
67
- }
68
- /** PURE scrub: a copy of `base` without the given keys nor the store-pointer denylist. Never mutates `base`. */
69
- function scrubEnv(base, hostKeys) {
70
- const out = {};
71
- for (const [k, v] of Object.entries(base)) {
72
- if (hostKeys.has(k))
73
- continue;
74
- if (exports.FOREIGN_POD_ENV_DENYLIST.includes(k))
75
- continue;
76
- out[k] = v;
77
- }
78
- return out;
79
- }
80
- /**
81
- * The spawn-base env for a child of `childRoot` under a daemon hosted at `hostRoot`: the daemon's own
82
- * workspace inherits untouched; a foreign workspace gets the scrubbed copy (the child's own nx/vite dotenv
83
- * load then supplies THAT workspace's values, which is the whole point).
84
- */
85
- function podBaseEnv(base, hostRoot, childRoot) {
86
- if (path.resolve(childRoot) === path.resolve(hostRoot))
87
- return base;
88
- return scrubEnv(base, workspaceDotenvKeys(hostRoot));
89
- }
1
+ "use strict";var p=Object.defineProperty;var a=(o,e)=>p(o,"name",{value:e,configurable:!0});var c=Object.defineProperty,s=a((o,e)=>c(o,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.FOREIGN_POD_ENV_DENYLIST=exports.WORKSPACE_DOTENV_FILES=void 0,exports.parseDotenvKeys=parseDotenvKeys,exports.workspaceDotenvKeys=workspaceDotenvKeys,exports.scrubEnv=scrubEnv,exports.podBaseEnv=podBaseEnv;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.WORKSPACE_DOTENV_FILES=[".env",".env.local",".local.env"],exports.FOREIGN_POD_ENV_DENYLIST=["PG_URL","REDIS_URL","DATABASE_URL","POSTGRES_URL"];function parseDotenvKeys(o){const e=new Set;for(const r of o.split(`
2
+ `)){const t=r.trim();if(!t||t.startsWith("#"))continue;const n=t.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);n&&e.add(n[1])}return e}a(parseDotenvKeys,"parseDotenvKeys"),s(parseDotenvKeys,"parseDotenvKeys");function workspaceDotenvKeys(o){const e=new Set;for(const r of exports.WORKSPACE_DOTENV_FILES){let t;try{t=fs.readFileSync(path.join(o,r),"utf8")}catch{continue}for(const n of parseDotenvKeys(t))e.add(n)}return e}a(workspaceDotenvKeys,"workspaceDotenvKeys"),s(workspaceDotenvKeys,"workspaceDotenvKeys");function scrubEnv(o,e){const r={};for(const[t,n]of Object.entries(o))e.has(t)||exports.FOREIGN_POD_ENV_DENYLIST.includes(t)||(r[t]=n);return r}a(scrubEnv,"scrubEnv"),s(scrubEnv,"scrubEnv");function podBaseEnv(o,e,r){return path.resolve(r)===path.resolve(e)?o:scrubEnv(o,workspaceDotenvKeys(e))}a(podBaseEnv,"podBaseEnv"),s(podBaseEnv,"podBaseEnv");