@quolu/lattice 0.12.21 → 0.12.22

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.12.21",
3
+ "version": "0.12.22",
4
4
  "description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli-help.mjs CHANGED
@@ -45,6 +45,7 @@ Commands:
45
45
 
46
46
  Read commands:
47
47
  status [--json]
48
+ bindings [--plan <key>] [--json] # compile_binding付きTaskをTODO identityつきで投影する
48
49
  verify [--plan <key>] [--json]
49
50
  snapshot --rebuild --plan <key>
50
51
  gantt [--out <file>] [--scope live|all] # 既定live: 完走した工程を図から除く(一覧には残る)
@@ -110,6 +111,7 @@ const SUBCOMMAND_USAGE = Object.freeze({
110
111
  'run list': 'run list --json',
111
112
  'event verify': 'event verify --run .lattice/runs/<id>',
112
113
  'todo status': 'todo status [--json]',
114
+ 'todo bindings': 'todo bindings [--plan <key>] [--json]',
113
115
  'todo verify': 'todo verify [--plan <key>] [--json]',
114
116
  'todo snapshot': 'todo snapshot --rebuild --plan <key>',
115
117
  'todo gantt': 'todo gantt [--out <file>] [--scope live|all] | status [--out <file>] | serve --port <port> [--scope live|all]',
package/src/todo-cli.mjs CHANGED
@@ -46,7 +46,7 @@ import {
46
46
  appendTodoExtraction,
47
47
  validateTodoExtraction,
48
48
  } from './todo-migration.mjs';
49
- import { projectTodoStatus } from './todo-status.mjs';
49
+ import { projectTodoBindings, projectTodoStatus } from './todo-status.mjs';
50
50
  import {
51
51
  parseTodoSourceRef, todoLegacyReconciliationDigest, validatePhaseTodoRevision,
52
52
  validateTodoRevision, validateTodoRevisionSet,
@@ -474,6 +474,10 @@ async function status({ repoRoot }) {
474
474
  return projectTodoStatus(await readTodoStore({ repoRoot }));
475
475
  }
476
476
 
477
+ async function bindings({ repoRoot, requestedPlanKey }) {
478
+ return projectTodoBindings(await readTodoStore({ repoRoot }), { requestedPlanKey });
479
+ }
480
+
477
481
  async function readNarrative(repoRoot, ref) {
478
482
  const canonicalRoot = await realpath(repoRoot);
479
483
  const source = parseTodoSourceRef(ref);
@@ -926,6 +930,13 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
926
930
  if ((argv.length === 1 && argv[0] === 'status')
927
931
  || (argv.length === 2 && argv[0] === 'status' && argv[1] === '--json')) {
928
932
  action = (repoRoot) => status({ repoRoot });
933
+ } else if ((argv.length === 1 && argv[0] === 'bindings')
934
+ || (argv.length === 2 && argv[0] === 'bindings' && argv[1] === '--json')) {
935
+ action = (repoRoot) => bindings({ repoRoot, requestedPlanKey: null });
936
+ } else if ((argv.length === 3 || argv.length === 4) && argv[0] === 'bindings'
937
+ && argv[1] === '--plan' && isTodoIdentifier(argv[2])
938
+ && (argv.length === 3 || argv[3] === '--json')) {
939
+ action = (repoRoot) => bindings({ repoRoot, requestedPlanKey: argv[2] });
929
940
  } else if ((argv.length === 1 && argv[0] === 'verify')
930
941
  || (argv.length === 2 && argv[0] === 'verify' && argv[1] === '--json')) {
931
942
  action = (repoRoot) => verify({ repoRoot, requestedPlanKey: null });
@@ -338,3 +338,63 @@ export function projectTodoStatus(readModel) {
338
338
  }
339
339
  return result;
340
340
  }
341
+
342
+ export const TODO_BINDING_PROJECTION_SCHEMA = 'lattice.todo_binding_projection.v1';
343
+
344
+ /**
345
+ * `compile_binding`が設定されたTaskだけを、TODO正本のidentityつきで投影する(ADR 0124)。
346
+ *
347
+ * これはTODO工程storeとruntime実行を結ぶ唯一の公開読み取り面である。host は
348
+ * `compiled_plan_digest`で`runtime_plan.v1`を、`base_sha`でrun requestのbaseを照合し、
349
+ * plan→`executor_packet.v1`→`executor_receipt.v1`(`packet_digest`帰属)まで辿れる。
350
+ *
351
+ * `todo_status_result.v4`は変更しない。binding投影は加算の別面とし、v4を受理する
352
+ * 既存hostを壊さない。
353
+ */
354
+ export function projectTodoBindings(readModel, { requestedPlanKey = null } = {}) {
355
+ if (!plain(readModel) || readModel.schema !== 'lattice.todo_store_read.v1'
356
+ || !isTodoIdentifier(readModel.project_id) || !Array.isArray(readModel.members)) {
357
+ fail('TODO_STATUS_INVALID_INPUT', 'todo_status_read_model_invalid');
358
+ }
359
+ if (requestedPlanKey !== null && !isTodoIdentifier(requestedPlanKey)) {
360
+ fail('TODO_STATUS_INVALID_INPUT', 'todo_binding_plan_key_invalid');
361
+ }
362
+ const members = [...readModel.members].sort((left, right) => {
363
+ const leftKey = left?.plan?.plan_key ?? '';
364
+ const rightKey = right?.plan?.plan_key ?? '';
365
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
366
+ });
367
+ const bindings = [];
368
+ let matchedPlan = requestedPlanKey === null;
369
+ for (const member of members) {
370
+ const plan = member?.plan;
371
+ if (!plain(plan) || !Array.isArray(plan.tasks)) continue;
372
+ if (requestedPlanKey !== null && plan.plan_key !== requestedPlanKey) continue;
373
+ if (requestedPlanKey !== null) matchedPlan = true;
374
+ for (const task of plan.tasks) {
375
+ if (!plain(task) || task.compile_binding === null || task.compile_binding === undefined) continue;
376
+ bindings.push({
377
+ project_id: plan.project_id,
378
+ plan_key: plan.plan_key,
379
+ plan_version: plan.plan_version,
380
+ task_id: task.task_id,
381
+ compile_binding: task.compile_binding,
382
+ });
383
+ if (bindings.length > TODO_STATUS_LIST_LIMIT) {
384
+ fail('TODO_SCALE_EXCEEDED', 'todo_binding_projection_limit_exceeded', {
385
+ binding_limit: TODO_STATUS_LIST_LIMIT,
386
+ });
387
+ }
388
+ }
389
+ }
390
+ if (!matchedPlan) fail('TODO_STATUS_INVALID_INPUT', 'todo_binding_plan_not_found');
391
+ const result = {
392
+ schema: TODO_BINDING_PROJECTION_SCHEMA,
393
+ project_id: readModel.project_id,
394
+ plan_key: requestedPlanKey,
395
+ bindings,
396
+ result_digest: '',
397
+ };
398
+ result.result_digest = todoSelfDigest(result, 'result_digest');
399
+ return result;
400
+ }