@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 +1 -1
- package/src/cli-help.mjs +1 -5
- package/src/runtime-cli.mjs +25 -175
- package/src/runtime-multi-epoch-store.mjs +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.41.
|
|
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>
|
|
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 を出す
|
package/src/runtime-cli.mjs
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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:
|
|
1264
|
-
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:
|
|
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:
|
|
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;
|