@quolu/lattice 0.50.1 → 0.52.0

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.
Files changed (42) hide show
  1. package/bin/lattice-work-order-adapter.mjs +20 -0
  2. package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -0
  3. package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -0
  4. package/package.json +5 -2
  5. package/src/boundary-observation-compiler-v2.mjs +1 -1
  6. package/src/cli-help.mjs +33 -2
  7. package/src/rc3-actual-dogfood.mjs +6 -2
  8. package/src/rc3-scripted-campaign.mjs +37 -10
  9. package/src/rc4-stage1-dogfood.mjs +6 -2
  10. package/src/runtime-adapter-registry.mjs +21 -7
  11. package/src/runtime-cli.mjs +476 -34
  12. package/src/runtime-contracts.mjs +59 -13
  13. package/src/runtime-controller-protocol.mjs +48 -3
  14. package/src/runtime-decision-verifier.mjs +70 -0
  15. package/src/runtime-diff-observer.mjs +66 -4
  16. package/src/runtime-direct-os-observer.mjs +25 -8
  17. package/src/runtime-driver-state.mjs +162 -0
  18. package/src/runtime-engine.mjs +37 -6
  19. package/src/runtime-front-end.mjs +39 -1
  20. package/src/runtime-managed-supervisor.mjs +80 -14
  21. package/src/runtime-multi-epoch-store.mjs +87 -14
  22. package/src/runtime-pull-intake.mjs +1188 -0
  23. package/src/runtime-work-order-contracts.mjs +91 -0
  24. package/src/runtime-work-order-controller.mjs +1167 -0
  25. package/src/seam-proposal-queries.mjs +1 -1
  26. package/src/todo-cli.mjs +273 -7
  27. package/src/todo-contracts.mjs +19 -2
  28. package/src/todo-gantt-html-independence.mjs +3 -2
  29. package/src/todo-gantt-html-shared.mjs +1 -2
  30. package/src/todo-gantt-html-style.mjs +13 -0
  31. package/src/todo-gantt-html.mjs +15 -2
  32. package/src/todo-gantt-layout.mjs +75 -1
  33. package/src/todo-gantt-nested.mjs +263 -0
  34. package/src/todo-gantt-svg.mjs +80 -5
  35. package/src/todo-independence-contracts.mjs +73 -7
  36. package/src/todo-independence-guidance.mjs +30 -1
  37. package/src/todo-independence.mjs +89 -7
  38. package/src/todo-revision.mjs +1 -1
  39. package/src/todo-split.mjs +472 -0
  40. package/src/todo-status.mjs +10 -1
  41. package/src/todo-store-git-transaction.mjs +418 -0
  42. package/src/todo-store.mjs +144 -4
@@ -32,6 +32,11 @@ import {
32
32
  compileRuntimePlanV1,
33
33
  evidenceFromCollectedOutcomes,
34
34
  } from './runtime-front-end.mjs';
35
+ import {
36
+ explainTodoWitnessSet,
37
+ isGitSha,
38
+ synthesizeWitnessRunRequest,
39
+ } from './todo-independence-contracts.mjs';
35
40
  import {
36
41
  explainRunRequest,
37
42
  validateRunRequest,
@@ -73,15 +78,44 @@ import {
73
78
  isManagedRunFrozen,
74
79
  readCommittedEpochStore,
75
80
  recordRuntimeFinding,
81
+ rejectFutureRuntimeStoreSchema,
76
82
  stageSuccessorEpoch,
77
83
  validateRuntimeEpochBundle,
78
84
  validateRuntimeFindingCandidate,
79
85
  validateRuntimeFindingRecord,
80
86
  } from './runtime-multi-epoch-store.mjs';
81
- import { createRuntimeControlRequest, validateRuntimeControlResponse } from './runtime-controller-protocol.mjs';
87
+ import {
88
+ acceptsHostDrivenEpoch,
89
+ createRuntimeControlRequest,
90
+ validateRuntimeControlResponse,
91
+ } from './runtime-controller-protocol.mjs';
82
92
  import { createRuntimeControlStore } from './runtime-control-store.mjs';
83
93
  import { createRuntimeGateStore } from './runtime-gate-store.mjs';
84
- import { acquireRuntimeLifecycleLock } from './runtime-lifecycle-lock.mjs';
94
+ import {
95
+ RuntimeLifecycleLockError,
96
+ acquireRuntimeLifecycleLock,
97
+ } from './runtime-lifecycle-lock.mjs';
98
+ import {
99
+ PullRunError,
100
+ acceptPullTask,
101
+ attachPullWorker,
102
+ closePullRun,
103
+ inspectRunMode as inspectPullRunMode,
104
+ intakePullTask,
105
+ interventionPullTask,
106
+ landingPullRun,
107
+ listPullRunEntry,
108
+ observePullRun,
109
+ releasePullTask,
110
+ startPullRun,
111
+ statusPullRun,
112
+ } from './runtime-pull-intake.mjs';
113
+ import {
114
+ RuntimeDriverStateError,
115
+ readRuntimeDriverState,
116
+ replaceRuntimeDriverState,
117
+ } from './runtime-driver-state.mjs';
118
+
85
119
  import {
86
120
  AdapterRegistryError,
87
121
  listRuntimeAdapters,
@@ -93,9 +127,29 @@ import {
93
127
  observeManagedProcessStartIdentity,
94
128
  prepareManagedSupervisorRestart,
95
129
  resolveActiveRuntimePaths,
96
- sendRuntimeActivationRequest,
130
+ sendRuntimeActivationRequestUntilSettled,
97
131
  sendRuntimeControlRequest,
98
132
  } from './runtime-managed-supervisor.mjs';
133
+ import { TodoStoreError } from './todo-store.mjs';
134
+
135
+ async function inspectRunMode(runDir) {
136
+ try {
137
+ return await inspectPullRunMode(runDir);
138
+ } catch (error) {
139
+ if (error instanceof PullRunError && error.code === 'UNSUPPORTED_RUN_STORE_SCHEMA') {
140
+ rejectFutureRuntimeStoreSchema(error.detail?.schema, {
141
+ artifact: 'run_meta', family: 'lattice.run_meta', expectedVersion: 2,
142
+ });
143
+ }
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ async function requireLegacyRunMode(runDir, operation) {
149
+ if ((await inspectRunMode(runDir)).mode !== 'legacy') {
150
+ throw new PullRunError('RUN_MODE_MISMATCH', `pull runはlegacy専用${operation}を受け付けない`);
151
+ }
152
+ }
99
153
 
100
154
  /**
101
155
  * RC3-D CLI surface(ADR 0044 Decision 8)。
@@ -103,11 +157,13 @@ import {
103
157
  * lattice plan compile --request <run-request.json>
104
158
  * lattice plan verify --request <run-request.json> --plan <plan.json>
105
159
  * lattice run start --request <run-request.json> --executor <adapter>
160
+ * lattice run request synthesize --witness <witness-set.json> --base-sha <sha> --id <request-id>
106
161
  * lattice run adapter register --input <descriptor.json>
107
162
  * lattice run adapter list --json
108
163
  * lattice run observe --run .lattice/runs/<run-id>
109
164
  * lattice run status --run .lattice/runs/<run-id>
110
165
  * lattice run resume --run .lattice/runs/<run-id>
166
+ * lattice run landing --run .lattice/runs/<run-id>
111
167
  * lattice run close --run .lattice/runs/<run-id>
112
168
  * lattice run abandon --run .lattice/runs/<run-id> --reason <reason>
113
169
  * lattice event verify --run .lattice/runs/<run-id>
@@ -130,7 +186,9 @@ const CLI_ERROR_SCHEMA = 'lattice.cli_error.v2';
130
186
  // 現役run storeは対象Git repo内のLattice-owned・ignored rootへ限定する。
131
187
  const RUN_STORE_ROOT = ['.lattice', 'runs'];
132
188
  const RUN_REF = /^\.lattice\/runs\/([0-9A-Za-z](?:[0-9A-Za-z._-]{0,127}))$/u;
133
- const KNOWN_ADAPTERS = Object.freeze(['scripted', 'isolated-worktree', 'actual-agent']);
189
+ const KNOWN_ADAPTERS = Object.freeze([
190
+ 'scripted', 'isolated-worktree', 'actual-agent', 'work-order',
191
+ ]);
134
192
  /**
135
193
  * 自動escalationがlifecycle lockを待つ上限(ADR 0143)。
136
194
  *
@@ -154,8 +212,7 @@ function ownedResourceId(kind, target) {
154
212
  function ownedPathResourceId(pathValue) {
155
213
  return ownedResourceId('path', pathValue);
156
214
  }
157
- /** 走行中workerの完了を待つ上限と間隔。待てないと走行中の観測が成立しない。 */
158
- const SCRIPTED_OBSERVE_TIMEOUT_MS = 120_000;
215
+ /** 走行中workerの完了を観測する間隔。待てないと走行中の観測が成立しない。 */
159
216
  const SCRIPTED_OBSERVE_POLL_MS = 20;
160
217
 
161
218
  class CliContractError extends Error {
@@ -312,11 +369,11 @@ async function runRequestSchema({ stdout }) {
312
369
  /** 公開登録入力を推測させないため、配布物に同梱したJSON Schemaをそのまま返す(ADR 0125)。 */
313
370
  async function runAdapterRegisterSchema({ stdout }) {
314
371
  const schemaUrl = new URL(
315
- '../docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json',
372
+ '../docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json',
316
373
  import.meta.url,
317
374
  );
318
375
  const schema = JSON.parse(await readFile(schemaUrl, 'utf8'));
319
- if (schema?.title !== 'lattice.runtime_adapter_registration_input.v1') {
376
+ if (schema?.title !== 'lattice.runtime_adapter_registration_input.v2') {
320
377
  throw new CliContractError('CONTRACT_VIOLATION', '同梱adapter registration input schemaが不正');
321
378
  }
322
379
  stdout.write(`${JSON.stringify(schema)}\n`);
@@ -346,6 +403,47 @@ async function runAdapterRegister({ cwd, inputPath, stdout }) {
346
403
  }
347
404
  }
348
405
 
406
+ /**
407
+ * witness setから`run start`へ渡せるrun requestを組み立てて返す(read-only)。
408
+ *
409
+ * **推測しない面である。** base shaもrequest idもCLIが決めず、呼び手が渡す——どのbaseへ
410
+ * 対する計画なのかは装置が知らないし、HEADを黙って採ると「撮ったつもりのbaseと違う木」で
411
+ * runが始まる。witness setの検査は`explainTodoWitnessSet`が単一の正本なので、ここでは
412
+ * 判定を持たず理由とpathをそのまま返す。
413
+ *
414
+ * request idの旗が`--id`なのは、`--request-id`が末尾2引数のときrun mutationの
415
+ * idempotency keyとして先に横取りされるためである(同じ綴りで別の意味を持たせない)。
416
+ */
417
+ async function runRequestSynthesize({ cwd, witnessPath, baseSha, requestId, stdout }) {
418
+ if (!isGitSha(baseSha)) {
419
+ throw new CliContractError('INVALID_BASE_SHA', '--base-shaが40桁のgit shaでない', {
420
+ base_sha: baseSha,
421
+ });
422
+ }
423
+ if (!/^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$/u.test(requestId)) {
424
+ throw new CliContractError('INVALID_REQUEST_ID', '--idは128文字以下の識別子でなければならない', {
425
+ request_id: requestId,
426
+ });
427
+ }
428
+ const witnessSet = await readBoundedJson(path.resolve(cwd, witnessPath), 'witness set');
429
+ const verdict = explainTodoWitnessSet(witnessSet);
430
+ if (!verdict.valid) {
431
+ throw new CliContractError('WITNESS_SET_INVALID', 'witness setが契約を満たさない', {
432
+ path: witnessPath, reason: verdict.reason, pointer: verdict.path ?? null,
433
+ });
434
+ }
435
+ const request = synthesizeWitnessRunRequest(witnessSet, { baseSha, requestId });
436
+ const explained = explainRunRequest(request);
437
+ if (!explained.valid) {
438
+ // 生成物が自分の契約を満たさないなら、それは装置の不具合である。黙って出さない
439
+ throw new CliContractError('CONTRACT_VIOLATION', '生成したrun requestがrun_request契約を満たさない', {
440
+ reason: explained.reason, pointer: explained.path ?? null,
441
+ });
442
+ }
443
+ stdout.write(`${JSON.stringify(request)}\n`);
444
+ return 0;
445
+ }
446
+
349
447
  async function runAdapterList({ cwd, stdout }) {
350
448
  try {
351
449
  const repoRoot = await resolveRepoRoot(cwd);
@@ -523,6 +621,9 @@ async function readRunStore(runDir) {
523
621
  throw new CliContractError('INVALID_RUN_STORE', 'events.jsonがarrayではない');
524
622
  }
525
623
  const meta = await readBoundedJson(path.join(runDir, 'run-meta.json'), 'run meta');
624
+ rejectFutureRuntimeStoreSchema(meta?.schema, {
625
+ artifact: 'run_meta', family: 'lattice.run_meta', expectedVersion: 2,
626
+ });
526
627
  const compileArtifact = await readBoundedJson(
527
628
  path.join(runDir, 'plan-compile-result.json'), 'plan compile result',
528
629
  );
@@ -536,9 +637,11 @@ async function readRunStore(runDir) {
536
637
  && meta.run_id === request.request_id
537
638
  && KNOWN_ADAPTERS.includes(meta.executor_adapter)
538
639
  && compileArtifact?.plan?.plan_digest === meta.plan_digest;
539
- const managed = meta?.schema === 'lattice.run_meta.v2'
540
- ? await readCommittedEpochStore(runDir)
541
- : null;
640
+ // v1だけがlegacy alias。その他はmulti-epoch readerへ渡し、既知familyの
641
+ // 将来世代を破損storeへ潰さずtyped診断する。
642
+ const managed = meta?.schema === 'lattice.run_meta.v1'
643
+ ? null
644
+ : await readCommittedEpochStore(runDir);
542
645
  const managedMetaValid = managed !== null
543
646
  && managed.meta.run_id === request.request_id
544
647
  && KNOWN_ADAPTERS.includes(managed.meta.executor_adapter)
@@ -806,6 +909,7 @@ async function driveScriptedManagedEpoch({
806
909
  drainEscalations = null,
807
910
  escalateTerminalConflict = null,
808
911
  preactivated = null,
912
+ reportDriverState = null,
809
913
  }) {
810
914
  let events = [...initialEvents];
811
915
  const { plan, manifests, executor_packets: packets } = committed.bundle;
@@ -819,6 +923,7 @@ async function driveScriptedManagedEpoch({
819
923
  for (;;) {
820
924
  const frontier = computeReadyFrontier({ plan, events }).dispatchable;
821
925
  if (frontier.length === 0) break;
926
+ await reportDriverState?.({ kind: 'frontier_dispatch', todo_ids: [...frontier].sort() });
822
927
  const alreadyRunning = projectRuntimeState({ events }).running;
823
928
  const issuedControlDigest = controlEvents().at(-1)?.event_digest;
824
929
  if (typeof issuedControlDigest !== 'string') {
@@ -936,9 +1041,11 @@ async function driveScriptedManagedEpoch({
936
1041
  process_group_id: response.worker_process.process_group_id,
937
1042
  process_start_identity:
938
1043
  structuredClone(response.worker_process.process_start_identity),
939
- // workerはさらに子を持たない。空配列は「子が居ない」という主張であり、
940
- // 直接OS観測は実測と突き合わせて未記録のchildが居ないことまで確かめる。
1044
+ // 省略は従来adapterのstatic child集合。外部長寿命workerはroot identityと
1045
+ // PGIDだけを不変にし、barrier時点の全group member静止を検証する。
941
1046
  process_children: [],
1047
+ process_membership_policy:
1048
+ response.worker_process.process_membership ?? 'static',
942
1049
  // TODOごとの木を指す。ここがrepo rootだった頃、帰属はrootから決まらなかった。
943
1050
  worktree_path: worktreeByTodo.get(packet.todo_id),
944
1051
  worktree_realpath: worktreeByTodo.get(packet.todo_id),
@@ -1012,8 +1119,10 @@ async function driveScriptedManagedEpoch({
1012
1119
  // 回し、その合間に早期警報のescalationを捌く。捌くのは`replaceEventsAtomically`の
1013
1120
  // 直後だけ——そこでしかdiskとメモリのeventsが一致していない。
1014
1121
  const awaiting = new Set([...dispatched.dispatched, ...alreadyRunning]);
1122
+ if (awaiting.size > 0) {
1123
+ await reportDriverState?.({ kind: 'executor_completion', todo_ids: [...awaiting].sort() });
1124
+ }
1015
1125
  const completedReceiptIds = new Set();
1016
- const observeDeadline = Date.now() + SCRIPTED_OBSERVE_TIMEOUT_MS;
1017
1126
  let frozen = false;
1018
1127
  while (awaiting.size > 0) {
1019
1128
  const drained = await drainEscalations?.(events) ?? null;
@@ -1088,7 +1197,9 @@ async function driveScriptedManagedEpoch({
1088
1197
  paths: peerCheckpoint.payload.diff.entries.map((entry) => entry.path) });
1089
1198
  }
1090
1199
  const actionable = classifyObservedDiff({ plan, manifests, observations,
1091
- relevantTodoIds }).findings.filter((finding) => finding.kind === 'observed_write_conflict'
1200
+ relevantTodoIds }).findings.filter((finding) => [
1201
+ 'observed_write_conflict', 'observed_line_change',
1202
+ ].includes(finding.kind)
1092
1203
  && finding.todo_ids.includes(todoId));
1093
1204
  if (actionable.length > 0) {
1094
1205
  if (typeof escalateTerminalConflict !== 'function') {
@@ -1107,17 +1218,18 @@ async function driveScriptedManagedEpoch({
1107
1218
  && event.subject?.kind === 'todo' && event.subject.ref === id)
1108
1219
  ?.payload?.direct_os_observation_binding?.worktree_path });
1109
1220
  awaiting.delete(todoId);
1221
+ if (awaiting.size > 0) {
1222
+ await reportDriverState?.({ kind: 'executor_completion', todo_ids: [...awaiting].sort() });
1223
+ }
1110
1224
  progressed = true;
1111
1225
  if (frozen) break;
1112
1226
  }
1113
1227
  if (frozen) break;
1114
1228
  if (awaiting.size === 0) break;
1115
- if (Date.now() >= observeDeadline) {
1116
- throw new ManagedRuntimeError(
1117
- 'ADAPTER_CONTROLLER_UNAVAILABLE',
1118
- `scripted workerが時間内に終わらない: ${[...awaiting].join(',')}`,
1119
- );
1120
- }
1229
+ // 長寿命workerの正常な作業時間を固定期限でcontroller障害へ変換しない。
1230
+ // controllerとのroute自体が失敗した時だけtyped errorになり、running応答中は
1231
+ // durable supervisorが観測を継続する。CLIはoutcome unknownで先に戻り得るが、
1232
+ // run resumeは同じstoreをread-only投影し、leaseを再認可しない。
1121
1233
  if (!progressed) {
1122
1234
  await new Promise((resolve) => { setTimeout(resolve, SCRIPTED_OBSERVE_POLL_MS); });
1123
1235
  }
@@ -1147,8 +1259,14 @@ async function driveScriptedManagedEpoch({
1147
1259
  return events;
1148
1260
  }
1149
1261
 
1150
- function isDistributedScriptedControllerActivation(activation) {
1151
- return activation?.controllerDescriptor?.adapter_kind === 'scripted'
1262
+ function isHostDrivenEpochActivation(activation) {
1263
+ const controllerCapabilities = activation?.controllerDescriptor?.capabilities;
1264
+ if (acceptsHostDrivenEpoch(controllerCapabilities)) {
1265
+ return activation?.launchDescriptor?.capabilities_digest
1266
+ === controllerCapabilities.capabilities_digest;
1267
+ }
1268
+ return controllerCapabilities?.schema === 'lattice.runtime_adapter_capabilities.v1'
1269
+ && activation?.controllerDescriptor?.adapter_kind === 'scripted'
1152
1270
  && activation?.launchDescriptor?.launch_kind === 'host_binary'
1153
1271
  && activation.launchDescriptor.argv.some((argument) => (
1154
1272
  path.basename(argument) === 'lattice-scripted-adapter.mjs'
@@ -1238,6 +1356,19 @@ async function runStart({ requestPath, executorAdapter, cwd, stdout }) {
1238
1356
  return 0;
1239
1357
  }
1240
1358
 
1359
+ async function runPullStart({ runId, planKey, equipment, cwd, stdout }) {
1360
+ const repoRoot = await resolveRepoRoot(cwd);
1361
+ await requireSafeRunAncestors(repoRoot);
1362
+ await requireIgnoredRunStore(repoRoot);
1363
+ const runDir = runStorePath(repoRoot, runId);
1364
+ await mkdir(path.dirname(runDir), { recursive: true });
1365
+ const output = await startPullRun({ repoRoot, runDir, runId, planKey, equipment });
1366
+ output.run_dir = path.relative(repoRoot, runDir);
1367
+ output.result_digest = digestArtifact(output);
1368
+ stdout.write(`${JSON.stringify(output)}\n`);
1369
+ return 0;
1370
+ }
1371
+
1241
1372
  /**
1242
1373
  * 記録済み競合findingを、実際の変換で解消する(請求項8・ADR 0137〜0141)。
1243
1374
  *
@@ -1341,7 +1472,54 @@ async function runSeamProfile({ runDir, repoRoot, findingDigest, inputPath, stdo
1341
1472
  return 0;
1342
1473
  }
1343
1474
 
1475
+ const stoppedDriverProjection = () => ({ driver_state: 'stopped', waiting_on: null });
1476
+
1477
+ async function runtimeDriverProjection(runDir) {
1478
+ const state = await readRuntimeDriverState({ runDir });
1479
+ if (state === null || state.driver_state === 'stopped') return stoppedDriverProjection();
1480
+ const descriptorPath = path.join(runDir, ...state.supervisor_descriptor_ref.split('/'));
1481
+ let descriptor;
1482
+ try {
1483
+ descriptor = await readBoundedJson(descriptorPath, 'runtime supervisor descriptor');
1484
+ } catch (error) {
1485
+ if (error instanceof CliContractError && error.code === 'INPUT_UNREADABLE') {
1486
+ return stoppedDriverProjection();
1487
+ }
1488
+ throw error;
1489
+ }
1490
+ if (descriptor?.descriptor_digest !== state.supervisor_descriptor_digest
1491
+ || descriptor?.process_start_identity?.identity_digest
1492
+ !== state.supervisor_process_start_identity_digest) {
1493
+ throw new RuntimeDriverStateError('INVALID_RUN_STORE', 'driver stateがsupervisor descriptorへbindしない');
1494
+ }
1495
+ let observedIdentity;
1496
+ try {
1497
+ observedIdentity = await observeManagedProcessStartIdentity(descriptor.pid);
1498
+ } catch (error) {
1499
+ if (error instanceof ManagedRuntimeError && error.code === 'ADAPTER_CONTROLLER_UNAVAILABLE') {
1500
+ return stoppedDriverProjection();
1501
+ }
1502
+ throw error;
1503
+ }
1504
+ if (observedIdentity.identity_digest !== state.supervisor_process_start_identity_digest) {
1505
+ return stoppedDriverProjection();
1506
+ }
1507
+ return { driver_state: 'driving', waiting_on: structuredClone(state.waiting_on) };
1508
+ }
1509
+
1510
+ async function withRuntimeDriverProjection(output, runDir) {
1511
+ const projected = { ...output, ...await runtimeDriverProjection(runDir) };
1512
+ delete projected.result_digest;
1513
+ projected.result_digest = digestArtifact(projected);
1514
+ return projected;
1515
+ }
1516
+
1344
1517
  async function runObserve({ runDir, stdout }) {
1518
+ if ((await inspectRunMode(runDir)).mode === 'pull') {
1519
+ const output = await withRuntimeDriverProjection(await observePullRun(runDir), runDir);
1520
+ stdout.write(`${JSON.stringify(output)}\n`);
1521
+ return 0;
1522
+ }
1345
1523
  const { events } = await readRunStore(runDir);
1346
1524
  const chain = verifyRunEventChain({ events });
1347
1525
  if (!chain.valid) {
@@ -1359,6 +1537,7 @@ async function runObserve({ runDir, stdout }) {
1359
1537
  closed: state.closed,
1360
1538
  event_count: events.length,
1361
1539
  events_digest: digestArtifact(events.map(({ event_digest: digest }) => digest)),
1540
+ ...await runtimeDriverProjection(runDir),
1362
1541
  };
1363
1542
  output.result_digest = digestArtifact(output);
1364
1543
  stdout.write(`${JSON.stringify(output)}\n`);
@@ -1366,6 +1545,11 @@ async function runObserve({ runDir, stdout }) {
1366
1545
  }
1367
1546
 
1368
1547
  async function runStatus({ runDir, stdout }) {
1548
+ if ((await inspectRunMode(runDir)).mode === 'pull') {
1549
+ const output = await withRuntimeDriverProjection(await statusPullRun(runDir), runDir);
1550
+ stdout.write(`${JSON.stringify(output)}\n`);
1551
+ return 0;
1552
+ }
1369
1553
  const { events, meta, compileArtifact, managed } = await readRunStore(runDir);
1370
1554
  const chain = verifyRunEventChain({ events });
1371
1555
  if (!chain.valid) {
@@ -1386,6 +1570,7 @@ async function runStatus({ runDir, stdout }) {
1386
1570
  freeze_active: state.freeze !== null,
1387
1571
  closed: state.closed,
1388
1572
  event_count: events.length,
1573
+ ...await runtimeDriverProjection(runDir),
1389
1574
  };
1390
1575
  if (managed !== null) {
1391
1576
  output.schema = 'lattice.managed_run_status.v1';
@@ -1425,6 +1610,14 @@ async function runList({ cwd, stdout }) {
1425
1610
  throw new CliContractError('INVALID_RUN_STORE', `不正なrun store entry: ${entry.name}`);
1426
1611
  }
1427
1612
  const runDir = path.join(root, entry.name);
1613
+ const mode = await inspectRunMode(runDir);
1614
+ if (mode.mode === 'pull') {
1615
+ const pull = await listPullRunEntry(runDir);
1616
+ if (!pull.closed) activeRuns.push({
1617
+ ...pull.entry, run_ref: `.lattice/runs/${entry.name}`,
1618
+ });
1619
+ continue;
1620
+ }
1428
1621
  const { events, meta, request } = await readRunStore(runDir);
1429
1622
  requireValidEventChain(events);
1430
1623
  if (!projectRuntimeState({ events }).closed) {
@@ -1457,8 +1650,9 @@ async function runResume({ runDir, repoRoot, stdout }) {
1457
1650
  throw new CliContractError('RUN_CLOSED', 'closed runはresumeできない');
1458
1651
  }
1459
1652
  await resolveRepoBinding(repoRoot, request);
1460
- // managed write gateの完全検証とdispatchsupervisorだけが所有する。read-only
1461
- // resumeがCLIからleaseを再認可しないよう、managed runでは常に空frontierを返す。
1653
+ // managed write gateの完全検証とdispatch・長寿命workerの観測継続はsupervisorだけが
1654
+ // 所有する。read-only resumeがCLIからleaseを再認可しないよう、managed runでは
1655
+ // 常に空frontierを返し、生きたsupervisorが更新するstoreの現在地だけを返す。
1462
1656
  const managedFrozen = managed === null ? false : await isManagedRunFrozen(runDir, events);
1463
1657
  const frontier = managedFrozen
1464
1658
  ? { dispatchable: [] }
@@ -1478,7 +1672,140 @@ async function runResume({ runDir, repoRoot, stdout }) {
1478
1672
  return 0;
1479
1673
  }
1480
1674
 
1675
+ /**
1676
+ * accepted receiptが束縛したworktree HEADの、remote既定branchへの着地状態を投影する。
1677
+ *
1678
+ * receipt本体はhead_shaを持たない。裁定時にexact bindされたcheckpoint_digestから、
1679
+ * 同じTODOのcheckpoint_observed.diff.head_shaへ辿る。git refsとrun storeを読むだけで、
1680
+ * branchもevent列も動かさない。未着地・upstream無しは判断結果であり失敗ではない。
1681
+ */
1682
+ async function buildRunLandingReport({ runDir, repoRoot, events: providedEvents = null,
1683
+ runId: providedRunId = null }) {
1684
+ let events = providedEvents;
1685
+ let runId = providedRunId;
1686
+ if (events === null || runId === null) {
1687
+ const stored = await readRunStore(runDir);
1688
+ events = stored.events;
1689
+ runId = stored.meta.run_id;
1690
+ }
1691
+ requireValidEventChain(events);
1692
+ const state = projectRuntimeState({ events });
1693
+
1694
+ const push = await runGit(['rev-parse', '--symbolic-full-name', '@{push}'], repoRoot);
1695
+ let pushState = 'no_upstream';
1696
+ let pushRef = null;
1697
+ let unpushedCommits = null;
1698
+ let remoteName = null;
1699
+ if (push.code === 0 && push.stdout.trim().length > 0) {
1700
+ pushRef = push.stdout.trim();
1701
+ const remoteMatch = /^refs\/remotes\/([^/]+)\/.+$/u.exec(pushRef);
1702
+ remoteName = remoteMatch?.[1] ?? null;
1703
+ const count = await runGit(['rev-list', '--count', `${pushRef}..HEAD`], repoRoot);
1704
+ if (count.code !== 0 || !/^\d+$/u.test(count.stdout.trim())) {
1705
+ throw new CliContractError('LANDING_GIT_UNRESOLVED',
1706
+ `push差分を読めない: ${count.stderr.trim() || count.stdout.trim()}`);
1707
+ }
1708
+ pushState = 'tracked';
1709
+ unpushedCommits = Number(count.stdout.trim());
1710
+ }
1711
+
1712
+ if (remoteName === null) {
1713
+ const remotes = await runGit(['remote'], repoRoot);
1714
+ if (remotes.code !== 0) {
1715
+ throw new CliContractError('LANDING_GIT_UNRESOLVED',
1716
+ `remote一覧を読めない: ${remotes.stderr.trim()}`);
1717
+ }
1718
+ const names = remotes.stdout.split('\n').map((name) => name.trim()).filter(Boolean);
1719
+ if (names.length === 1) [remoteName] = names;
1720
+ }
1721
+ let defaultBranchState = 'unresolved';
1722
+ let defaultBranchRef = null;
1723
+ if (remoteName !== null) {
1724
+ const symbolic = await runGit(
1725
+ ['symbolic-ref', '--quiet', `refs/remotes/${remoteName}/HEAD`], repoRoot,
1726
+ );
1727
+ const candidate = symbolic.stdout.trim();
1728
+ if (symbolic.code === 0
1729
+ && candidate.startsWith(`refs/remotes/${remoteName}/`)
1730
+ && candidate !== `refs/remotes/${remoteName}/HEAD`) {
1731
+ defaultBranchState = 'resolved';
1732
+ defaultBranchRef = candidate;
1733
+ }
1734
+ }
1735
+
1736
+ const acceptedReceipts = [];
1737
+ const accepted = state.receipts
1738
+ .filter((receipt) => receipt.accepted_sequence !== null)
1739
+ .sort((left, right) => left.todo_id.localeCompare(right.todo_id)
1740
+ || left.receipt_id.localeCompare(right.receipt_id));
1741
+ for (const receipt of accepted) {
1742
+ const checkpointDigest = receipt.payload?.checkpoint_digest;
1743
+ const checkpoint = state.checkpoints.findLast((entry) => (
1744
+ entry.todo_id === receipt.todo_id
1745
+ && entry.sequence < receipt.accepted_sequence
1746
+ && entry.payload?.checkpoint_digest === checkpointDigest
1747
+ ));
1748
+ const candidateHead = checkpoint?.payload?.diff?.head_sha;
1749
+ const headSha = typeof candidateHead === 'string' && /^[0-9a-f]{40}$/u.test(candidateHead)
1750
+ ? candidateHead : null;
1751
+ let landingState = 'head_unavailable';
1752
+ let landed = false;
1753
+ if (headSha !== null && defaultBranchRef === null) {
1754
+ landingState = 'default_branch_unresolved';
1755
+ } else if (headSha !== null) {
1756
+ const ancestry = await runGit(
1757
+ ['merge-base', '--is-ancestor', headSha, defaultBranchRef], repoRoot,
1758
+ );
1759
+ if (ancestry.code === 0) {
1760
+ landingState = 'landed';
1761
+ landed = true;
1762
+ } else if (ancestry.code === 1) {
1763
+ landingState = 'not_landed';
1764
+ } else {
1765
+ throw new CliContractError('LANDING_GIT_UNRESOLVED',
1766
+ `receipt HEADの祖先性を読めない: ${ancestry.stderr.trim()}`);
1767
+ }
1768
+ }
1769
+ acceptedReceipts.push({
1770
+ todo_id: receipt.todo_id,
1771
+ receipt_id: receipt.receipt_id,
1772
+ head_sha: headSha,
1773
+ landing_state: landingState,
1774
+ landed,
1775
+ });
1776
+ }
1777
+
1778
+ const output = {
1779
+ schema: 'lattice.run_landing_report.v1',
1780
+ run_id: runId,
1781
+ landed: acceptedReceipts.length > 0 && acceptedReceipts.every((receipt) => receipt.landed),
1782
+ accepted_receipts: acceptedReceipts,
1783
+ repository: {
1784
+ default_branch_state: defaultBranchState,
1785
+ default_branch_ref: defaultBranchRef,
1786
+ push_state: pushState,
1787
+ push_ref: pushRef,
1788
+ unpushed_commits: unpushedCommits,
1789
+ },
1790
+ };
1791
+ output.result_digest = digestArtifact(output);
1792
+ return output;
1793
+ }
1794
+
1795
+ async function runLanding({ runDir, repoRoot, stdout }) {
1796
+ const output = (await inspectRunMode(runDir)).mode === 'pull'
1797
+ ? await landingPullRun({ runDir, repoRoot })
1798
+ : await buildRunLandingReport({ runDir, repoRoot });
1799
+ stdout.write(`${JSON.stringify(output)}\n`);
1800
+ return 0;
1801
+ }
1802
+
1481
1803
  async function runClose({ runDir, repoRoot, stdout, requestId = null }) {
1804
+ if ((await inspectRunMode(runDir)).mode === 'pull') {
1805
+ const output = await closePullRun({ runDir, repoRoot });
1806
+ stdout.write(`${JSON.stringify(output)}\n`);
1807
+ return 0;
1808
+ }
1482
1809
  return withLifecycleLock(runDir, async () => {
1483
1810
  const { events, meta, compileArtifact, request, managed } = await readRunStore(runDir);
1484
1811
  requireValidEventChain(events);
@@ -1489,7 +1816,6 @@ async function runClose({ runDir, repoRoot, stdout, requestId = null }) {
1489
1816
  if (alreadyClosed && existingClose?.payload?.outcome === 'abandoned') {
1490
1817
  throw new CliContractError('RUN_ABANDONED', 'abandoned runは正常closeへ変更できない');
1491
1818
  }
1492
- await resolveRepoBinding(repoRoot, request);
1493
1819
  if (!alreadyClosed) {
1494
1820
  const closed = closeRunIfComplete({
1495
1821
  runId: meta.run_id,
@@ -1521,6 +1847,9 @@ async function runClose({ runDir, repoRoot, stdout, requestId = null }) {
1521
1847
  already_closed: alreadyClosed,
1522
1848
  event_count: next.length,
1523
1849
  events_digest: digestArtifact(next.map(({ event_digest: digest }) => digest)),
1850
+ landing: await buildRunLandingReport({
1851
+ runDir, repoRoot, events: next, runId: meta.run_id,
1852
+ }),
1524
1853
  };
1525
1854
  output.result_digest = digestArtifact(output);
1526
1855
  stdout.write(`${JSON.stringify(output)}\n`);
@@ -1909,7 +2238,7 @@ async function runActivate({ runDir, runRef, repoRoot, stdout, requestId = null
1909
2238
  let ambiguous = false;
1910
2239
  for (let attempt = 0; attempt < 100; attempt += 1) {
1911
2240
  try {
1912
- response = await sendRuntimeActivationRequest({
2241
+ response = await sendRuntimeActivationRequestUntilSettled({
1913
2242
  socketPath: launched.socketPath, request, expectedPid: launched.pid,
1914
2243
  expectedProcessStartIdentity: launched.processStartIdentity,
1915
2244
  });
@@ -2294,12 +2623,13 @@ export async function runManagedSupervisorDaemon({
2294
2623
 
2295
2624
  const escalateTerminalConflict = async ({ finding, checkpointDigest }) => {
2296
2625
  const activeEpoch = await readCommittedEpochStore(runDir);
2626
+ const resourceFinding = finding.kind === 'observed_line_change';
2297
2627
  const candidate = {
2298
2628
  schema: 'lattice.runtime_finding_candidate.v1',
2299
2629
  proposed_kind: finding.kind,
2300
2630
  todo_ids: [...finding.todo_ids].sort(),
2301
- path: finding.path,
2302
- resource_id: null,
2631
+ path: resourceFinding ? null : finding.path,
2632
+ resource_id: resourceFinding ? finding.resource_id : null,
2303
2633
  evidence_digests: [checkpointDigest],
2304
2634
  candidate_digest: '',
2305
2635
  };
@@ -2457,7 +2787,7 @@ export async function runManagedSupervisorDaemon({
2457
2787
  for (const extra of additionalActivations) {
2458
2788
  await extra.registerWithManagedSupervisor(managedSupervisor);
2459
2789
  }
2460
- if (!restarting && isDistributedScriptedControllerActivation(activation)) {
2790
+ if (!restarting && isHostDrivenEpochActivation(activation)) {
2461
2791
  sentinel = createRunSentinel({
2462
2792
  packets: committed.bundle.executor_packets,
2463
2793
  onWarning: (warning) => {
@@ -2472,6 +2802,19 @@ export async function runManagedSupervisorDaemon({
2472
2802
  return settled;
2473
2803
  },
2474
2804
  });
2805
+ const reportDriverState = (waitingOn) => replaceRuntimeDriverState({
2806
+ runDir,
2807
+ runId: request.request_id,
2808
+ supervisorDescriptorRef: restarting
2809
+ ? `supervisor/restart-candidates/${path.basename(candidateDir)}/descriptor.json`
2810
+ : 'supervisor/descriptor.json',
2811
+ supervisorDescriptorDigest: activation.supervisorDescriptor.descriptor_digest,
2812
+ supervisorProcessStartIdentityDigest:
2813
+ activation.supervisorDescriptor.process_start_identity.identity_digest,
2814
+ driverState: 'driving',
2815
+ waitingOn,
2816
+ updatedAt: canonicalNow(),
2817
+ });
2475
2818
  epochDriveActive = true;
2476
2819
  try {
2477
2820
  await driveScriptedManagedEpoch({
@@ -2487,9 +2830,23 @@ export async function runManagedSupervisorDaemon({
2487
2830
  preDispatchBindings,
2488
2831
  drainEscalations: drainPendingEscalations,
2489
2832
  escalateTerminalConflict,
2833
+ reportDriverState,
2490
2834
  });
2491
2835
  } finally {
2492
2836
  epochDriveActive = false;
2837
+ await replaceRuntimeDriverState({
2838
+ runDir,
2839
+ runId: request.request_id,
2840
+ supervisorDescriptorRef: restarting
2841
+ ? `supervisor/restart-candidates/${path.basename(candidateDir)}/descriptor.json`
2842
+ : 'supervisor/descriptor.json',
2843
+ supervisorDescriptorDigest: activation.supervisorDescriptor.descriptor_digest,
2844
+ supervisorProcessStartIdentityDigest:
2845
+ activation.supervisorDescriptor.process_start_identity.identity_digest,
2846
+ driverState: 'stopped',
2847
+ waitingOn: null,
2848
+ updatedAt: canonicalNow(),
2849
+ });
2493
2850
  }
2494
2851
  }
2495
2852
  if (restarting) {
@@ -2590,6 +2947,7 @@ export async function runManagedSupervisorDaemon({
2590
2947
  if (candidate.path !== null) {
2591
2948
  const producer = detectCheckpointFindings({ todoId: observed.subject.ref,
2592
2949
  checkpoint: observed.payload, packets: active.bundle.executor_packets,
2950
+ manifests: fresh.manifests,
2593
2951
  runningTodoIds: projectRuntimeState({ events }).running }).findings;
2594
2952
  const match = producer.find((findingValue) => findingValue.kind === candidate.proposed_kind
2595
2953
  && findingValue.path === candidate.path
@@ -3199,7 +3557,7 @@ export async function runManagedSupervisorDaemon({
3199
3557
  event_digest: events.at(-1).event_digest,
3200
3558
  });
3201
3559
  if (recompileRequest.mode === 'intentional_serial'
3202
- && isDistributedScriptedControllerActivation(activation)) {
3560
+ && isHostDrivenEpochActivation(activation)) {
3203
3561
  epochDriveActive = true;
3204
3562
  try {
3205
3563
  events = await driveScriptedManagedEpoch({
@@ -3965,6 +4323,14 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
3965
4323
  && argv[0] === 'run' && argv[1] === 'adapter' && argv[2] === 'register'
3966
4324
  && argv[3] === '--schema' && argv[4] === '--json') {
3967
4325
  action = () => runAdapterRegisterSchema({ stdout });
4326
+ } else if (argv.length === 9
4327
+ && argv[0] === 'run' && argv[1] === 'request' && argv[2] === 'synthesize'
4328
+ && argv[3] === '--witness' && typeof argv[4] === 'string' && argv[4].length > 0
4329
+ && argv[5] === '--base-sha' && typeof argv[6] === 'string' && argv[6].length > 0
4330
+ && argv[7] === '--id' && typeof argv[8] === 'string' && argv[8].length > 0) {
4331
+ action = () => runRequestSynthesize({
4332
+ cwd, witnessPath: argv[4], baseSha: argv[6], requestId: argv[8], stdout,
4333
+ });
3968
4334
  } else if (argv.length === 5
3969
4335
  && argv[0] === 'run' && argv[1] === 'adapter' && argv[2] === 'register'
3970
4336
  && argv[3] === '--input' && typeof argv[4] === 'string' && argv[4].length > 0) {
@@ -4005,21 +4371,84 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4005
4371
  cwd,
4006
4372
  stdout,
4007
4373
  });
4374
+ } else if (argv.length === 10
4375
+ && argv[0] === 'run' && argv[1] === 'start'
4376
+ && argv[2] === '--selection' && argv[3] === 'pull'
4377
+ && argv[4] === '--id' && typeof argv[5] === 'string' && argv[5].length > 0
4378
+ && argv[6] === '--plan' && typeof argv[7] === 'string' && argv[7].length > 0
4379
+ && argv[8] === '--equipment' && argv[9] === 'detached-worktree') {
4380
+ action = () => runPullStart({
4381
+ runId: argv[5], planKey: argv[7], equipment: argv[9], cwd, stdout,
4382
+ });
4383
+ } else if (argv.length === 6
4384
+ && argv[0] === 'run' && argv[1] === 'intake'
4385
+ && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0
4386
+ && argv[4] === '--task' && typeof argv[5] === 'string' && argv[5].length > 0) {
4387
+ action = async () => {
4388
+ const { repoRoot, runDir } = await resolveRunStore(cwd, argv[3]);
4389
+ const output = await intakePullTask({ repoRoot, runDir, taskId: argv[5] });
4390
+ stdout.write(`${JSON.stringify(output)}\n`); return 0;
4391
+ };
4392
+ } else if (argv.length === 7
4393
+ && argv[0] === 'run' && argv[1] === 'intake' && argv[2] === 'accept'
4394
+ && argv[3] === '--run' && typeof argv[4] === 'string' && argv[4].length > 0
4395
+ && argv[5] === '--task' && typeof argv[6] === 'string' && argv[6].length > 0) {
4396
+ action = async () => {
4397
+ const { repoRoot, runDir } = await resolveRunStore(cwd, argv[4]);
4398
+ const output = await acceptPullTask({ repoRoot, runDir, taskId: argv[6] });
4399
+ stdout.write(`${JSON.stringify(output)}\n`); return 0;
4400
+ };
4401
+ } else if (argv.length === 7
4402
+ && argv[0] === 'run' && argv[1] === 'intake' && argv[2] === 'release'
4403
+ && argv[3] === '--run' && typeof argv[4] === 'string' && argv[4].length > 0
4404
+ && argv[5] === '--task' && typeof argv[6] === 'string' && argv[6].length > 0) {
4405
+ action = async () => {
4406
+ const { runDir } = await resolveRunStore(cwd, argv[4]);
4407
+ const output = await releasePullTask({ runDir, taskId: argv[6] });
4408
+ stdout.write(`${JSON.stringify(output)}\n`); return 0;
4409
+ };
4410
+ } else if (argv.length === 9
4411
+ && argv[0] === 'run' && argv[1] === 'intake' && argv[2] === 'attach'
4412
+ && argv[3] === '--run' && typeof argv[4] === 'string' && argv[4].length > 0
4413
+ && argv[5] === '--task' && typeof argv[6] === 'string' && argv[6].length > 0
4414
+ && argv[7] === '--input' && typeof argv[8] === 'string' && argv[8].length > 0) {
4415
+ action = async () => {
4416
+ const { runDir } = await resolveRunStore(cwd, argv[4]);
4417
+ const input = await readBoundedJson(path.resolve(cwd, argv[8]), 'pull worker attach input');
4418
+ const output = await attachPullWorker({ runDir, taskId: argv[6], input });
4419
+ stdout.write(`${JSON.stringify(output)}\n`); return 0;
4420
+ };
4421
+ } else if (argv.length === 7
4422
+ && argv[0] === 'run' && argv[1] === 'intake' && argv[2] === 'intervention'
4423
+ && argv[3] === '--run' && typeof argv[4] === 'string' && argv[4].length > 0
4424
+ && argv[5] === '--task' && typeof argv[6] === 'string' && argv[6].length > 0) {
4425
+ action = async () => {
4426
+ const { runDir } = await resolveRunStore(cwd, argv[4]);
4427
+ const output = await interventionPullTask(runDir, argv[6]);
4428
+ stdout.write(`${JSON.stringify(output)}\n`); return 0;
4429
+ };
4008
4430
  } else if (argv.length === 4
4009
4431
  && argv[0] === 'run' && argv[1] === 'activate'
4010
4432
  && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
4011
4433
  action = async () => {
4012
4434
  const { repoRoot, runDir, runRef } = await resolveRunStore(cwd, argv[3]);
4435
+ await requireLegacyRunMode(runDir, 'activate');
4013
4436
  return runActivate({ runDir, runRef, repoRoot, stdout, requestId: requestIdOverride });
4014
4437
  };
4015
4438
  } else if (argv.length === 4
4016
- && argv[0] === 'run' && ['observe', 'status', 'resume', 'close'].includes(argv[1])
4439
+ && argv[0] === 'run' && ['observe', 'status', 'resume', 'landing', 'close'].includes(argv[1])
4017
4440
  && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
4018
4441
  action = async () => {
4019
4442
  const { repoRoot, runDir } = await resolveRunStore(cwd, argv[3]);
4020
4443
  if (argv[1] === 'observe') return runObserve({ runDir, stdout });
4021
4444
  if (argv[1] === 'status') return runStatus({ runDir, stdout });
4022
- if (argv[1] === 'resume') return runResume({ runDir, repoRoot, stdout });
4445
+ if (argv[1] === 'resume') {
4446
+ if ((await inspectRunMode(runDir)).mode === 'pull') {
4447
+ throw new PullRunError('RUN_MODE_MISMATCH', 'pull runはresumeでleaseを再認可しない');
4448
+ }
4449
+ return runResume({ runDir, repoRoot, stdout });
4450
+ }
4451
+ if (argv[1] === 'landing') return runLanding({ runDir, repoRoot, stdout });
4023
4452
  return runClose({ runDir, repoRoot, stdout, requestId: requestIdOverride });
4024
4453
  };
4025
4454
  } else if (argv.length === 6
@@ -4028,6 +4457,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4028
4457
  && argv[4] === '--reason' && typeof argv[5] === 'string') {
4029
4458
  action = async () => {
4030
4459
  const { runDir } = await resolveRunStore(cwd, argv[3]);
4460
+ await requireLegacyRunMode(runDir, 'abandon');
4031
4461
  return runAbandon({ runDir, runRef: argv[3], reason: argv[5], stdout,
4032
4462
  requestId: requestIdOverride });
4033
4463
  };
@@ -4037,6 +4467,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4037
4467
  && argv[4] === '--finding' && /^[0-9a-f]{64}$/u.test(argv[5])) {
4038
4468
  action = async () => {
4039
4469
  const { runDir, runRef } = await resolveRunStore(cwd, argv[3]);
4470
+ await requireLegacyRunMode(runDir, 'conflict');
4040
4471
  return runManagedControl({
4041
4472
  runDir, runRef, operation: 'conflict', artifactDigest: argv[5], stdout,
4042
4473
  requestId: requestIdOverride,
@@ -4049,6 +4480,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4049
4480
  && argv[7] === '--input' && typeof argv[8] === 'string' && argv[8].length > 0) {
4050
4481
  action = async () => {
4051
4482
  const { repoRoot, runDir } = await resolveRunStore(cwd, argv[4]);
4483
+ await requireLegacyRunMode(runDir, 'seam profile');
4052
4484
  return runSeamProfile({ runDir, repoRoot, findingDigest: argv[6],
4053
4485
  inputPath: path.resolve(cwd, argv[8]), stdout });
4054
4486
  };
@@ -4059,6 +4491,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4059
4491
  && argv[7] === '--input' && typeof argv[8] === 'string' && argv[8].length > 0) {
4060
4492
  action = async () => {
4061
4493
  const { repoRoot, runDir } = await resolveRunStore(cwd, argv[4]);
4494
+ await requireLegacyRunMode(runDir, 'seam resolve');
4062
4495
  return runSeamResolve({ runDir, repoRoot, findingDigest: argv[6],
4063
4496
  requestPath: path.resolve(cwd, argv[8]), stdout });
4064
4497
  };
@@ -4076,6 +4509,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4076
4509
  && argv[7] === '--input' && typeof argv[8] === 'string' && argv[8].length > 0) {
4077
4510
  action = async () => {
4078
4511
  const { runDir, runRef } = await resolveRunStore(cwd, argv[4]);
4512
+ await requireLegacyRunMode(runDir, 'finding record');
4079
4513
  if (await readCommittedEpochStore(runDir) === null) {
4080
4514
  throw new CliContractError('RUN_NOT_MANAGED', 'runがmanaged storeへactivateされていない');
4081
4515
  }
@@ -4090,6 +4524,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4090
4524
  && argv[4] === '--input' && typeof argv[5] === 'string' && argv[5].length > 0) {
4091
4525
  action = async () => {
4092
4526
  const { runDir, runRef } = await resolveRunStore(cwd, argv[3]);
4527
+ await requireLegacyRunMode(runDir, 'recompile');
4093
4528
  if (await readCommittedEpochStore(runDir) === null) {
4094
4529
  throw new CliContractError('RUN_NOT_MANAGED', 'runがmanaged storeへactivateされていない');
4095
4530
  }
@@ -4102,6 +4537,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4102
4537
  && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
4103
4538
  action = async () => {
4104
4539
  const { runDir, runRef } = await resolveRunStore(cwd, argv[3]);
4540
+ await requireLegacyRunMode(runDir, argv[1]);
4105
4541
  return runManagedControl({
4106
4542
  runDir, runRef, operation: argv[1], artifactDigest: null, stdout,
4107
4543
  requestId: requestIdOverride,
@@ -4115,6 +4551,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4115
4551
  && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
4116
4552
  action = async () => {
4117
4553
  const { runDir } = await resolveRunStore(cwd, argv[3]);
4554
+ await requireLegacyRunMode(runDir, 'event verify');
4118
4555
  return eventVerify({ runDir, stdout });
4119
4556
  };
4120
4557
  }
@@ -4129,7 +4566,7 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4129
4566
  return typedFailure(stderr, 'CONTRACT_VIOLATION', error.message);
4130
4567
  }
4131
4568
  if (error instanceof RuntimeEpochStoreError) {
4132
- return typedFailure(stderr, error.code, error.message);
4569
+ return typedFailure(stderr, error.code, error.message, error.detail);
4133
4570
  }
4134
4571
  if (error instanceof AdapterRegistryError) {
4135
4572
  return typedFailure(stderr, error.code, error.message, error.detail);
@@ -4137,6 +4574,11 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4137
4574
  if (error instanceof ManagedRuntimeError) {
4138
4575
  return typedFailure(stderr, error.code, error.message);
4139
4576
  }
4577
+ if (error instanceof PullRunError || error instanceof TodoStoreError
4578
+ || error instanceof RuntimeDriverStateError
4579
+ || error instanceof RuntimeLifecycleLockError) {
4580
+ return typedFailure(stderr, error.code, error.message, error.detail);
4581
+ }
4140
4582
  throw error;
4141
4583
  }
4142
4584
  }