@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.
- package/bin/lattice-scripted-adapter.mjs +20 -0
- package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -0
- package/package.json +6 -3
- package/src/cli-help.mjs +5 -0
- package/src/runtime-adapter-registry.mjs +506 -0
- package/src/runtime-cli.mjs +325 -1
- package/src/runtime-engine.mjs +22 -6
- package/src/runtime-managed-supervisor.mjs +43 -4
- package/src/runtime-scripted-adapter-controller.mjs +901 -0
package/src/runtime-cli.mjs
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/runtime-engine.mjs
CHANGED
|
@@ -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, {
|
|
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)
|
|
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 {
|
|
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 {
|
|
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(() => {});
|