@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
|
-
|
|
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.
|
|
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
|
}
|
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,
|
|
@@ -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 });
|
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(() => {});
|