@quolu/lattice 0.63.2 → 0.63.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.63.2",
3
+ "version": "0.63.3",
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",
@@ -319,6 +319,15 @@ async function replaceCanonical({ pathname, expectedBytes, value, validator, cra
319
319
  }
320
320
  }
321
321
 
322
+ /**
323
+ * control-events.jsonを書く他module(gate storeのatomic commit)が、このstoreのappendと
324
+ * 同じper-directory直列化を共有するための入口。同一process内で書き手が2つの規律に
325
+ * 分かれると、appendがcommitの読みと置換の間へ挟まり偽のcommitted prefix衝突になる。
326
+ */
327
+ export function withControlJournalMutation(runDir, mutation) {
328
+ return enqueue(path.resolve(runDir), mutation);
329
+ }
330
+
322
331
  function enqueue(runDir, mutation) {
323
332
  const previous = mutationQueues.get(runDir) ?? Promise.resolve();
324
333
  const result = previous.then(mutation);
@@ -4,6 +4,7 @@ import { lstat, mkdir, open, readFile, rename, unlink } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
 
6
6
  import { canonicalizeArtifact } from './artifact-contracts.mjs';
7
+ import { withControlJournalMutation } from './runtime-control-store.mjs';
7
8
  import { selfDigest } from './runtime-contracts.mjs';
8
9
 
9
10
  const SHA256 = /^[0-9a-f]{64}$/;
@@ -407,9 +408,17 @@ async function commitBuilt({ runDir, built, crashInjector, controlEventsPath = n
407
408
  }
408
409
 
409
410
  export async function commitRuntimeGateActivation(options) {
410
- const built = await buildCommit(options);
411
- return commitBuilt({ runDir: options.runDir, built, crashInjector: options.crashInjector,
412
- controlEventsPath: options.controlEventsPath ?? null });
411
+ // control journalの読み(buildCommit)と置換(commitBuilt)の間へ、同一process内の
412
+ // control store appendが挟まると、committed prefix検査が偽のGATE_COMMIT_CONFLICTに
413
+ // なる(2026-08-22 CI負荷下で実被弾: managed recompileのepoch activationへdaemonの
414
+ // 並行appendが割り込んだ)。appendと同じper-directory直列化を共有して排除する。
415
+ // 別process間の競合検査は従来どおりprefix比較が持つ。
416
+ const eventsPath = options.controlEventsPath ?? path.join(options.runDir, 'control-events.json');
417
+ return withControlJournalMutation(path.dirname(eventsPath), async () => {
418
+ const built = await buildCommit(options);
419
+ return commitBuilt({ runDir: options.runDir, built, crashInjector: options.crashInjector,
420
+ controlEventsPath: options.controlEventsPath ?? null });
421
+ });
413
422
  }
414
423
 
415
424
  export async function recoverRuntimeGateCommit(options) {
@@ -1011,8 +1011,33 @@ async function activateManagedSupervisorController({ repoRoot, runDir, runId, ad
1011
1011
  fail('ADAPTER_LAUNCH_INVALID', 'existing endpoint ownerが一意でない');
1012
1012
  }
1013
1013
  }
1014
- const handshake = await exchangeControllerHandshake({ socketPath: handshakeConnectPath, runId,
1015
- supervisorSessionNonce, controllerSocketRef, timeoutMs });
1014
+ // socket fileの出現はbindの証拠でありlistenの証拠ではない。負荷下ではcontrollerが
1015
+ // bindとlistenの間でpreemptされ、fileを見て接続した一回だけECONNREFUSEDになる
1016
+ // (2026-08-22 CI負荷下で実被弾)。起動したchildに限り、listen受理までを起動待ちに
1017
+ // 含めてdeadline内はECONNREFUSEDだけを待ち直す。死んだcontrollerはexitCode検査が、
1018
+ // 起動しないcontrollerはdeadlineが従来どおり落とす。既存endpoint(child無し)は
1019
+ // owner検証済みのlistener前提なので単発のまま。
1020
+ const handshakeDeadline = Date.now() + timeoutMs;
1021
+ let handshake;
1022
+ for (;;) {
1023
+ try {
1024
+ handshake = await exchangeControllerHandshake({ socketPath: handshakeConnectPath, runId,
1025
+ supervisorSessionNonce, controllerSocketRef, timeoutMs });
1026
+ break;
1027
+ } catch (error) {
1028
+ const listenPending = child !== null && error instanceof ManagedRuntimeError
1029
+ && ['ECONNREFUSED', 'ENOENT'].some((code) => String(error.message).includes(code))
1030
+ && Date.now() < handshakeDeadline;
1031
+ if (!listenPending) throw error;
1032
+ if (child.exitCode !== null) {
1033
+ fail(
1034
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
1035
+ `controller exited: ${child.exitCode}${childStderr ? `: ${childStderr.trim()}` : ''}`,
1036
+ );
1037
+ }
1038
+ await new Promise((resolve) => setTimeout(resolve, 20));
1039
+ }
1040
+ }
1016
1041
  const controllerDescriptor = handshake.descriptor;
1017
1042
  if (controllerDescriptor.adapter_kind !== adapterKind || controllerDescriptor.socket_ref !== controllerSocketRef
1018
1043
  || controllerDescriptor.capabilities.capabilities_digest !== launch.capabilities_digest
@@ -490,18 +490,11 @@ export async function activateEpochOneStore({
490
490
  }
491
491
  }
492
492
 
493
- const meta = {
494
- schema: 'lattice.run_meta.v2',
495
- run_id: normalizedMeta.run_id,
496
- executor_adapter: normalizedMeta.executor_adapter,
497
- run_event_schema: 'lattice.run_event.v1',
498
- control_event_schema: 'lattice.runtime_control_event.v1',
499
- epoch_bundle_schema: 'lattice.runtime_epoch_bundle.v1',
500
- created_plan_digest: compileArtifact.plan.plan_digest,
501
- };
502
- meta.meta_digest = digestArtifact(meta);
503
- await replaceDurableJson(runDir, 'run-meta.json', meta);
504
-
493
+ // pointerをmetaより先に書く。読み手はmeta v2を見てからpointerを読むので、逆順だと
494
+ // 2書き込みの間にmeta v2だけが見え、並行するreaderがINVALID_RUN_STORE
495
+ // (committed epoch pointerを読めない)で落ちる(2026-08-22 CI負荷下で実被弾)。
496
+ // この順ならmeta v2の可視がpointer実在を含意し、途中crashはmeta v1のまま
497
+ // (余剰のpointerはlegacy読取りに影響せず、再activationが置換する)。
505
498
  const pointer = {
506
499
  schema: 'lattice.committed_epoch_pointer.v1',
507
500
  run_id: normalizedMeta.run_id,
@@ -513,6 +506,18 @@ export async function activateEpochOneStore({
513
506
  };
514
507
  pointer.pointer_digest = digestArtifact(pointer);
515
508
  await replaceDurableJson(runDir, 'committed-epoch.json', pointer);
509
+
510
+ const meta = {
511
+ schema: 'lattice.run_meta.v2',
512
+ run_id: normalizedMeta.run_id,
513
+ executor_adapter: normalizedMeta.executor_adapter,
514
+ run_event_schema: 'lattice.run_event.v1',
515
+ control_event_schema: 'lattice.runtime_control_event.v1',
516
+ epoch_bundle_schema: 'lattice.runtime_epoch_bundle.v1',
517
+ created_plan_digest: compileArtifact.plan.plan_digest,
518
+ };
519
+ meta.meta_digest = digestArtifact(meta);
520
+ await replaceDurableJson(runDir, 'run-meta.json', meta);
516
521
  return { bundle, meta, pointer };
517
522
  }
518
523