@quolu/lattice 0.57.1 → 0.57.2

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.
@@ -140,12 +140,15 @@ timer = setInterval(async () => {
140
140
  // the terminal identity file) reaches this catch.
141
141
  try {
142
142
  const heartbeat = await hubHeartbeat.tick({ config });
143
- hubHeartbeatError = null;
143
+ // 直前に書いた指紋は、健全な状態へ戻った時にだけ捨てる。判定より前に捨てると
144
+ // 比較相手が毎周期nullになり、同じ結果をpoll周期ごとに書き続ける。stderrの
145
+ // 消費者が居ない常駐(WindowsのStartup launcher、LaunchAgent)では、この
146
+ // 書込みが積み上がること自体が常駐を不安定にする。
144
147
  if (['unreachable', 'rejected', 'partial'].includes(heartbeat?.state)) {
145
148
  const fingerprint = JSON.stringify(heartbeat);
146
149
  if (fingerprint !== hubHeartbeatError) process.stderr.write(`${fingerprint}\n`);
147
150
  hubHeartbeatError = fingerprint;
148
- }
151
+ } else hubHeartbeatError = null;
149
152
  } catch (error) {
150
153
  const failure = { schema: 'lattice.bridge_daemon_error.v1',
151
154
  code: error?.code ?? 'BRIDGE_HUB_HEARTBEAT_FAILED',
@@ -54,6 +54,11 @@ bridgeが無効な間は以下すべてnullで、bridgeを有効にしている
54
54
  | `node_path`・`node_exists` | 起動するNode実行体と、それが今も存在するか |
55
55
  | `bridge_path`・`bridge_exists` | 起動するbridge scriptと、それが今も存在するか |
56
56
  | `error` | `unreadable`のときだけtyped code(例`BRIDGE_LAUNCH_AGENT_PLIST_UNSAFE`) |
57
+ | `error=BRIDGE_PERSISTENCE_STATE_SPLIT` | launcherまたはplistの片割れだけが残った状態。手でfileを消さず、`lattice bridge reconfigure --json`で常駐設定を揃える(0.57.2以降) |
58
+
59
+ `BRIDGE_PERSISTENCE_STATE_SPLIT`は、常駐設定の一方(macOSのplist、またはWindowsのlauncher/descriptor)だけが
60
+ 存在することを示す。これは復旧対象そのものが壊れている状態なので、手作業でfileを削除せず、
61
+ `lattice bridge reconfigure --json`を実行して正規のinstall経路で両方を再生成する。
57
62
 
58
63
  **`runtime`** — いま応答しているprocess自身の申告。`state`は`running`/`not_running`/`unattested`/
59
64
  `descriptor_invalid`で、`running`以外では各値がnullになる。`running`でも、identityを返さない
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.57.1",
3
+ "version": "0.57.2",
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",
@@ -112,6 +112,10 @@ async function bridgePersistence({ launchAgent, env }) {
112
112
  // `loaded` is launchd-specific; the Windows Startup folder has no such
113
113
  // concept and reports null rather than pretending to know.
114
114
  const loaded = typeof snapshot?.loaded === 'boolean' ? snapshot.loaded : null;
115
+ if (snapshot?.split === true) {
116
+ return { state: 'unreadable', ...UNREADABLE_PERSISTENCE, loaded,
117
+ error: 'BRIDGE_PERSISTENCE_STATE_SPLIT' };
118
+ }
115
119
  if (described === null) return { state: 'not_installed', ...UNREADABLE_PERSISTENCE, loaded, error: null };
116
120
  return { state: 'installed', loaded, ...described, error: null };
117
121
  } catch (error) {
@@ -247,10 +247,8 @@ export async function snapshotBridgeLaunchAgent({ env = process.env,
247
247
  await prepareDirectory(refs.directory, uid);
248
248
  const content = await strictPlist(refs.plist, uid);
249
249
  const loaded = await loadedState(runner, uid);
250
- if (loaded && content === null) {
251
- throw fail('BRIDGE_LAUNCH_AGENT_STATE_INVALID', 'loaded bridge LaunchAgent has no owned plist');
252
- }
253
- return Object.freeze({ installed: content !== null, loaded, content });
250
+ const split = loaded && content === null;
251
+ return Object.freeze({ installed: content !== null, loaded, split, content });
254
252
  }
255
253
 
256
254
  const PROGRAM_ARGUMENTS_PATTERN =
@@ -346,6 +344,10 @@ export async function restoreBridgeLaunchAgent({ snapshot, listen = null, env =
346
344
  await prepareDirectory(refs.directory, uid);
347
345
  const stopped = await bootoutIfLoaded({ runner, uid });
348
346
  if (stopped) await waitStopped({ listen, env });
347
+ if (snapshot.split === true) {
348
+ await rm(refs.plist, { force: true });
349
+ return snapshot;
350
+ }
349
351
  if (snapshot.installed) await atomicPlist(refs.plist, snapshot.content);
350
352
  else await rm(refs.plist, { force: true });
351
353
  if (snapshot.loaded) {
@@ -239,10 +239,9 @@ export async function snapshotBridgeStartupFolder({ env = process.env } = {}) {
239
239
  await prepareDirectory(refs.runtimeDirectory);
240
240
  const launcherContent = await strictFile(refs.launcher);
241
241
  const descriptorContent = await strictFile(refs.descriptor);
242
- if ((launcherContent === null) !== (descriptorContent === null)) {
243
- throw fail('BRIDGE_STARTUP_FOLDER_STATE_INVALID', 'startup launcher and descriptor disagree on installed state');
244
- }
245
- return Object.freeze({ installed: launcherContent !== null, launcherContent, descriptorContent });
242
+ const split = (launcherContent === null) !== (descriptorContent === null);
243
+ return Object.freeze({ installed: launcherContent !== null && descriptorContent !== null,
244
+ split, launcherContent, descriptorContent });
246
245
  }
247
246
 
248
247
  async function pathPresent(ref) {