@quolu/lattice 0.41.0 → 0.41.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.41.0",
3
+ "version": "0.41.1",
4
4
  "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "author": {
6
6
  "name": "Quo / クオ at kitepon.dev",
package/src/cli-help.mjs CHANGED
@@ -30,18 +30,14 @@ Commands:
30
30
  create --schema --json # 既定は最新版(v4)のJSON Schemaを返す
31
31
  create --schema-version <1|2|3|4> --json
32
32
  show <plan_key> --json # task・依存・phase・状態をplan本体から1コマンドで投影する
33
- compile --request <request.json> [--todo-plan <key>]
33
+ compile --request <request.json>
34
34
  compile --schema --json # lattice.run_request.v1 の JSON Schema を出す
35
35
  verify --request <request.json> --plan <plan.json>
36
36
  `,
37
37
  run: `Usage: lattice run <command> [options]
38
38
 
39
39
  Commands:
40
- start --request <request.json> --plan <compile-artifact.json> --executor <adapter>
41
- # TODO storeと連携して同一repo writerを並列起動する場合のgate付き入口。
42
- # artifactはplan compile --todo-plan <key>で現revisionへ束縛して発行する
43
40
  start --request <request.json> --executor <adapter>
44
- # artifactを消費しない既存runtime互換入口
45
41
  start --schema --json # lattice.run_request.v1 の JSON Schema を出す
46
42
  adapter register --input <descriptor.json>
47
43
  adapter register --schema --json # 登録入力の JSON Schema を出す
@@ -81,8 +81,6 @@ import { createRuntimeControlRequest, validateRuntimeControlResponse } from './r
81
81
  import { createRuntimeControlStore } from './runtime-control-store.mjs';
82
82
  import { createRuntimeGateStore } from './runtime-gate-store.mjs';
83
83
  import { acquireRuntimeLifecycleLock } from './runtime-lifecycle-lock.mjs';
84
- import { readTodoIndependenceArtifact, readTodoStore } from './todo-store.mjs';
85
- import { projectIndependenceFrontier } from './todo-independence.mjs';
86
84
  import {
87
85
  AdapterRegistryError,
88
86
  listRuntimeAdapters,
@@ -132,47 +130,6 @@ const CLI_ERROR_SCHEMA = 'lattice.cli_error.v2';
132
130
  const RUN_STORE_ROOT = ['.lattice', 'runs'];
133
131
  const RUN_REF = /^\.lattice\/runs\/([0-9A-Za-z](?:[0-9A-Za-z._-]{0,127}))$/u;
134
132
  const KNOWN_ADAPTERS = Object.freeze(['scripted', 'isolated-worktree', 'actual-agent']);
135
-
136
- function sameTextSet(left, right) {
137
- return Array.isArray(left) && Array.isArray(right)
138
- && left.length === right.length
139
- && [...left].sort().every((value, index) => value === [...right].sort()[index]);
140
- }
141
-
142
- function compileArtifactBodyIsValid(artifact, request) {
143
- const keys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
144
- 'todo_plan_binding', 'result_digest'];
145
- if (artifact === null || typeof artifact !== 'object' || Array.isArray(artifact)
146
- || Object.keys(artifact).sort().join('\0') !== keys.sort().join('\0')
147
- || artifact.schema !== COMPILE_RESULT_SCHEMA) return false;
148
- const { result_digest: claimedDigest, ...body } = artifact;
149
- if (claimedDigest !== digestArtifact(body) || artifact.request_digest !== request.request_digest
150
- || !validateRuntimePlan(artifact.plan)
151
- || !verifyRuntimePlanBinding({ plan: artifact.plan, request })) return false;
152
- const nodeIds = artifact.plan.nodes.map((node) => node.todo_id);
153
- if (artifact.manifests === null || typeof artifact.manifests !== 'object'
154
- || Array.isArray(artifact.manifests)
155
- || !sameTextSet(Object.keys(artifact.manifests), nodeIds)) return false;
156
- return nodeIds.every((todoId) => validateRuntimeBoundaryManifest(artifact.manifests[todoId])
157
- && artifact.manifests[todoId].manifest_digest === artifact.plan.manifest_digests[todoId]);
158
- }
159
-
160
- function todoPlanBindingFor(member) {
161
- return {
162
- project_id: member.plan.project_id,
163
- plan_key: member.plan.plan_key,
164
- plan_version: member.plan.plan_version,
165
- topology_digest: member.plan.topology_digest,
166
- plan_digest: member.plan.plan_digest,
167
- };
168
- }
169
-
170
- function sameTodoPlanBinding(left, right) {
171
- return left !== null && typeof left === 'object' && !Array.isArray(left)
172
- && Object.keys(left).sort().join('\0') === ['project_id', 'plan_key', 'plan_version',
173
- 'topology_digest', 'plan_digest'].sort().join('\0')
174
- && Object.entries(right).every(([key, value]) => left[key] === value);
175
- }
176
133
  /**
177
134
  * 自動escalationがlifecycle lockを待つ上限(ADR 0143)。
178
135
  *
@@ -439,27 +396,9 @@ async function compileFromRepo({ request, cwd, planRef, planEpoch, predecessorRe
439
396
  });
440
397
  }
441
398
 
442
- async function planCompile({ requestPath, todoPlanKey = null, cwd, stdout }) {
399
+ async function planCompile({ requestPath, cwd, stdout }) {
443
400
  const request = await loadRequest(requestPath);
444
401
  await resolveRepoBinding(cwd, request);
445
- let todoPlanBinding = null;
446
- if (todoPlanKey !== null) {
447
- const store = await readTodoStore({ repoRoot: cwd });
448
- const member = store.members.find(({ descriptor }) => descriptor.plan_key === todoPlanKey);
449
- if (member === undefined) {
450
- throw new CliContractError('TODO_PLAN_NOT_ACTIVE', '指定されたTODO planはactiveではない', {
451
- plan_key: todoPlanKey, next_action: 'select_an_active_todo_plan',
452
- });
453
- }
454
- if (!sameTextSet(member.plan.tasks.map(({ task_id: taskId }) => taskId),
455
- request.todos.map(({ todo_id: todoId }) => todoId))) {
456
- throw new CliContractError('TODO_PLAN_TASK_MISMATCH',
457
- 'run requestのTODO集合がactive TODO planと一致しない', {
458
- plan_key: todoPlanKey, next_action: 'compile_a_request_for_the_current_plan_revision',
459
- });
460
- }
461
- todoPlanBinding = todoPlanBindingFor(member);
462
- }
463
402
  const result = await compileFromRepo({
464
403
  request,
465
404
  cwd,
@@ -477,7 +416,6 @@ async function planCompile({ requestPath, todoPlanKey = null, cwd, stdout }) {
477
416
  manifests: result.manifests,
478
417
  schedule: result.schedule,
479
418
  graph_digest: result.graph_digest,
480
- todo_plan_binding: todoPlanBinding,
481
419
  };
482
420
  artifact.result_digest = digestArtifact(artifact);
483
421
  stdout.write(`${JSON.stringify(artifact)}\n`);
@@ -588,8 +526,7 @@ async function readRunStore(runDir) {
588
526
  path.join(runDir, 'plan-compile-result.json'), 'plan compile result',
589
527
  );
590
528
  const request = await readBoundedJson(path.join(runDir, 'request.json'), 'run request');
591
- const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
592
- 'todo_plan_binding', 'result_digest'];
529
+ const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest', 'result_digest'];
593
530
  const metaKeys = ['schema', 'run_id', 'executor_adapter', 'plan_digest'];
594
531
  const { result_digest: claimedCompileDigest, ...compileBody } = compileArtifact ?? {};
595
532
  const legacyMetaValid = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
@@ -1153,76 +1090,7 @@ function isDistributedScriptedControllerActivation(activation) {
1153
1090
  ));
1154
1091
  }
1155
1092
 
1156
- async function verifyRunStartGate({ request, compileArtifact, repoRoot }) {
1157
- if (!compileArtifactBodyIsValid(compileArtifact, request)) {
1158
- throw new CliContractError('INVALID_PLAN_ARTIFACT',
1159
- 'compile artifactのdigest・request binding・plan/manifest bindingが不正', {
1160
- next_action: 'rerun_lattice_plan_compile_then_pass_its_artifact',
1161
- });
1162
- }
1163
- if (compileArtifact.todo_plan_binding === null) {
1164
- throw new CliContractError('COMPILE_ARTIFACT_UNBOUND',
1165
- 'compile artifactがTODO plan revisionへ束縛されていない', {
1166
- next_action: 'rerun_lattice_plan_compile_with_todo_plan',
1167
- });
1168
- }
1169
- const store = await readTodoStore({ repoRoot });
1170
- const binding = compileArtifact.todo_plan_binding;
1171
- const member = store.members.find(({ descriptor }) => descriptor.plan_key === binding.plan_key);
1172
- if (member === undefined || !sameTodoPlanBinding(binding, todoPlanBindingFor(member))) {
1173
- throw new CliContractError('STALE_TODO_PLAN_BINDING',
1174
- 'compile artifactのTODO plan version/digestが現在のactive revisionと一致しない', {
1175
- plan_key: binding.plan_key,
1176
- next_action: 'recompile_for_the_current_todo_plan_revision',
1177
- });
1178
- }
1179
- const taskIds = compileArtifact.plan.nodes.map(({ todo_id: todoId }) => todoId);
1180
- if (!sameTextSet(member.plan.tasks.map(({ task_id: taskId }) => taskId), taskIds)) {
1181
- throw new CliContractError('TODO_PLAN_TASK_MISMATCH',
1182
- 'compile artifactのtask集合が現在のTODO planと一致しない', {
1183
- plan_key: binding.plan_key,
1184
- next_action: 'recompile_for_the_current_todo_plan_revision',
1185
- });
1186
- }
1187
- const independenceArtifact = await readTodoIndependenceArtifact({
1188
- repoRoot, store, planKey: binding.plan_key,
1189
- });
1190
- const currentHead = await runGit(['rev-parse', 'HEAD'], repoRoot);
1191
- if (currentHead.code !== 0) throw new CliContractError('REPO_UNRESOLVED', 'cwdのgit HEADを解決できない');
1192
- const currentBaseSha = currentHead.stdout.trim();
1193
- const projected = projectIndependenceFrontier({
1194
- artifact: independenceArtifact,
1195
- readyTaskIds: taskIds,
1196
- activeTaskIds: [],
1197
- plan: member.plan,
1198
- currentBaseSha,
1199
- changedPaths: null,
1200
- });
1201
- if (projected.coverage !== 'verified') {
1202
- throw new CliContractError('PARALLEL_GROUP_UNVERIFIED',
1203
- 'TODO independence recordがverifiedではないため並列writerを起動できない', {
1204
- coverage: projected.coverage,
1205
- next_action: 'lattice todo independence compile --plan ' + binding.plan_key + ' --input <witness.json>',
1206
- });
1207
- }
1208
- for (const wave of compileArtifact.schedule.waves) {
1209
- if (wave.todo_ids.length < 2) continue;
1210
- const verified = projected.frontier.parallel_groups.some((group) => (
1211
- sameTextSet(group.task_ids, wave.todo_ids)
1212
- ));
1213
- if (!verified) {
1214
- throw new CliContractError('PARALLEL_GROUP_UNVERIFIED',
1215
- 'compile artifactが同時dispatchするtask群はverified parallel groupではない', {
1216
- task_ids: wave.todo_ids,
1217
- coverage: projected.coverage,
1218
- unknown: projected.frontier.unknown,
1219
- next_action: 'compile_independence_for_the_current_plan_then_recompile_the_run_plan',
1220
- });
1221
- }
1222
- }
1223
- }
1224
-
1225
- async function runStart({ requestPath, planPath = null, executorAdapter, cwd, stdout }) {
1093
+ async function runStart({ requestPath, executorAdapter, cwd, stdout }) {
1226
1094
  // --executor省略時の暗黙fallbackは持たない(Decision 8)。未知adapterはtyped reject。
1227
1095
  if (!KNOWN_ADAPTERS.includes(executorAdapter)) {
1228
1096
  throw new CliContractError('UNKNOWN_ADAPTER', `未知のexecutor adapter: ${executorAdapter}`);
@@ -1236,32 +1104,30 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
1236
1104
  await requireSafeRunAncestors(repoRoot);
1237
1105
  await requireIgnoredRunStore(repoRoot);
1238
1106
  await resolveRepoBinding(repoRoot, request);
1239
- let compileArtifact;
1240
- if (planPath === null) {
1241
- // 旧public入口はartifactを消費しないため、このgateの対象ではない。互換入口を
1242
- // 改変せず残し、TODO storeを持つ統合経路は下の--plan必須入口を使う。
1243
- const result = await compileFromRepo({
1244
- request, cwd: repoRoot, planRef: `plan-${request.request_id}-e1`, planEpoch: 1,
1245
- predecessorRefs: [],
1246
- });
1247
- if (result.outcome !== 'dispatchable') {
1248
- throw new CliContractError(result.code, 'dispatchable planを発行できない', result.detail);
1249
- }
1250
- compileArtifact = {
1251
- schema: COMPILE_RESULT_SCHEMA, request_digest: request.request_digest, plan: result.plan,
1252
- manifests: result.manifests, schedule: result.schedule, graph_digest: result.graph_digest,
1253
- todo_plan_binding: null,
1254
- };
1255
- compileArtifact.result_digest = digestArtifact(compileArtifact);
1256
- } else {
1257
- compileArtifact = await readBoundedJson(planPath, 'plan artifact');
1258
- await verifyRunStartGate({ request, compileArtifact, repoRoot });
1107
+ const result = await compileFromRepo({
1108
+ request,
1109
+ cwd: repoRoot,
1110
+ planRef: `plan-${request.request_id}-e1`,
1111
+ planEpoch: 1,
1112
+ predecessorRefs: [],
1113
+ });
1114
+ if (result.outcome !== 'dispatchable') {
1115
+ throw new CliContractError(result.code, 'dispatchable planを発行できない', result.detail);
1259
1116
  }
1117
+ const compileArtifact = {
1118
+ schema: COMPILE_RESULT_SCHEMA,
1119
+ request_digest: request.request_digest,
1120
+ plan: result.plan,
1121
+ manifests: result.manifests,
1122
+ schedule: result.schedule,
1123
+ graph_digest: result.graph_digest,
1124
+ };
1125
+ compileArtifact.result_digest = digestArtifact(compileArtifact);
1260
1126
  const events = initializeRunEvents({
1261
1127
  runId: request.request_id,
1262
1128
  request,
1263
- plan: compileArtifact.plan,
1264
- manifests: compileArtifact.manifests,
1129
+ plan: result.plan,
1130
+ manifests: result.manifests,
1265
1131
  recordedAt: new Date().toISOString().replace(/\.\d+Z$/u, '.000Z'),
1266
1132
  });
1267
1133
  const runDir = runStorePath(repoRoot, request.request_id);
@@ -1279,7 +1145,7 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
1279
1145
  schema: 'lattice.run_meta.v1',
1280
1146
  run_id: request.request_id,
1281
1147
  executor_adapter: executorAdapter,
1282
- plan_digest: compileArtifact.plan.plan_digest,
1148
+ plan_digest: result.plan.plan_digest,
1283
1149
  };
1284
1150
  try {
1285
1151
  await writeJsonFile(path.join(temporaryDir, 'request.json'), request);
@@ -1299,7 +1165,7 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
1299
1165
  run_id: request.request_id,
1300
1166
  run_dir: path.relative(repoRoot, runDir),
1301
1167
  executor_adapter: executorAdapter,
1302
- plan_digest: compileArtifact.plan.plan_digest,
1168
+ plan_digest: result.plan.plan_digest,
1303
1169
  events_digest: digestArtifact(events.map(({ event_digest: digest }) => digest)),
1304
1170
  };
1305
1171
  output.result_digest = digestArtifact(output);
@@ -4035,13 +3901,6 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4035
3901
  && argv[0] === 'plan' && argv[1] === 'compile' && argv[2] === '--request'
4036
3902
  && typeof argv[3] === 'string' && argv[3].length > 0) {
4037
3903
  action = () => planCompile({ requestPath: path.resolve(cwd, argv[3]), cwd, stdout });
4038
- } else if (argv.length === 6
4039
- && argv[0] === 'plan' && argv[1] === 'compile' && argv[2] === '--request'
4040
- && typeof argv[3] === 'string' && argv[3].length > 0
4041
- && argv[4] === '--todo-plan' && typeof argv[5] === 'string' && argv[5].length > 0) {
4042
- action = () => planCompile({
4043
- requestPath: path.resolve(cwd, argv[3]), todoPlanKey: argv[5], cwd, stdout,
4044
- });
4045
3904
  } else if (argv.length === 6
4046
3905
  && argv[0] === 'plan' && argv[1] === 'verify'
4047
3906
  && argv[2] === '--request' && typeof argv[3] === 'string' && argv[3].length > 0
@@ -4062,15 +3921,6 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
4062
3921
  cwd,
4063
3922
  stdout,
4064
3923
  });
4065
- } else if (argv.length === 8
4066
- && argv[0] === 'run' && argv[1] === 'start'
4067
- && argv[2] === '--request' && typeof argv[3] === 'string' && argv[3].length > 0
4068
- && argv[4] === '--plan' && typeof argv[5] === 'string' && argv[5].length > 0
4069
- && argv[6] === '--executor' && typeof argv[7] === 'string' && argv[7].length > 0) {
4070
- action = () => runStart({
4071
- requestPath: path.resolve(cwd, argv[3]), planPath: path.resolve(cwd, argv[5]),
4072
- executorAdapter: argv[7], cwd, stdout,
4073
- });
4074
3924
  } else if (argv.length === 4
4075
3925
  && argv[0] === 'run' && argv[1] === 'activate'
4076
3926
  && argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
@@ -215,8 +215,7 @@ async function replaceDurableJson(directory, name, value) {
215
215
  }
216
216
 
217
217
  function validateLegacyInputs({ request, compileArtifact, legacyMeta }) {
218
- const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
219
- 'todo_plan_binding', 'result_digest'];
218
+ const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest', 'result_digest'];
220
219
  const metaKeys = ['schema', 'run_id', 'executor_adapter', 'plan_digest'];
221
220
  const compileBody = { ...compileArtifact };
222
221
  delete compileBody.result_digest;