@quolu/lattice 0.12.23 → 0.12.25

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.
@@ -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,
@@ -68,6 +72,11 @@ import { createRuntimeControlRequest, validateRuntimeControlResponse } from './r
68
72
  import { createRuntimeControlStore } from './runtime-control-store.mjs';
69
73
  import { createRuntimeGateStore } from './runtime-gate-store.mjs';
70
74
  import { acquireRuntimeLifecycleLock } from './runtime-lifecycle-lock.mjs';
75
+ import {
76
+ AdapterRegistryError,
77
+ listRuntimeAdapters,
78
+ registerRuntimeAdapter,
79
+ } from './runtime-adapter-registry.mjs';
71
80
  import {
72
81
  ManagedRuntimeError,
73
82
  launchDurableSupervisor,
@@ -84,6 +93,8 @@ import {
84
93
  * lattice plan compile --request <run-request.json>
85
94
  * lattice plan verify --request <run-request.json> --plan <plan.json>
86
95
  * lattice run start --request <run-request.json> --executor <adapter>
96
+ * lattice run adapter register --input <descriptor.json>
97
+ * lattice run adapter list --json
87
98
  * lattice run observe --run .lattice/runs/<run-id>
88
99
  * lattice run status --run .lattice/runs/<run-id>
89
100
  * lattice run resume --run .lattice/runs/<run-id>
@@ -262,6 +273,58 @@ async function runRequestSchema({ stdout }) {
262
273
  return 0;
263
274
  }
264
275
 
276
+ /** 公開登録入力を推測させないため、配布物に同梱したJSON Schemaをそのまま返す(ADR 0125)。 */
277
+ async function runAdapterRegisterSchema({ stdout }) {
278
+ const schemaUrl = new URL(
279
+ '../docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json',
280
+ import.meta.url,
281
+ );
282
+ const schema = JSON.parse(await readFile(schemaUrl, 'utf8'));
283
+ if (schema?.title !== 'lattice.runtime_adapter_registration_input.v1') {
284
+ throw new CliContractError('CONTRACT_VIOLATION', '同梱adapter registration input schemaが不正');
285
+ }
286
+ stdout.write(`${JSON.stringify(schema)}\n`);
287
+ return 0;
288
+ }
289
+
290
+ async function runAdapterRegister({ cwd, inputPath, stdout }) {
291
+ try {
292
+ const repoRoot = await resolveRepoRoot(cwd);
293
+ const input = await readBoundedJson(inputPath, 'adapter registration input');
294
+ const result = await registerRuntimeAdapter({ repoRoot, input });
295
+ stdout.write(`${JSON.stringify(result)}\n`);
296
+ return 0;
297
+ } catch (error) {
298
+ if (error instanceof AdapterRegistryError) throw error;
299
+ if (error instanceof CliContractError) {
300
+ if (error.detail !== undefined) throw error;
301
+ throw new CliContractError(error.code, error.message, {
302
+ path: inputPath,
303
+ reason: error.code.toLowerCase(),
304
+ });
305
+ }
306
+ throw new CliContractError('ADAPTER_REGISTRY_WRITE_FAILED', 'adapter registryを書けない', {
307
+ path: '.lattice/runtime/adapter-registry/registry.json',
308
+ reason: typeof error?.code === 'string' ? error.code : 'unexpected_write_failure',
309
+ });
310
+ }
311
+ }
312
+
313
+ async function runAdapterList({ cwd, stdout }) {
314
+ try {
315
+ const repoRoot = await resolveRepoRoot(cwd);
316
+ const result = await listRuntimeAdapters({ repoRoot });
317
+ stdout.write(`${JSON.stringify(result)}\n`);
318
+ return 0;
319
+ } catch (error) {
320
+ if (error instanceof AdapterRegistryError || error instanceof CliContractError) throw error;
321
+ throw new CliContractError('ADAPTER_REGISTRY_READ_FAILED', 'adapter registryを読めない', {
322
+ path: '.lattice/runtime/adapter-registry/registry.json',
323
+ reason: typeof error?.code === 'string' ? error.code : 'unexpected_read_failure',
324
+ });
325
+ }
326
+ }
327
+
265
328
  async function loadRequest(requestPath) {
266
329
  const request = await readBoundedJson(requestPath, 'run request');
267
330
  const verdict = explainRunRequest(request);
@@ -619,6 +682,236 @@ async function withLifecycleLock(runDir, action) {
619
682
  }
620
683
  }
621
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
+
622
915
  async function runStart({ requestPath, executorAdapter, cwd, stdout }) {
623
916
  // --executor省略時の暗黙fallbackは持たない(Decision 8)。未知adapterはtyped reject。
624
917
  if (!KNOWN_ADAPTERS.includes(executorAdapter)) {
@@ -1302,7 +1595,7 @@ async function runActivate({ runDir, runRef, repoRoot, stdout, requestId = null
1302
1595
  adapter_kind: meta?.executor_adapter ?? null,
1303
1596
  required_artifact: '.lattice/runtime/adapter-registry/registry.json',
1304
1597
  registered_adapters: await registeredAdapterKinds(repoRoot),
1305
- reason: 'run activateは登録済みexecutor adapterを要求する。adapter登録は現時点で公開CLI面ではない',
1598
+ reason: 'run activateは登録済みexecutor adapterを要求する。run adapter registerで登録する',
1306
1599
  });
1307
1600
  }
1308
1601
  throw new CliContractError(code, `managed activateが${response.outcome}で終了した: ${response.result?.unmet?.[1] ?? code}`);
@@ -1461,6 +1754,18 @@ export async function runManagedSupervisorDaemon({
1461
1754
  for (const extra of additionalActivations) {
1462
1755
  await extra.registerWithManagedSupervisor(managedSupervisor);
1463
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
+ }
1464
1769
  if (restarting) {
1465
1770
  await managedSupervisor.recoveryBarrier({ barrierId: `recovery-${randomUUID()}`,
1466
1771
  frozenEventDigest: events.at(-1).event_digest });
@@ -2803,6 +3108,22 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
2803
3108
  && argv[0] === 'plan' && argv[1] === 'compile'
2804
3109
  && argv[2] === '--schema' && argv[3] === '--json') {
2805
3110
  action = () => runRequestSchema({ stdout });
3111
+ } else if (argv.length === 5
3112
+ && argv[0] === 'run' && argv[1] === 'adapter' && argv[2] === 'register'
3113
+ && argv[3] === '--schema' && argv[4] === '--json') {
3114
+ action = () => runAdapterRegisterSchema({ stdout });
3115
+ } else if (argv.length === 5
3116
+ && argv[0] === 'run' && argv[1] === 'adapter' && argv[2] === 'register'
3117
+ && argv[3] === '--input' && typeof argv[4] === 'string' && argv[4].length > 0) {
3118
+ action = () => runAdapterRegister({
3119
+ cwd,
3120
+ inputPath: path.resolve(cwd, argv[4]),
3121
+ stdout,
3122
+ });
3123
+ } else if (argv.length === 4
3124
+ && argv[0] === 'run' && argv[1] === 'adapter' && argv[2] === 'list'
3125
+ && argv[3] === '--json') {
3126
+ action = () => runAdapterList({ cwd, stdout });
2806
3127
  } else if (argv.length === 4
2807
3128
  && argv[0] === 'run' && argv[1] === 'start'
2808
3129
  && argv[2] === '--schema' && argv[3] === '--json') {
@@ -2937,6 +3258,9 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
2937
3258
  if (error instanceof RuntimeEpochStoreError) {
2938
3259
  return typedFailure(stderr, error.code, error.message);
2939
3260
  }
3261
+ if (error instanceof AdapterRegistryError) {
3262
+ return typedFailure(stderr, error.code, error.message, error.detail);
3263
+ }
2940
3264
  if (error instanceof ManagedRuntimeError) {
2941
3265
  return typedFailure(stderr, error.code, error.message);
2942
3266
  }
@@ -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(() => {});