@quolu/lattice 0.79.3 → 0.79.4

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 CHANGED
@@ -159,8 +159,14 @@ npm install -g @quolu/lattice
159
159
  lattice setup --json
160
160
  ```
161
161
 
162
- Requires **Node.js 22.13 or newer, except 25.x** (`engines: >=22.13 <25 || >=26`; Node 25's V8
163
- turboshaft WASM JIT breaks the bundled sensor, so it is blocked with a banner). The structure sensor ships inside the package — there is nothing
162
+ Requires **Node.js 22.13 or newer** with the supported runtime ranges
163
+ `>=22.13 <24.2 || >=24.22 <25 || >=26.10`. Node 24.2–24.21 and 26.0–26.9
164
+ contain the [upstream cppgc Realm lifetime defect](https://github.com/nodejs/node/pull/65778),
165
+ which can corrupt the heap during worker teardown. Use 24.22.0+ or 26.10.0+ on
166
+ those release lines; Node 22 LTS remains supported. Node 25.x remains excluded
167
+ because its V8 WASM JIT can crash while compiling tree-sitter grammars.
168
+
169
+ The structure sensor ships inside the package — there is nothing
164
170
  else to install, and Lattice never falls back to a sensor on your `PATH`.
165
171
 
166
172
  `lattice setup`は既存のClaude・Codex・Grok・Cursor設定を検出し、LatticeのMCP登録、
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { readBridgeConfig } from '../src/bridge-config.mjs';
4
4
  import {
5
- bridgeDaemonVersionDrifted, readBridgeStopRequest, removeBridgeDaemonActiveMarker,
6
- removeBridgeDaemonDescriptor, writeBridgeDaemonDescriptor, writeBridgeStopReceipt,
5
+ bridgeDaemonVersionDrifted, completeBridgeDaemonStop, readBridgeStopRequest,
6
+ writeBridgeDaemonDescriptor,
7
7
  } from '../src/bridge-daemon.mjs';
8
8
  import { createBridgeHubHeartbeatController } from '../src/bridge-hub-heartbeat.mjs';
9
9
  import { migrateBridgeToHub, retireBridgeTunnelLaunchAgent } from '../src/bridge-hub-migration.mjs';
@@ -31,25 +31,29 @@ if (typeof instanceToken !== 'string' || !/^[0-9a-f]{64}$/u.test(instanceToken))
31
31
  const controller = bridgeRuntimeController({ env, instanceToken,
32
32
  heartbeat: () => hubHeartbeat.lastHeartbeatSummary() });
33
33
  let timer;
34
- let closing = false;
34
+ let closing = null;
35
+ let stopping = null;
35
36
  let descriptorFingerprint = null;
36
37
  let heartbeatBinding = null;
37
38
  let checking = false;
38
39
  let failClosedError = null;
39
40
  let hubHeartbeatError = null;
40
- const close = async () => {
41
- if (closing) return;
42
- closing = true;
43
- clearInterval(timer);
44
- await controller.close();
41
+ const close = () => {
42
+ if (closing === null) {
43
+ clearInterval(timer);
44
+ closing = controller.close();
45
+ }
46
+ return closing;
47
+ };
48
+ const stop = () => {
49
+ if (stopping === null) stopping = completeBridgeDaemonStop({ env, close });
50
+ return stopping;
45
51
  };
46
52
 
47
53
  try {
48
54
  const stopRequest = await readBridgeStopRequest({ env });
49
55
  if (stopRequest !== null) {
50
- await writeBridgeStopReceipt({ nonce: stopRequest.nonce, env });
51
- await removeBridgeDaemonDescriptor({ env });
52
- await removeBridgeDaemonActiveMarker({ env });
56
+ await stop();
53
57
  process.exit(0);
54
58
  }
55
59
  const config = await readBridgeConfig({ env });
@@ -64,15 +68,12 @@ try {
64
68
  }
65
69
 
66
70
  timer = setInterval(async () => {
67
- if (checking || closing) return;
71
+ if (checking || closing !== null || stopping !== null) return;
68
72
  checking = true;
69
73
  try {
70
74
  const stopRequest = await readBridgeStopRequest({ env });
71
75
  if (stopRequest !== null) {
72
- await close();
73
- await writeBridgeStopReceipt({ nonce: stopRequest.nonce, env });
74
- await removeBridgeDaemonDescriptor({ env });
75
- await removeBridgeDaemonActiveMarker({ env });
76
+ await stop();
76
77
  process.exit(0);
77
78
  }
78
79
  // A stale-version exit is a clean stop, not a failure: whatever supervises
@@ -83,9 +84,7 @@ timer = setInterval(async () => {
83
84
  if (Date.now() - lastVersionCheckAt >= BACKGROUND_CHECK_INTERVAL_MS) {
84
85
  lastVersionCheckAt = Date.now();
85
86
  if (await bridgeDaemonVersionDrifted({})) {
86
- await close();
87
- await removeBridgeDaemonDescriptor({ env });
88
- await removeBridgeDaemonActiveMarker({ env });
87
+ await stop();
89
88
  process.exit(0);
90
89
  }
91
90
  }
@@ -122,9 +121,7 @@ timer = setInterval(async () => {
122
121
  }
123
122
  const config = await readBridgeConfig({ env });
124
123
  if (config === null || !config.enabled) {
125
- await close();
126
- await removeBridgeDaemonDescriptor({ env });
127
- await removeBridgeDaemonActiveMarker({ env });
124
+ await stop();
128
125
  process.exit(0);
129
126
  }
130
127
  const binding = await controller.reconcile();
@@ -169,10 +166,20 @@ timer = setInterval(async () => {
169
166
  const fingerprint = JSON.stringify(failure);
170
167
  if (fingerprint !== failClosedError) process.stderr.write(`${fingerprint}\n`);
171
168
  failClosedError = fingerprint;
169
+ if (stopping !== null) {
170
+ // A failed terminal transition cannot be acknowledged or turned into exit 0.
171
+ clearInterval(timer);
172
+ process.exit(1);
173
+ }
172
174
  // Public traffic fails closed immediately. Keep only the local control loop alive so an
173
175
  // authenticated nonce stop request can still receive a socket-closed receipt.
174
176
  await controller.close();
175
177
  } finally { checking = false; }
176
178
  }, 250);
177
- process.once('SIGINT', () => close().finally(() => process.exit(0)));
178
- process.once('SIGTERM', () => close().finally(() => process.exit(0)));
179
+ const stopOnSignal = () => stop().then(() => process.exit(0), (error) => {
180
+ process.stderr.write(`${JSON.stringify({ schema: 'lattice.bridge_daemon_error.v1',
181
+ code: error?.code ?? 'BRIDGE_DAEMON_STOP_FAILED', message: 'bridge daemon did not complete shutdown' })}\n`);
182
+ process.exit(1);
183
+ });
184
+ process.once('SIGINT', stopOnSignal);
185
+ process.once('SIGTERM', stopOnSignal);
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { superviseDashboard } from '../src/dashboard-supervisor.mjs';
4
+
5
+ try {
6
+ const root = process.env.LATTICE_DASHBOARD_RUNTIME_DIR;
7
+ if (process.argv.length !== 3 || process.argv[2] !== root || !path.isAbsolute(root)) {
8
+ throw Object.assign(new Error('invalid supervisor runtime'), { code: 'DASHBOARD_SUPERVISOR_ARGUMENT_INVALID' });
9
+ }
10
+ await superviseDashboard({ args: [path.resolve(import.meta.dirname, 'lattice-dashboard.mjs'), '--supervised-runtime', root],
11
+ env: process.env, root: process.env.LATTICE_DASHBOARD_RUNTIME_DIR,
12
+ runId: process.env.LATTICE_DASHBOARD_LIFECYCLE_RUN });
13
+ } catch (error) {
14
+ const code = /^[A-Z_]+$/u.test(error.code ?? '') ? error.code : 'DASHBOARD_SUPERVISOR_FAILED';
15
+ if (process.connected) process.send({ type: 'supervisor_error', code }, () => process.disconnect());
16
+ process.stderr.write(`${JSON.stringify({ code })}\n`);
17
+ process.exitCode = 1;
18
+ }
@@ -14,8 +14,11 @@ import {
14
14
  createTodoGanttProjectRegistry,
15
15
  startTodoGanttDashboardServer,
16
16
  } from '../src/todo-gantt-live.mjs';
17
+ import { dashboardLifecyclePhase } from '../src/dashboard-lifecycle-ipc.mjs';
17
18
 
18
19
  const args = process.argv.slice(2);
20
+ if (args.length === 2 && args[0] === '--supervised-runtime' && process.connected
21
+ && args[1] === process.env.LATTICE_DASHBOARD_RUNTIME_DIR) args.splice(0, 2);
19
22
  if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
20
23
  process.stdout.write('Usage: lattice-dashboard\n');
21
24
  process.exit(0);
@@ -40,8 +43,10 @@ const storeCache = createTodoStoreCache();
40
43
  const readCachedStore = (repoRoot) => storeCache.read(repoRoot);
41
44
 
42
45
  async function synchronize() {
46
+ dashboardLifecyclePhase('registry');
43
47
  const active = await readVisibleTodoDashboardProjects({ env,
44
48
  projectHasActiveRun: async (entry) => {
49
+ dashboardLifecyclePhase('store');
45
50
  try {
46
51
  const store = await readCachedStore(entry.repo_root);
47
52
  const active = projectTodoStatus(store, TODO_STATUS_DISPATCH_ONLY).active_set.length > 0
@@ -51,8 +56,10 @@ async function synchronize() {
51
56
  } catch (error) {
52
57
  if (!reportedStoreReadFailures.has(entry.project_id)) {
53
58
  reportedStoreReadFailures.add(entry.project_id);
54
- console.error(JSON.stringify({ schema: 'lattice.dashboard_project_store_error.v1',
55
- project_id: entry.project_id, message: error instanceof Error ? error.message : String(error) }));
59
+ // This finite stage survives in the parent's receipt without project/error text.
60
+ // A lost supervisor leaves no stderr reader; do not create an EPIPE failure
61
+ // in an otherwise serving daemon by writing to its abandoned pipe.
62
+ dashboardLifecyclePhase('store_read_failed');
56
63
  }
57
64
  return false;
58
65
  }
@@ -103,28 +110,43 @@ let closing = null;
103
110
  */
104
111
  function close() {
105
112
  if (closing === null) {
113
+ dashboardLifecyclePhase('close');
106
114
  clearInterval(timer);
107
115
  closing = (async () => {
108
- try { await forgetTodoDashboardDaemonRecord({ env }); } catch {}
109
- await dashboard.close();
116
+ let cleanupFailure = false;
117
+ try { await forgetTodoDashboardDaemonRecord({ env }); }
118
+ catch { cleanupFailure = true; dashboardLifecyclePhase('cleanup_failed'); }
119
+ try { await dashboard.close(); }
120
+ catch (error) { dashboardLifecyclePhase('close_failed'); throw error; }
121
+ if (cleanupFailure) throw new Error('dashboard record cleanup failed');
110
122
  })();
111
123
  }
112
124
  return closing;
113
125
  }
114
126
 
115
127
  function exit(code) {
116
- close().catch(() => {}).finally(() => process.exit(code));
128
+ close().then(() => process.exit(code), () => process.exit(code === 0 ? 1 : code));
117
129
  }
118
130
 
119
131
  await synchronize();
132
+ dashboardLifecyclePhase('listen');
120
133
  const dashboard = await startTodoGanttDashboardServer({
121
134
  registry,
122
135
  port,
123
- onShutdown: process.platform === 'win32' ? () => exit(0) : null,
136
+ onShutdown: process.platform === 'win32' ? () => {
137
+ dashboardLifecyclePhase('shutdown_request'); exit(0);
138
+ } : null,
124
139
  });
125
140
  await writeTodoDashboardDaemonRecord({ port: dashboard.port, env });
141
+ dashboardLifecyclePhase('ready');
126
142
  const timer = setInterval(() => synchronize().then((count) => {
127
- if (count === 0) exit(0);
128
- }).catch(() => exit(1)), 1_000);
129
- process.once('SIGINT', () => exit(0));
130
- process.once('SIGTERM', () => exit(0));
143
+ dashboardLifecyclePhase('periodic_sync');
144
+ if (count === 0) { dashboardLifecyclePhase('empty_shutdown'); exit(0); }
145
+ }).catch(() => { dashboardLifecyclePhase('sync_failed'); exit(1); }), 1_000);
146
+ process.on('message', (message) => {
147
+ if (message?.type === 'dashboard_shutdown') {
148
+ dashboardLifecyclePhase('shutdown_request'); exit(0);
149
+ }
150
+ });
151
+ process.once('SIGINT', () => { dashboardLifecyclePhase('shutdown_request'); exit(0); });
152
+ process.once('SIGTERM', () => { dashboardLifecyclePhase('shutdown_request'); exit(0); });
@@ -119,6 +119,35 @@ bridgeが無効な間は以下すべてnullで、bridgeを有効にしている
119
119
  | `node_path`・`node_version` | そのprocessを実行しているNodeの実体pathと版 |
120
120
  | `bridge_path` | そのprocessが実行しているbridge script |
121
121
 
122
+ **`dashboard_lifecycle`** — `bridge status --json`の読み取り専用の終了記録。
123
+ `lattice.dashboard_lifecycle_status.v1`の`last_ensure`に起動・再利用・無応答拒否の判断、
124
+ `active`に監視中のrun、`last_termination`に最後の実終了または監督消失を返す。
125
+ 読み取りでdaemonを起動したり欠測を補完したりしない。未導入の旧daemonには記録が無い。
126
+
127
+ 通常のdashboard起動は小さいsupervisorが子をwaitし、実終了の観測時刻・codeまたはsignal・最後の段階を
128
+ 所有runtimeの`lifecycle/`へ保存する。POSIXはprivate directory/file、Windowsは既存storeの所有境界を使う。
129
+ 64run、各16KiB・64段階イベントまでとし、activeや生存中の監視外childは削除しない。閉じた古いrunから
130
+ 整理し、満杯なら記録を捨てて起動を続けずtyped容量エラーで止める。stderr本文やproject本文は保存せず、
131
+ byte数と先頭64KiBまでのdigestを残す。中断された一時fileも上限内で扱い、所有writerの死亡確認後に除去する。
132
+ Windowsで記録の置換が一時的に拒否された場合は、所有pathを再検証しながら短い期限内だけ公開を待つ。
133
+ 解除されなければ元のfilesystem errorを伴う`DASHBOARD_LIFECYCLE_PUBLISH_FAILED`を返し、旧完全記録を保持する。
134
+
135
+ Windowsの`observed_exit`は実waitで得たDWORDの`uint32`と`hex`を保持し、signalは`not_applicable`とする。
136
+ `0xC0000005`等の値だけでは例外と外部TerminateProcessを区別できない。`termination_request`は自分のIPC終了要求と、
137
+ FILETIMEで認証したhandleへのTerminateProcess要求・実GetExitCodeProcess結果を別に記録する。
138
+ Nodeの疑似SIGTERMしか残らない場合はraw値を得た扱いにしない。
139
+
140
+ supervisorだけがKILLなどで消えた場合、childは残して配信を継続する。Windowsではchildをdetachedで起動し、
141
+ supervisorが所有するJobの終了に連動させない。通常の停止要求はIPCで送り、実childの終了をwaitする。
142
+ 次の通常ensureはchildのhealthと
143
+ identityを確認し、同じdaemonを再利用する。監督消失後の過去の終了値・終了時刻はunknownのまま残す。
144
+ OS再起動などでsupervisorとchildの両方が消えた場合も、観測できなかった終了値や理由はunknownのまま残す。
145
+ 周期LaunchAgentは従来のstableなCLI pathでensureするままで、plist・Node・信用設定の書換えは要らない。
146
+
147
+ 配信失敗のreceiptには、`reason`のtyped codeに加え、許可済みの`failure.stage`と`failure.subreason`が入る。
148
+ 段階は`heartbeat_registration`、`hub_listing`、`project_route`、欠測時の`unknown`であり、任意のerror detailや
149
+ messageは写さない。配信確認の失敗は、daemonの死亡を示す判定には使わない。
150
+
122
151
  **`runtime_drift`** — 両者の食い違い。空配列は「差が無い」または「`runtime`が名乗っていないので
123
152
  判定できない」のどちらかである。
124
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.79.3",
3
+ "version": "0.79.4",
4
4
  "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "author": {
6
6
  "name": "Quo / クオ at kitepon.dev",
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/kitepon/Lattice.git"
14
14
  },
15
15
  "engines": {
16
- "node": ">=22.13 <25 || >=26"
16
+ "node": ">=22.13 <24.2 || >=24.22 <25 || >=26.10"
17
17
  },
18
18
  "bin": {
19
19
  "lattice": "bin/lattice.mjs",
@@ -125,32 +125,12 @@ async function loadLatticeSensor() {
125
125
  // which fails for ESM-only packages. This bypasses the transformation.
126
126
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
127
127
  const importESM = new Function('specifier', 'return import(specifier)');
128
- // Block LatticeSensor on Node.js 25.x — V8's turboshaft WASM JIT has a Zone
129
- // allocator bug that reliably crashes when compiling tree-sitter
130
- // grammars (see #54, #81, #140). The previous behaviour was a soft
131
- // console.warn that scrolls off-screen before the OOM crash 30 seconds
132
- // later, leading to a steady stream of "what is this OOM" reports.
133
- // Hard-exit before any WASM work; allow override via env var for users
134
- // who patched V8 themselves or want to test a future fix.
135
- const nodeVersion = process.versions.node;
136
- const nodeMajor = parseInt(nodeVersion.split('.')[0] ?? '0', 10);
137
- if ((0, node_version_check_1.isNode25Affected)(nodeMajor)) {
138
- process.stderr.write((0, node_version_check_1.buildNode25BlockBanner)(nodeVersion) + '\n');
139
- if (!process.env.LATTICE_SENSOR_ALLOW_UNSAFE_NODE) {
140
- process.exit(1);
141
- }
142
- // Override active — banner shown for visibility, continuing.
143
- }
144
- // Enforce the supported Node floor. `engines` in package.json only *warns* on
145
- // install (unless engine-strict), so hard-block here to actually keep users off
146
- // unsupported versions. Mirrors the Node 25 block above. See package.json `engines`.
147
- if (nodeMajor < node_version_check_1.MIN_NODE_MAJOR) {
148
- process.stderr.write((0, node_version_check_1.buildNodeTooOldBanner)(nodeVersion) + '\n');
149
- if (!process.env.LATTICE_SENSOR_ALLOW_UNSAFE_NODE) {
150
- process.exit(1);
151
- }
152
- // Override active — banner shown for visibility, continuing.
153
- }
128
+ // Keep the CLI and MCP bootstrap on the same runtime compatibility policy.
129
+ const runtimeSupport = (0, node_version_check_1.evaluateNodeVersionGuard)(process.versions.node, Boolean(process.env.LATTICE_SENSOR_ALLOW_UNSAFE_NODE));
130
+ if (runtimeSupport.banner !== null)
131
+ process.stderr.write(runtimeSupport.banner + '\n');
132
+ if (runtimeSupport.blocked)
133
+ process.exit(1);
154
134
  // Re-exec with V8's `--liftoff-only` if it isn't already set, so tree-sitter's
155
135
  // large WASM grammars never hit the turboshaft Zone OOM (`Fatal process out of
156
136
  // memory: Zone`) on Node >= 22. No-op under the bundled launcher, which already
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Node.js version compatibility check.
2
+ * Shared Node.js compatibility policy for CLI and MCP bootstrap.
3
+ * Includes the Node cppgc Realm lifetime repair and the existing Node25 exclusion.
3
4
  *
4
5
  * Node 25.x has a V8 turboshaft WASM JIT Zone allocator bug that
5
6
  * reliably crashes LatticeSensor with `Fatal process out of memory: Zone`
@@ -36,4 +37,12 @@ export declare const MIN_NODE_MAJOR = 20;
36
37
  * (see ../ui/glyphs.ts for the rationale).
37
38
  */
38
39
  export declare function buildNodeTooOldBanner(nodeVersion: string): string;
40
+ /** Node's cppgc Realm lifetime repair: nodejs/node#65778 (24.x backport#83b3431c). */
41
+ export declare function isNodeRealmLifetimeAffected(nodeVersion: string): boolean;
42
+ export declare function buildNodeRealmLifetimeBanner(nodeVersion: string): string;
43
+ /** Shared bootstrap policy for the sensor CLI and the packaged MCP entry. */
44
+ export declare function evaluateNodeVersionGuard(nodeVersion: string, overrideActive: boolean): {
45
+ blocked: boolean;
46
+ banner: string | null;
47
+ };
39
48
  //# sourceMappingURL=node-version-check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-version-check.d.ts","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAmBlE;AAED,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAkBjE"}
1
+ {"version":3,"file":"node-version-check.d.ts","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAmBlE;AAED,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAkBjE;AAED,sFAAsF;AACtF,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAMxE;AAED,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAiBxE;AAED,6EAA6E;AAC7E,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAOlI"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * Node.js version compatibility check.
3
+ * Shared Node.js compatibility policy for CLI and MCP bootstrap.
4
+ * Includes the Node cppgc Realm lifetime repair and the existing Node25 exclusion.
4
5
  *
5
6
  * Node 25.x has a V8 turboshaft WASM JIT Zone allocator bug that
6
7
  * reliably crashes LatticeSensor with `Fatal process out of memory: Zone`
@@ -13,6 +14,9 @@ exports.MIN_NODE_MAJOR = void 0;
13
14
  exports.buildNode25BlockBanner = buildNode25BlockBanner;
14
15
  exports.isNode25Affected = isNode25Affected;
15
16
  exports.buildNodeTooOldBanner = buildNodeTooOldBanner;
17
+ exports.isNodeRealmLifetimeAffected = isNodeRealmLifetimeAffected;
18
+ exports.buildNodeRealmLifetimeBanner = buildNodeRealmLifetimeBanner;
19
+ exports.evaluateNodeVersionGuard = evaluateNodeVersionGuard;
16
20
  /**
17
21
  * Build the bordered banner shown when LatticeSensor detects an
18
22
  * unsupported Node.js major version (Node 25.x). Pinned via unit
@@ -81,4 +85,42 @@ function buildNodeTooOldBanner(nodeVersion) {
81
85
  sep,
82
86
  ].join('\n');
83
87
  }
88
+ /** Node's cppgc Realm lifetime repair: nodejs/node#65778 (24.x backport#83b3431c). */
89
+ function isNodeRealmLifetimeAffected(nodeVersion) {
90
+ const parts = nodeVersion.split('.');
91
+ const major = Number(parts[0]);
92
+ const minor = Number(parts[1]);
93
+ return (major === 24 && minor >= 2 && minor < 22)
94
+ || (major === 26 && minor < 10);
95
+ }
96
+ function buildNodeRealmLifetimeBanner(nodeVersion) {
97
+ const sep = '-'.repeat(72);
98
+ return [
99
+ sep,
100
+ `[LatticeSensor] Unsupported Node.js version: ${nodeVersion}`,
101
+ sep,
102
+ 'This Node.js release has a cppgc wrapper/Realm lifetime defect.',
103
+ 'Worker teardown can write to freed memory and corrupt the native heap.',
104
+ 'Upstream repair: https://github.com/nodejs/node/pull/65778',
105
+ '',
106
+ 'Fix: use Node.js 24.22.0+ on the 24.x line or 26.10.0+ on the 26.x line.',
107
+ 'Supported Node.js 22 LTS is also outside this affected implementation.',
108
+ '',
109
+ 'For a custom patched runtime only, the existing override is:',
110
+ ' LATTICE_SENSOR_ALLOW_UNSAFE_NODE=1 lattice sensor ...',
111
+ sep,
112
+ ].join('\n');
113
+ }
114
+ /** Shared bootstrap policy for the sensor CLI and the packaged MCP entry. */
115
+ function evaluateNodeVersionGuard(nodeVersion, overrideActive) {
116
+ const nodeMajor = parseInt(nodeVersion.split('.')[0] ?? '0', 10);
117
+ let banner = null;
118
+ if (isNode25Affected(nodeMajor))
119
+ banner = buildNode25BlockBanner(nodeVersion);
120
+ else if (nodeMajor < exports.MIN_NODE_MAJOR)
121
+ banner = buildNodeTooOldBanner(nodeVersion);
122
+ else if (isNodeRealmLifetimeAffected(nodeVersion))
123
+ banner = buildNodeRealmLifetimeBanner(nodeVersion);
124
+ return { blocked: banner !== null && !overrideActive, banner };
125
+ }
84
126
  //# sourceMappingURL=node-version-check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-version-check.js","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAWH,wDAmBC;AAGD,4CAEC;AAmBD,sDAkBC;AAtED;;;;;;;;GAQG;AACH,SAAgB,sBAAsB,CAAC,WAAmB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG;QACH,gDAAgD,WAAW,EAAE;QAC7D,GAAG;QACH,qEAAqE;QACrE,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,EAAE;QACF,8BAA8B;QAC9B,+DAA+D;QAC/D,6EAA6E;QAC7E,EAAE;QACF,sDAAsD;QACtD,yDAAyD;QACzD,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,wDAAwD;AACxD,SAAgB,gBAAgB,CAAC,SAAiB;IAChD,OAAO,SAAS,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACU,QAAA,cAAc,GAAG,EAAE,CAAC;AAEjC;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CAAC,WAAmB;IACvD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,OAAO;QACL,GAAG;QACH,gDAAgD,WAAW,EAAE;QAC7D,GAAG;QACH,kCAAkC,sBAAc,gCAAgC;QAChF,yEAAyE;QACzE,sBAAsB;QACtB,EAAE;QACF,8BAA8B;QAC9B,+DAA+D;QAC/D,6EAA6E;QAC7E,EAAE;QACF,8CAA8C;QAC9C,yDAAyD;QACzD,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"node-version-check.js","sourceRoot":"","sources":["../../src/bin/node-version-check.ts"],"names":[],"mappings":"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"}
@@ -59,6 +59,10 @@ export interface DaemonLockInfo {
59
59
  socketPath: string;
60
60
  startedAt: number;
61
61
  }
62
+ /** The election and its clients must agree how long an observed startup is protected. */
63
+ export declare const DAEMON_STARTUP_GRACE_MS = 10000;
64
+ /** Time component only: callers still verify liveness and the socket hello. */
65
+ export declare function daemonStartupDeadline(info: Pick<DaemonLockInfo, 'startedAt'> | null, now?: number): number | null;
62
66
  /** Whether a lock record contains enough identity data for a socket hello. */
63
67
  export declare function canProbeDaemonIdentity(info: DaemonLockInfo): boolean;
64
68
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-paths.d.ts","sourceRoot":"","sources":["../../src/mcp/daemon-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA8BH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CA0C7F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAuBjE"}
1
+ {"version":3,"file":"daemon-paths.d.ts","sourceRoot":"","sources":["../../src/mcp/daemon-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA8BH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yFAAyF;AACzF,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,IAAI,EAAE,GAAG,SAAa,GAAG,MAAM,GAAG,IAAI,CAKrH;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CA0C7F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAuBjE"}
@@ -62,9 +62,11 @@ var __importStar = (this && this.__importStar) || (function () {
62
62
  };
63
63
  })();
64
64
  Object.defineProperty(exports, "__esModule", { value: true });
65
+ exports.DAEMON_STARTUP_GRACE_MS = void 0;
65
66
  exports.getDaemonSocketCandidates = getDaemonSocketCandidates;
66
67
  exports.getDaemonSocketPath = getDaemonSocketPath;
67
68
  exports.getDaemonPidPath = getDaemonPidPath;
69
+ exports.daemonStartupDeadline = daemonStartupDeadline;
68
70
  exports.canProbeDaemonIdentity = canProbeDaemonIdentity;
69
71
  exports.probeDaemonIdentity = probeDaemonIdentity;
70
72
  exports.encodeLockInfo = encodeLockInfo;
@@ -134,6 +136,16 @@ function getDaemonSocketPath(projectRoot) {
134
136
  function getDaemonPidPath(projectRoot) {
135
137
  return path.join((0, directory_1.getLatticeSensorDir)(projectRoot), 'daemon.pid');
136
138
  }
139
+ /** The election and its clients must agree how long an observed startup is protected. */
140
+ exports.DAEMON_STARTUP_GRACE_MS = 10_000;
141
+ /** Time component only: callers still verify liveness and the socket hello. */
142
+ function daemonStartupDeadline(info, now = Date.now()) {
143
+ const startedAt = info?.startedAt;
144
+ if (typeof startedAt !== 'number' || !Number.isFinite(startedAt) || startedAt <= 0
145
+ || !Number.isFinite(now) || now < startedAt || now - startedAt >= exports.DAEMON_STARTUP_GRACE_MS)
146
+ return null;
147
+ return startedAt + exports.DAEMON_STARTUP_GRACE_MS;
148
+ }
137
149
  /** Whether a lock record contains enough identity data for a socket hello. */
138
150
  function canProbeDaemonIdentity(info) {
139
151
  return (Number.isInteger(info.pid) &&
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-paths.js","sourceRoot":"","sources":["../../src/mcp/daemon-paths.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CH,8DAUC;AASD,kDAGC;AAGD,4CAEC;AAWD,wDAOC;AAOD,kDA0CC;AAMD,wCAEC;AAOD,wCAuBC;AA9KD,+CAAiC;AACjC,yCAA2B;AAC3B,uCAAyB;AACzB,2CAA6B;AAC7B,4CAAmD;AAEnD,oDAAoD;AACpD,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,8EAA8E;AAC9E,SAAS,WAAW,CAAC,WAAmB;IACtC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,qEAAqE;IACrE,+EAA+E;IAC/E,6EAA6E;IAC7E,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,yBAAyB,CAAC,WAAmB;IAC3D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,yEAAyE;QACzE,0EAA0E;QAC1E,OAAO,CAAC,+BAA+B,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAA,+BAAmB,EAAC,WAAW,CAAC,EAAE,aAAa,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,SAAS,CAAC,MAAM,GAAG,uBAAuB;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,WAAmB;IACrD,4EAA4E;IAC5E,OAAO,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE,CAAC;AACpD,CAAC;AAED,kEAAkE;AAClE,SAAgB,gBAAgB,CAAC,WAAmB;IAClD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAA,+BAAmB,EAAC,WAAW,CAAC,EAAE,YAAY,CAAC,CAAC;AACnE,CAAC;AAUD,8EAA8E;AAC9E,SAAgB,sBAAsB,CAAC,IAAoB;IACzD,OAAO,CACL,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG,CAAC;QACZ,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;QACnC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAC3B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,IAAoB,EAAE,SAAS,GAAG,KAAK;IACzE,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjE,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QACtC,IAAI,MAAkB,CAAC;QACvB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;QACzD,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,OAAO;QACT,CAAC;QACD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI;gBAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,OAAO,GAAG,CAAC;gBAAE,OAAO;YACxB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAA4B,CAAC;gBAC9E,MAAM,CACJ,KAAK,CAAC,QAAQ,KAAK,CAAC;oBACpB,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG;oBACtB,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO,CAAC,CAC9D,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAAC,IAAoB;IACjD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,IACE,MAAM;YACN,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;YAClC,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;YACrC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,MAAwB,CAAC;QAClC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;IAC/C,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"daemon-paths.js","sourceRoot":"","sources":["../../src/mcp/daemon-paths.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AA4JH;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAO7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAIvF;AAgFD;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,WAAW,CAAgB;IAGnC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAA+C;IAGnE,OAAO,CAAC,gBAAgB,CAA+B;IAIvD,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAA4D;IAE5E,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAA4D;IACxE,sFAAsF;IACtF,OAAO,CAAC,cAAc,CAAuB;gBAEjC,WAAW,CAAC,EAAE,MAAM;IAIhC;;;;;;;;;;;;OAYG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmE5B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B3B,uEAAuE;YACzD,WAAW;IA4DzB;;;;;;;;OAQG;YACW,kBAAkB;IAmEhC;;;;;;;OAOG;YACW,0BAA0B;IA6CxC,iFAAiF;IACjF,OAAO,CAAC,qBAAqB;IAK7B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;CAoB5B;AAWD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAiKH;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAO7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAIvF;AAgFD;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,WAAW,CAAgB;IAGnC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAA+C;IAGnE,OAAO,CAAC,gBAAgB,CAA+B;IAIvD,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAA4D;IAE5E,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAA4D;IACxE,sFAAsF;IACtF,OAAO,CAAC,cAAc,CAAuB;gBAEjC,WAAW,CAAC,EAAE,MAAM;IAIhC;;;;;;;;;;;;OAYG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmE5B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B3B,uEAAuE;YACzD,WAAW;IA4DzB;;;;;;;;OAQG;YACW,kBAAkB;IAiEhC;;;;;;;OAOG;YACW,0BAA0B;IAkExC,iFAAiF;IACjF,OAAO,CAAC,qBAAqB;IAK7B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;CAoB5B;AAWD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC"}
@@ -151,21 +151,26 @@ function makeFallbackEngine(root) {
151
151
  }
152
152
  return new engine_1.MCPEngine({ writerLockRoot: root, queryPool: true, queryPoolDefaultMax: DIRECT_QUERY_POOL_MAX });
153
153
  }
154
- /**
155
- * How long a launcher waits for a freshly-spawned daemon to bind its socket
156
- * before giving up and running in-process. The daemon binds the socket *before*
157
- * the (backgrounded) engine/grammar warm-up, so this only needs to cover node
158
- * process startup. 60 × 100ms = 6s of headroom for a cold/slow box; on the
159
- * common path the socket appears within a few rounds.
160
- */
161
- // Poll finely (25ms) so the proxy attaches the instant the freshly-spawned
162
- // daemon binds, instead of waiting up to a coarse 100ms after — shaves the
163
- // cold-start handshake (the window the headless agent races). Same ~6s total
164
- // give-up budget (240 × 25ms), just finer granularity; socket-connect probes
165
- // are cheap. Paired with deferring the LatticeSensor load (engine.ts) off the bind
166
- // path, this narrows the "No such tool available" race window.
154
+ /** Initial socket polling budget. A positively observed live same-version
155
+ * startup owner may receive the remainder of its original shared startup grace
156
+ * below; absent, expired, or untrusted owners do not extend this budget. Only a
157
+ * verified hello establishes readiness. */
158
+ // Fine polling attaches promptly without renewing the owner's fixed deadline.
167
159
  const DAEMON_CONNECT_MAX_RETRIES = 240;
168
160
  const DAEMON_CONNECT_RETRY_DELAY_MS = 25;
161
+ function observeStartupLock(root) {
162
+ try {
163
+ return (0, daemon_paths_1.decodeLockInfo)(fs.readFileSync((0, daemon_paths_1.getDaemonPidPath)(root), 'utf8'));
164
+ }
165
+ catch (error) {
166
+ const code = error.code;
167
+ if (code === 'ENOENT')
168
+ return null;
169
+ const safeCode = typeof code === 'string' && /^[A-Z0-9_]{1,40}$/.test(code) ? code : 'UNKNOWN';
170
+ process.stderr.write(`[LatticeSensor MCP] Startup owner observation failed (${safeCode}).\n`);
171
+ throw error;
172
+ }
173
+ }
169
174
  /** Whether `LATTICE_SENSOR_NO_DAEMON` was set to a truthy value. */
170
175
  function daemonOptOutSet() {
171
176
  const raw = process.env.LATTICE_SENSOR_NO_DAEMON;
@@ -551,9 +556,7 @@ class MCPServer {
551
556
  // Give a newly-elected daemon time to bind, then require its socket hello
552
557
  // to match the lock PID/version. PID existence alone accepts an unrelated
553
558
  // process after OS PID reuse and permanently wedges startup (#1553).
554
- const age = Date.now() - existing.startedAt;
555
- const startupGraceMs = 10_000;
556
- const stillStarting = existing.startedAt > 0 && age >= 0 && age < startupGraceMs;
559
+ const stillStarting = (0, daemon_paths_1.daemonStartupDeadline)(existing) !== null;
557
560
  // Legacy plain-PID locks have no socket identity to test. Preserve those
558
561
  // live holders: an inconclusive probe is not permission to create a
559
562
  // second writer.
@@ -614,32 +617,53 @@ class MCPServer {
614
617
  }
615
618
  return null;
616
619
  };
620
+ const connectOutcome = async () => {
621
+ const socket = await connectAnyCandidate();
622
+ if (socket === 'foreign-product')
623
+ return 'foreign-product';
624
+ if (socket === 'version-mismatch')
625
+ return { fallbackReason: 'version-skew' };
626
+ return socket;
627
+ };
617
628
  const getDaemonSocket = async () => {
618
629
  // Fast path: a daemon may already be listening (on either candidate).
619
- const probe = await connectAnyCandidate();
630
+ const probe = await connectOutcome();
620
631
  // ADR 0049 Decision 5③: a different product's daemon — propagate
621
632
  // verbatim so the local-handshake proxy fails the session closed
622
633
  // instead of silently falling back to direct mode.
623
- if (probe === 'foreign-product')
624
- return 'foreign-product';
625
- // Same-product version skew — definitive, serve in-process (direct,
626
- // reason: version-skew), don't poll for 6s.
627
- if (probe === 'version-mismatch')
628
- return { fallbackReason: 'version-skew' };
629
634
  if (probe)
630
635
  return probe;
631
636
  // None reachable — spawn one (detached) and poll for its bind.
632
637
  spawnDetachedDaemon(root);
633
638
  for (let attempt = 0; attempt < DAEMON_CONNECT_MAX_RETRIES; attempt++) {
634
639
  await sleep(DAEMON_CONNECT_RETRY_DELAY_MS);
635
- const s = await connectAnyCandidate();
636
- if (s === 'foreign-product')
637
- return 'foreign-product';
638
- if (s === 'version-mismatch')
639
- return { fallbackReason: 'version-skew' };
640
+ const s = await connectOutcome();
640
641
  if (s)
641
642
  return s;
642
643
  }
644
+ // An elected same-version owner may still be inside the very grace window
645
+ // in which competing candidates are required to leave it alone. Do not
646
+ // permanently degrade its clients before that owner's original deadline.
647
+ const owner = observeStartupLock(root);
648
+ const startupDeadline = owner?.version === version_1.LatticeSensorPackageVersion
649
+ && (0, daemon_paths_1.canProbeDaemonIdentity)(owner) && (0, daemon_1.isProcessAlive)(owner.pid) ? (0, daemon_paths_1.daemonStartupDeadline)(owner) : null;
650
+ if (owner && startupDeadline !== null) {
651
+ const monotonicDeadline = performance.now() + Math.max(0, startupDeadline - Date.now());
652
+ const maxAttempts = Math.ceil(daemon_paths_1.DAEMON_STARTUP_GRACE_MS / DAEMON_CONNECT_RETRY_DELAY_MS);
653
+ for (let attempt = 0; attempt < maxAttempts && Date.now() < startupDeadline
654
+ && performance.now() < monotonicDeadline; attempt++) {
655
+ await sleep(Math.min(DAEMON_CONNECT_RETRY_DELAY_MS, Math.max(1, monotonicDeadline - performance.now())));
656
+ const socket = await connectOutcome();
657
+ if (socket)
658
+ return socket;
659
+ const current = observeStartupLock(root);
660
+ // A temporarily missing record neither revokes nor renews the fixed
661
+ // observed wait. It never proves readiness; only the hello above does.
662
+ if (!(0, daemon_1.isProcessAlive)(owner.pid) || (current !== null && (current.pid !== owner.pid
663
+ || current.startedAt !== owner.startedAt || current.version !== owner.version)))
664
+ break;
665
+ }
666
+ }
643
667
  return { fallbackReason: 'no-daemon' }; // never bound — the proxy serves this session in-process
644
668
  };
645
669
  await (0, proxy_1.runLocalHandshakeProxy)({ getDaemonSocket, makeEngine: () => makeFallbackEngine(root), root });