@quolu/lattice 0.12.24 → 0.12.26

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
@@ -52,7 +52,30 @@ lattice plan create --input .lattice/plan-create.json
52
52
  discoveryと初期transactionの不変条件は
53
53
  [ADR 0058](docs/adr/0058-project-discovery-and-initial-authoring.md)が正です。
54
54
 
55
- TODO工程storeの読取は`lattice todo status`、検証は`lattice todo verify`、表示生成は
55
+ ## 実行runを端から端まで動かす
56
+
57
+ compileしたrunを実際にdispatchするには、executor adapterを登録してからactivateします。
58
+ 参照実装の`lattice-scripted-adapter`を配布しているため、公開CLIと配布binだけで
59
+ 実write・receipt受理・closeまで到達できます。
60
+
61
+ ```bash
62
+ lattice run adapter register --schema --json # 登録入力のJSON Schema
63
+ lattice run adapter register --input adapter.json
64
+ lattice run adapter list --json
65
+ lattice run activate --run .lattice/runs/<id>
66
+ lattice run status --run .lattice/runs/<id> # accepted に子が入る
67
+ lattice event verify --run .lattice/runs/<id>
68
+ lattice run close --run .lattice/runs/<id>
69
+ ```
70
+
71
+ digestは手で計算しません。binary・config・capabilities・自己digestは登録時にCLIが導出します。
72
+
73
+ `plan compile`が`BOUNDARY_UNKNOWN`を返す場合は、まず`git status --short`が空かを確認してください。
74
+ 未追跡ファイルがあるとsensor statusが`stale`になり、witnessが未解決unknownへ落ちます。
75
+ 作業ツリーをcleanにすると同じrequestがそのまま通ります。
76
+
77
+ TODO工程storeの読取は`lattice todo status`、`compile_binding`付きTaskの投影は
78
+ `lattice todo bindings`、検証は`lattice todo verify`、表示生成は
56
79
  `lattice todo gantt`を使います。topology/source reconciliationは
57
80
  `lattice todo revise --plan <key> --input <canonical-revision.json>`、Phase付きplanは
58
81
  `lattice todo revise-phase --plan <key> --input <canonical-phase-revision.json>`でsuccessor発行します。
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ runScriptedAdapterController,
5
+ ScriptedAdapterControllerError,
6
+ } from '../src/runtime-scripted-adapter-controller.mjs';
7
+
8
+ try {
9
+ await runScriptedAdapterController();
10
+ } catch (error) {
11
+ const payload = {
12
+ schema: 'lattice.scripted_adapter_error.v1',
13
+ code: error instanceof ScriptedAdapterControllerError
14
+ ? error.code
15
+ : 'SCRIPTED_CONTROLLER_FAILED',
16
+ message: String(error?.message ?? error),
17
+ };
18
+ process.stderr.write(`${JSON.stringify(payload)}\n`);
19
+ process.exitCode = 1;
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.12.24",
3
+ "version": "0.12.26",
4
4
  "description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,10 +13,12 @@
13
13
  },
14
14
  "bin": {
15
15
  "lattice": "bin/lattice.mjs",
16
- "lattice-mcp": "bin/lattice-mcp.mjs"
16
+ "lattice-mcp": "bin/lattice-mcp.mjs",
17
+ "lattice-scripted-adapter": "bin/lattice-scripted-adapter.mjs"
17
18
  },
18
19
  "files": [
19
20
  "bin",
21
+ "bin/lattice-scripted-adapter.mjs",
20
22
  "src",
21
23
  "docs/schemas/lattice.plan_create_input.v1.schema.json",
22
24
  "docs/schemas/lattice.plan_create_input.v2.schema.json",
@@ -58,7 +60,7 @@
58
60
  "test": "node scripts/run-product-tests.mjs",
59
61
  "test:sensor": "npm --prefix sensor test",
60
62
  "precheck": "node --check src/bridge-launch-agent.mjs",
61
- "check": "node --check bin/lattice.mjs && node --check bin/lattice-mcp.mjs && node --check bin/lattice-dashboard.mjs && node --check bin/lattice-bridge.mjs && node --check src/cli-stdio.mjs && node --check src/bridge-address.mjs && node --check src/bridge-registrar.mjs && node --check src/todo-gantt-layout.mjs && node --check src/todo-gantt-scope.mjs && node --check src/bridge-config.mjs && node --check src/bridge-server.mjs && node --check src/bridge-daemon.mjs && node --check src/bridge-cli.mjs && node --check src/project-cli.mjs && node --check src/sensor-cli.mjs && node --check src/sensor-runtime.mjs && node --check src/sensor-adapter.mjs && node --check src/factory-diagnostics.mjs && node --check src/runtime-errors.mjs && node --check src/runtime-contracts.mjs && node --check src/runtime-event-store.mjs && node --check src/runtime-adapter-registry.mjs && node --check src/hash-chain.mjs && node --check src/dag-chain.mjs && node --check src/todo-contracts.mjs && node --check src/todo-chain.mjs && node --check src/todo-store.mjs && node --check src/todo-migration.mjs && node --check src/todo-revision.mjs && node --check src/todo-status.mjs && node --check src/todo-cli.mjs && node --check src/todo-dashboard-registry.mjs && node --check src/todo-gantt-presentation.mjs && node --check src/todo-gantt-live.mjs && node --check src/bounded-seam.mjs && node --check src/todo-narrative-anchor.mjs && node --check src/todo-markdown-renderer.mjs && node --check src/todo-gantt-svg.mjs && node --check src/todo-gantt-html.mjs && node --check src/runtime-projection.mjs && node --check src/runtime-decision-verifier.mjs && node --check src/runtime-front-end.mjs && node --check src/runtime-cli.mjs && node --check src/rc3-dogfood-scaffold.mjs && node --check src/runtime-engine.mjs && node --check src/runtime-scripted-executor.mjs && node --check src/runtime-diff-observer.mjs && node --check src/runtime-worktree-executor.mjs && node --check src/runtime-hold-recompile.mjs && node --check src/rc3-scripted-campaign.mjs && node --check src/rc3-actual-dogfood.mjs && node --check src/rc4-stage1-dogfood.mjs && node --check research/fixtures/dispatch-record/src/dispatch-record.mjs && node --check test/research-dispatch-record.test.mjs",
63
+ "check": "node --check bin/lattice.mjs && node --check bin/lattice-mcp.mjs && node --check bin/lattice-dashboard.mjs && node --check bin/lattice-bridge.mjs && node --check bin/lattice-scripted-adapter.mjs && node --check src/cli-stdio.mjs && node --check src/bridge-address.mjs && node --check src/bridge-registrar.mjs && node --check src/todo-gantt-layout.mjs && node --check src/todo-gantt-scope.mjs && node --check src/bridge-config.mjs && node --check src/bridge-server.mjs && node --check src/bridge-daemon.mjs && node --check src/bridge-cli.mjs && node --check src/project-cli.mjs && node --check src/sensor-cli.mjs && node --check src/sensor-runtime.mjs && node --check src/sensor-adapter.mjs && node --check src/factory-diagnostics.mjs && node --check src/runtime-errors.mjs && node --check src/runtime-contracts.mjs && node --check src/runtime-event-store.mjs && node --check src/runtime-adapter-registry.mjs && node --check src/runtime-scripted-adapter-controller.mjs && node --check src/hash-chain.mjs && node --check src/dag-chain.mjs && node --check src/todo-contracts.mjs && node --check src/todo-chain.mjs && node --check src/todo-store.mjs && node --check src/todo-migration.mjs && node --check src/todo-revision.mjs && node --check src/todo-status.mjs && node --check src/todo-cli.mjs && node --check src/todo-dashboard-registry.mjs && node --check src/todo-gantt-presentation.mjs && node --check src/todo-gantt-live.mjs && node --check src/bounded-seam.mjs && node --check src/todo-narrative-anchor.mjs && node --check src/todo-markdown-renderer.mjs && node --check src/todo-gantt-svg.mjs && node --check src/todo-gantt-html.mjs && node --check src/runtime-projection.mjs && node --check src/runtime-decision-verifier.mjs && node --check src/runtime-front-end.mjs && node --check src/runtime-cli.mjs && node --check src/rc3-dogfood-scaffold.mjs && node --check src/runtime-engine.mjs && node --check src/runtime-scripted-executor.mjs && node --check src/runtime-diff-observer.mjs && node --check src/runtime-worktree-executor.mjs && node --check src/runtime-hold-recompile.mjs && node --check src/rc3-scripted-campaign.mjs && node --check src/rc3-actual-dogfood.mjs && node --check src/rc4-stage1-dogfood.mjs && node --check research/fixtures/dispatch-record/src/dispatch-record.mjs && node --check test/research-dispatch-record.test.mjs",
62
64
  "check:project-identity": "node --check src/project-identity.mjs",
63
65
  "ci": "npm run test && npm run test:sensor && npm run check && npm run check:project-identity"
64
66
  }
@@ -29,14 +29,18 @@ import {
29
29
  validateRuntimeBoundaryManifest,
30
30
  validateRuntimePlan,
31
31
  validRuntimeAbandonReason,
32
+ validateExecutorReceipt,
32
33
  verifyRuntimePlanBinding,
33
34
  selfDigest,
34
35
  } from './runtime-contracts.mjs';
35
36
  import {
37
+ adjudicatePendingReceipts,
36
38
  buildNextRunEvent,
37
39
  buildExecutorPackets,
38
40
  closeRunIfComplete,
41
+ dispatchReadyFrontier,
39
42
  initializeRunEvents,
43
+ observeExecutor,
40
44
  } from './runtime-engine.mjs';
41
45
  import {
42
46
  computeReadyFrontier,
@@ -678,6 +682,236 @@ async function withLifecycleLock(runDir, action) {
678
682
  }
679
683
  }
680
684
 
685
+ async function readScriptedControllerReceipt({
686
+ runDir,
687
+ controllerId,
688
+ payloadDigest,
689
+ }) {
690
+ const receiptPath = path.join(
691
+ runDir,
692
+ 'controllers',
693
+ controllerId,
694
+ 'receipts',
695
+ `${payloadDigest}.json`,
696
+ );
697
+ const info = await lstat(receiptPath);
698
+ if (!info.isFile() || info.isSymbolicLink()) {
699
+ throw new ManagedRuntimeError(
700
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
701
+ 'scripted controller receipt sidecarがregular fileではない',
702
+ );
703
+ }
704
+ const bytes = await readFile(receiptPath);
705
+ let receipt;
706
+ try {
707
+ receipt = JSON.parse(bytes.toString('utf8'));
708
+ } catch {
709
+ throw new ManagedRuntimeError(
710
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
711
+ 'scripted controller receipt sidecarのJSONが不正',
712
+ );
713
+ }
714
+ if (bytes.toString('utf8') !== `${canonicalizeArtifact(receipt)}\n`
715
+ || !validateExecutorReceipt(receipt)
716
+ || digestArtifact(receipt) !== payloadDigest) {
717
+ throw new ManagedRuntimeError(
718
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
719
+ 'scripted controller receipt sidecarのdigest bindingが不正',
720
+ );
721
+ }
722
+ return receipt;
723
+ }
724
+
725
+ async function driveInitialScriptedManagedEpoch({
726
+ runDir,
727
+ repoRoot,
728
+ request,
729
+ committed,
730
+ activation,
731
+ managedSupervisor,
732
+ initialEvents,
733
+ controlEvents,
734
+ }) {
735
+ let events = [...initialEvents];
736
+ const { plan, manifests, executor_packets: packets } = committed.bundle;
737
+ const controllerId = activation.controllerDescriptor.controller_id;
738
+ const registrationDigest = activation.registration.registration_digest;
739
+ const sessionNonceDigest = digestArtifact(activation.sessionNonce);
740
+ const processGroupId = activation.childPid;
741
+ for (;;) {
742
+ const frontier = computeReadyFrontier({ plan, events }).dispatchable;
743
+ if (frontier.length === 0) break;
744
+ await managedSupervisor.barrierAll({
745
+ barrierId: `dispatch-${plan.plan_epoch}-${events.length}`,
746
+ reason: 'initial_scripted_dispatch',
747
+ frozenEventDigest: events.at(-1).event_digest,
748
+ });
749
+ const issuedControlDigest = controlEvents().at(-1)?.event_digest;
750
+ if (typeof issuedControlDigest !== 'string') {
751
+ throw new ManagedRuntimeError(
752
+ 'EPOCH_ACTIVATION_INCOMPLETE',
753
+ 'initial dispatch leaseのcontrol bindingが無い',
754
+ );
755
+ }
756
+ const stagedLeases = [];
757
+ for (const todoId of frontier) {
758
+ const packet = packets[todoId];
759
+ const staged = {
760
+ schema: 'lattice.runtime_write_lease.v1',
761
+ lease_id: `lease-${packet.packet_digest.slice(0, 24)}`,
762
+ run_id: request.request_id,
763
+ todo_id: todoId,
764
+ plan_epoch: plan.plan_epoch,
765
+ packet_digest: packet.packet_digest,
766
+ controller_registration_digest: registrationDigest,
767
+ supervisor_session_nonce_digest: sessionNonceDigest,
768
+ state: 'staged',
769
+ ttl_ms: 60_000,
770
+ issued_control_digest: issuedControlDigest,
771
+ lease_digest: '',
772
+ };
773
+ staged.lease_digest = selfDigest(staged, 'lease_digest');
774
+ await managedSupervisor.prepareController({
775
+ controllerId,
776
+ executorPacket: packet,
777
+ stagedLease: staged,
778
+ });
779
+ stagedLeases.push(staged);
780
+ }
781
+ const activationDigest = digestArtifact({
782
+ schema: 'lattice.initial_scripted_activation.v1',
783
+ committed_epoch_pointer_digest: committed.pointer.pointer_digest,
784
+ staged_lease_digests: stagedLeases.map((lease) => lease.lease_digest).sort(),
785
+ });
786
+ const activated = await managedSupervisor.commitWriteGate({
787
+ planEpoch: plan.plan_epoch,
788
+ committedEpochDigest: committed.pointer.pointer_digest,
789
+ activationDigest,
790
+ commitReleaseBarrier: (barrier) => commitReleaseEpochBarrier({ runDir, barrier }),
791
+ committedAt: canonicalNow(),
792
+ });
793
+ const armedByPacket = new Map(activated.armedLeases.map((lease) => [
794
+ lease.packet_digest,
795
+ lease,
796
+ ]));
797
+ const managedAdapter = {
798
+ async dispatch({ packet }) {
799
+ const lease = armedByPacket.get(packet.packet_digest);
800
+ if (lease === undefined) {
801
+ throw new ManagedRuntimeError(
802
+ 'EPOCH_ACTIVATION_INCOMPLETE',
803
+ `armed leaseが無い: ${packet.todo_id}`,
804
+ );
805
+ }
806
+ await managedSupervisor.authorizeWrite({ leaseDigest: lease.lease_digest });
807
+ const response = await managedSupervisor.route('dispatch', controllerId, {
808
+ packet,
809
+ write_lease: lease,
810
+ });
811
+ if (response.packet_digest !== packet.packet_digest
812
+ || response.lease_digest !== lease.lease_digest) {
813
+ throw new ManagedRuntimeError(
814
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
815
+ `dispatch response binding不一致: ${packet.todo_id}`,
816
+ );
817
+ }
818
+ return {
819
+ executor_handle: response.executor_handle,
820
+ worktree_id: response.worktree_id,
821
+ write_lease_id: lease.lease_id,
822
+ write_lease_digest: lease.lease_digest,
823
+ controller_registration_digest: registrationDigest,
824
+ controller_session_nonce_digest:
825
+ activation.controllerDescriptor.controller_session_nonce_digest,
826
+ direct_os_observation_binding: {
827
+ process_pid: activation.childPid,
828
+ process_group_id: processGroupId,
829
+ process_start_identity:
830
+ structuredClone(activation.controllerDescriptor.process_start_identity),
831
+ worktree_path: repoRoot,
832
+ base_sha: packet.base_sha,
833
+ },
834
+ };
835
+ },
836
+ async observe({ executor_handle: executorHandle }) {
837
+ const dispatch = events.findLast((event) => (
838
+ event.kind === 'executor_dispatched'
839
+ && event.payload?.executor_handle === executorHandle
840
+ ));
841
+ const response = await managedSupervisor.route('observe', controllerId, {
842
+ executor_handle: executorHandle,
843
+ expected_epoch: dispatch.plan_epoch,
844
+ expected_lease_digest: dispatch.payload.write_lease_digest,
845
+ });
846
+ if (response.observation.state !== 'terminal') {
847
+ throw new ManagedRuntimeError(
848
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
849
+ `scripted controllerがterminal以外を返した: ${response.observation.state}`,
850
+ );
851
+ }
852
+ const receipt = await readScriptedControllerReceipt({
853
+ runDir,
854
+ controllerId,
855
+ payloadDigest: response.observation.payload_digest,
856
+ });
857
+ return { state: 'terminal', receipt };
858
+ },
859
+ };
860
+ const dispatched = await dispatchReadyFrontier({
861
+ runId: request.request_id,
862
+ plan,
863
+ events,
864
+ packets,
865
+ manifests,
866
+ adapter: managedAdapter,
867
+ recordedAt: canonicalNow(),
868
+ });
869
+ if (dispatched.failure !== null) {
870
+ throw new ManagedRuntimeError(
871
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
872
+ `scripted dispatch失敗: ${dispatched.failure.todo_id}: ${dispatched.failure.message}`,
873
+ );
874
+ }
875
+ events = dispatched.events;
876
+ await replaceEventsAtomically(runDir, events);
877
+ for (const todoId of dispatched.dispatched) {
878
+ const observed = await observeExecutor({
879
+ runId: request.request_id,
880
+ todoId,
881
+ plan,
882
+ events,
883
+ adapter: managedAdapter,
884
+ recordedAt: canonicalNow(),
885
+ });
886
+ events = observed.events;
887
+ await replaceEventsAtomically(runDir, events);
888
+ }
889
+ const adjudicated = adjudicatePendingReceipts({
890
+ runId: request.request_id,
891
+ plan,
892
+ events,
893
+ recordedAt: canonicalNow(),
894
+ });
895
+ if (adjudicated.decisions.some((decision) => decision.decision !== 'accepted')) {
896
+ throw new ManagedRuntimeError(
897
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
898
+ 'scripted controller receiptが受理されなかった',
899
+ );
900
+ }
901
+ events = adjudicated.events;
902
+ await replaceEventsAtomically(runDir, events);
903
+ }
904
+ return events;
905
+ }
906
+
907
+ function isDistributedScriptedControllerActivation(activation) {
908
+ return activation?.controllerDescriptor?.adapter_kind === 'scripted'
909
+ && activation?.launchDescriptor?.launch_kind === 'host_binary'
910
+ && activation.launchDescriptor.argv.some((argument) => (
911
+ path.basename(argument) === 'lattice-scripted-adapter.mjs'
912
+ ));
913
+ }
914
+
681
915
  async function runStart({ requestPath, executorAdapter, cwd, stdout }) {
682
916
  // --executor省略時の暗黙fallbackは持たない(Decision 8)。未知adapterはtyped reject。
683
917
  if (!KNOWN_ADAPTERS.includes(executorAdapter)) {
@@ -1520,6 +1754,18 @@ export async function runManagedSupervisorDaemon({
1520
1754
  for (const extra of additionalActivations) {
1521
1755
  await extra.registerWithManagedSupervisor(managedSupervisor);
1522
1756
  }
1757
+ if (!restarting && isDistributedScriptedControllerActivation(activation)) {
1758
+ await driveInitialScriptedManagedEpoch({
1759
+ runDir,
1760
+ repoRoot,
1761
+ request,
1762
+ committed,
1763
+ activation,
1764
+ managedSupervisor,
1765
+ initialEvents: events,
1766
+ controlEvents: () => controlEvents,
1767
+ });
1768
+ }
1523
1769
  if (restarting) {
1524
1770
  await managedSupervisor.recoveryBarrier({ barrierId: `recovery-${randomUUID()}`,
1525
1771
  frozenEventDigest: events.at(-1).event_digest });
@@ -292,18 +292,34 @@ export async function dispatchReadyFrontier(options = {}) {
292
292
  || dispatchResult.worktree_id.length === 0) {
293
293
  fail(`adapter dispatchがopaque handle/worktreeを返さない: ${todoId}`);
294
294
  }
295
+ const dispatchPayload = {
296
+ executor_handle: dispatchResult.executor_handle,
297
+ worktree_id: dispatchResult.worktree_id,
298
+ packet_digest: packet.packet_digest,
299
+ context_content_digest: packet.context_content_digest,
300
+ };
301
+ const managedFields = [
302
+ 'write_lease_id',
303
+ 'write_lease_digest',
304
+ 'controller_registration_digest',
305
+ 'controller_session_nonce_digest',
306
+ 'direct_os_observation_binding',
307
+ ];
308
+ if (managedFields.some((field) => Object.hasOwn(dispatchResult, field))) {
309
+ if (!managedFields.every((field) => Object.hasOwn(dispatchResult, field))) {
310
+ fail(`managed adapter dispatch bindingが不足する: ${todoId}`);
311
+ }
312
+ Object.assign(dispatchPayload, Object.fromEntries(
313
+ managedFields.map((field) => [field, structuredClone(dispatchResult[field])]),
314
+ ));
315
+ }
295
316
  next.push(buildNextRunEvent({
296
317
  events: next,
297
318
  runId,
298
319
  kind: 'executor_dispatched',
299
320
  planEpoch: plan.plan_epoch,
300
321
  subject: { kind: 'todo', ref: todoId },
301
- payload: {
302
- executor_handle: dispatchResult.executor_handle,
303
- worktree_id: dispatchResult.worktree_id,
304
- packet_digest: packet.packet_digest,
305
- context_content_digest: packet.context_content_digest,
306
- },
322
+ payload: dispatchPayload,
307
323
  recordedAt,
308
324
  }));
309
325
  dispatchedNow.push(todoId);
@@ -699,6 +699,13 @@ function createControllerSocketTransport(socketPath, timeoutMs) {
699
699
  const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1);
700
700
  let document;
701
701
  try { document = JSON.parse(line); } catch { failPending('controller document JSON不正'); socket.destroy(); return; }
702
+ if (document?.schema === 'lattice.scripted_adapter_error.v1'
703
+ && typeof document.code === 'string'
704
+ && typeof document.message === 'string') {
705
+ failPending(`${document.code}: ${document.message}`);
706
+ socket.destroy();
707
+ return;
708
+ }
702
709
  if (validateControllerHeartbeat(document)) {
703
710
  Promise.resolve(heartbeatHandler?.(structuredClone(document))).catch(() => socket.destroy());
704
711
  continue;
@@ -755,6 +762,7 @@ async function activateManagedSupervisorController({ repoRoot, runDir, runId, ad
755
762
  const controllerSocketPath = path.join(runDir, controllerSocketRef);
756
763
  const supervisorSocketRef = 'supervisor/control.sock';
757
764
  let child = null;
765
+ let childStderr = '';
758
766
  try {
759
767
  await mkdir(controllerDir, { recursive: true, mode: 0o700 });
760
768
  let handshakeSocket = launch.endpoint;
@@ -775,7 +783,15 @@ async function activateManagedSupervisorController({ repoRoot, runDir, runId, ad
775
783
  if (!config.isFile() || config.isSymbolicLink() || sha256Bytes(await readFile(configPath)) !== launch.config_digest) fail('ADAPTER_LAUNCH_INVALID', 'config digest不一致');
776
784
  const bootstrap = createControllerBootstrap({ requestId: randomUUID(), runId, controllerSocketRef, supervisorSocketRef, supervisorSessionNonce });
777
785
  // controller hostのcwdはrun store。bootstrapの固定relative socket refを任意absolute pathへ拡張しない。
778
- child = spawn(binaryReal, launch.argv, { cwd: runDir, detached: true, stdio: ['ignore', 'ignore', 'ignore', 'pipe'] });
786
+ child = spawn(binaryReal, launch.argv, {
787
+ cwd: runDir,
788
+ detached: true,
789
+ stdio: ['ignore', 'ignore', 'pipe', 'pipe'],
790
+ });
791
+ child.stderr.setEncoding('utf8');
792
+ child.stderr.on('data', (chunk) => {
793
+ childStderr = `${childStderr}${chunk}`.slice(-8_192);
794
+ });
779
795
  child.stdio[3].write(`${canonicalizeArtifact(bootstrap)}\n`);
780
796
  child.stdio[3].end();
781
797
  handshakeSocket = controllerSocketPath;
@@ -783,11 +799,23 @@ async function activateManagedSupervisorController({ repoRoot, runDir, runId, ad
783
799
  const deadline = Date.now() + timeoutMs;
784
800
  while (Date.now() < deadline) {
785
801
  try { if ((await lstat(handshakeSocket)).isSocket()) break; } catch (error) { if (error?.code !== 'ENOENT') throw error; }
786
- if (child.exitCode !== null) fail('ADAPTER_CONTROLLER_UNAVAILABLE', `controller exited: ${child.exitCode}`);
802
+ if (child.exitCode !== null) {
803
+ fail(
804
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
805
+ `controller exited: ${child.exitCode}${childStderr ? `: ${childStderr.trim()}` : ''}`,
806
+ );
807
+ }
787
808
  await new Promise((resolve) => setTimeout(resolve, 20));
788
809
  }
789
810
  let socketInfo;
790
- try { socketInfo = await lstat(handshakeSocket); } catch { fail('ADAPTER_CONTROLLER_UNAVAILABLE', 'controller socket未生成'); }
811
+ try {
812
+ socketInfo = await lstat(handshakeSocket);
813
+ } catch {
814
+ fail(
815
+ 'ADAPTER_CONTROLLER_UNAVAILABLE',
816
+ `controller socket未生成${childStderr ? `: ${childStderr.trim()}` : ''}`,
817
+ );
818
+ }
791
819
  if (!socketInfo.isSocket()) fail('ADAPTER_CONTROLLER_UNAVAILABLE', 'controller endpointがsocketでない');
792
820
  // exec後にも同じ実行image bytesを再検証する。PID生存も同時に要求する。
793
821
  try { process.kill(child.pid, 0); } catch { fail('ADAPTER_CONTROLLER_UNAVAILABLE', 'controller process不達'); }
@@ -902,7 +930,18 @@ async function activateManagedSupervisorController({ repoRoot, runDir, runId, ad
902
930
  await registerWithManagedSupervisor(managedSupervisor);
903
931
  return managedSupervisor;
904
932
  };
905
- return { supervisorDescriptor, activationControlEvent, controllerDescriptor, registration, sessionNonce: supervisorSessionNonce, childPid: child?.pid ?? controllerDescriptor.pid, createManagedSupervisor, registerWithManagedSupervisor, disposeController };
933
+ return {
934
+ supervisorDescriptor,
935
+ activationControlEvent,
936
+ controllerDescriptor,
937
+ registration,
938
+ launchDescriptor: structuredClone(launch),
939
+ sessionNonce: supervisorSessionNonce,
940
+ childPid: child?.pid ?? controllerDescriptor.pid,
941
+ createManagedSupervisor,
942
+ registerWithManagedSupervisor,
943
+ disposeController,
944
+ };
906
945
  } catch (error) {
907
946
  if (child?.pid) { try { process.kill(child.pid, 'SIGTERM'); } catch { /* already exited */ } }
908
947
  await rm(controllerSocketPath, { force: true }).catch(() => {});