@quolu/lattice 0.59.1 → 0.60.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/bin/lattice.mjs CHANGED
@@ -2,22 +2,21 @@
2
2
 
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { installPipeCloseGuard } from '../src/cli-stdio.mjs';
5
- import { runRuntimeCli } from '../src/runtime-cli.mjs';
6
5
  import { renderCliHelp } from '../src/cli-help.mjs';
7
- import { relaunchSensorForNode22IfNeeded } from '../src/sensor-node-runtime.mjs';
6
+ import { relaunchForNode22IfNeeded } from '../src/sensor-node-runtime.mjs';
8
7
  import packageJson from '../package.json' with { type: 'json' };
9
8
 
10
9
  installPipeCloseGuard();
11
10
 
12
11
  const args = process.argv.slice(2);
13
- const sensorRelaunchStatus = relaunchSensorForNode22IfNeeded({
12
+ const node22RelaunchStatus = relaunchForNode22IfNeeded({
14
13
  args,
15
14
  scriptPath: fileURLToPath(import.meta.url),
16
15
  });
17
16
  const help = renderCliHelp(args);
18
17
 
19
- if (sensorRelaunchStatus !== null) {
20
- process.exitCode = sensorRelaunchStatus;
18
+ if (node22RelaunchStatus !== null) {
19
+ process.exitCode = node22RelaunchStatus;
21
20
  } else if (help !== null) {
22
21
  process.stdout.write(help);
23
22
  } else if (args.length === 1 && args[0] === '--version') {
@@ -125,6 +124,7 @@ if (sensorRelaunchStatus !== null) {
125
124
  });
126
125
  } else {
127
126
  try {
127
+ const { runRuntimeCli } = await import('../src/runtime-cli.mjs');
128
128
  process.exitCode = await runRuntimeCli({
129
129
  argv: args,
130
130
  cwd: process.cwd(),
@@ -22,7 +22,11 @@
22
22
  "type": "array", "maxItems": 2048,
23
23
  "items": {
24
24
  "type": "object", "additionalProperties": false, "required": ["from", "to"],
25
- "properties": { "from": { "$ref": "#/$defs/nodeRef" }, "to": { "$ref": "#/$defs/nodeRef" } }
25
+ "properties": {
26
+ "from": { "$ref": "#/$defs/nodeRef" },
27
+ "to": { "$ref": "#/$defs/nodeRef" },
28
+ "reason": { "$ref": "#/$defs/text" }
29
+ }
26
30
  }
27
31
  },
28
32
  "joins": {
@@ -0,0 +1,161 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/kitepon-rgb/Lattice/blob/main/docs/schemas/lattice.todo_extraction.v4.schema.json",
4
+ "title": "lattice.todo_extraction.v4",
5
+ "description": "AI-authored ToDo migration artifact. Every task carries a non-blank Markdown design_memo. After the complete plan is written, run `lattice plan scope-review` and confirm that every registered task preserves an owner-supplied work specification without adding a product purpose. If no plan exists, write NO_PLAN explicitly. Runtime validation accepts at most one explicit, reasoned cross-plan dependency; an empty tasks array is reserved for that one dependency between existing tasks, with the top-level plan identity naming the source plan and both endpoint topology digests.",
6
+ "$comment": "あなたがこのToDoに対して、何も考えていないならば、設計メモに `NO_PLAN` と書いてください。Runtime validation also enforces UTF-8 byte bounds, exact keys, strict sort order, referential integrity, disposition consistency, the cross-plan dependency limit, and the canonical self-digest.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "schema", "project_id", "plan_key", "plan_version", "actor", "recorded_at",
11
+ "tasks", "hard_dependencies", "joins", "extraction_digest"
12
+ ],
13
+ "properties": {
14
+ "schema": { "const": "lattice.todo_extraction.v4" },
15
+ "project_id": { "$ref": "#/$defs/identifier" },
16
+ "plan_key": { "$ref": "#/$defs/identifier" },
17
+ "plan_version": { "$ref": "#/$defs/identifier" },
18
+ "actor": { "$ref": "#/$defs/actor" },
19
+ "recorded_at": { "$ref": "#/$defs/strictTimestamp" },
20
+ "tasks": { "type": "array", "minItems": 0, "maxItems": 512, "items": { "$ref": "#/$defs/task" } },
21
+ "hard_dependencies": {
22
+ "type": "array", "maxItems": 2048,
23
+ "items": {
24
+ "type": "object", "additionalProperties": false, "required": ["from", "to"],
25
+ "properties": {
26
+ "from": { "$ref": "#/$defs/nodeRef" },
27
+ "to": { "$ref": "#/$defs/nodeRef" },
28
+ "reason": { "$ref": "#/$defs/text" }
29
+ }
30
+ }
31
+ },
32
+ "joins": {
33
+ "type": "array", "maxItems": 128,
34
+ "items": {
35
+ "type": "object", "additionalProperties": false, "required": ["id", "after", "before"],
36
+ "properties": {
37
+ "id": { "$ref": "#/$defs/identifier" },
38
+ "after": { "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/nodeRef" } },
39
+ "before": { "$ref": "#/$defs/nodeRef" }
40
+ }
41
+ }
42
+ },
43
+ "extraction_digest": { "$ref": "#/$defs/digest" }
44
+ },
45
+ "allOf": [
46
+ {
47
+ "if": { "properties": { "tasks": { "maxItems": 0 } }, "required": ["tasks"] },
48
+ "then": {
49
+ "properties": {
50
+ "hard_dependencies": { "minItems": 1, "maxItems": 1 },
51
+ "joins": { "maxItems": 0 }
52
+ }
53
+ }
54
+ }
55
+ ],
56
+ "$defs": {
57
+ "identifier": { "type": "string", "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$" },
58
+ "digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
59
+ "repoRef": {
60
+ "type": "string", "minLength": 1, "maxLength": 1024,
61
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])[^/]+(?:/[^/]+)*$"
62
+ },
63
+ "text": { "type": "string", "minLength": 1, "maxLength": 16384 },
64
+ "designMemo": {
65
+ "type": "string", "minLength": 1, "maxLength": 16384, "pattern": "\\S",
66
+ "description": "Markdown design memo. Use the literal NO_PLAN only when no plan exists."
67
+ },
68
+ "nullableText": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/text" }] },
69
+ "strictTimestamp": {
70
+ "type": "string",
71
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$"
72
+ },
73
+ "actor": {
74
+ "type": "object", "additionalProperties": false, "required": ["host", "session", "agent"],
75
+ "properties": {
76
+ "host": { "$ref": "#/$defs/identifier" },
77
+ "session": { "$ref": "#/$defs/identifier" },
78
+ "agent": { "$ref": "#/$defs/identifier" }
79
+ }
80
+ },
81
+ "nodeRef": {
82
+ "type": "object", "additionalProperties": false, "required": ["project_id", "plan_key", "task_id"],
83
+ "properties": {
84
+ "project_id": { "$ref": "#/$defs/identifier" },
85
+ "plan_key": { "$ref": "#/$defs/identifier" },
86
+ "task_id": { "$ref": "#/$defs/identifier" },
87
+ "expected_topology_digest": { "$ref": "#/$defs/digest" }
88
+ }
89
+ },
90
+ "sourceLocation": {
91
+ "type": "object", "additionalProperties": false,
92
+ "required": ["origin_plan_ref", "origin_line", "source_commit", "heading_path", "markdown_depth", "parent_task_id", "checkbox_state"],
93
+ "properties": {
94
+ "origin_plan_ref": { "$ref": "#/$defs/repoRef" },
95
+ "origin_line": { "type": "integer", "minimum": 1 },
96
+ "source_commit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$" },
97
+ "heading_path": { "type": "array", "maxItems": 32, "items": { "type": "string", "minLength": 1, "maxLength": 1024 } },
98
+ "markdown_depth": { "type": "integer", "minimum": 0, "maximum": 64 },
99
+ "parent_task_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] },
100
+ "checkbox_state": { "enum": ["checked", "unchecked", "absent", "ambiguous"] }
101
+ }
102
+ },
103
+ "migrationContext": {
104
+ "type": "object", "additionalProperties": false,
105
+ "required": ["external_canonical_ref", "carry_over_ref", "h_required", "condition", "evidence_refs", "notes"],
106
+ "properties": {
107
+ "external_canonical_ref": { "$ref": "#/$defs/nullableText" },
108
+ "carry_over_ref": { "$ref": "#/$defs/nullableText" },
109
+ "h_required": { "type": "boolean" },
110
+ "condition": { "$ref": "#/$defs/nullableText" },
111
+ "evidence_refs": { "type": "array", "maxItems": 64, "items": { "type": "string", "minLength": 1, "maxLength": 4096 } },
112
+ "notes": { "type": "array", "maxItems": 64, "items": { "type": "string", "minLength": 1, "maxLength": 4096 } }
113
+ }
114
+ },
115
+ "historicalStart": {
116
+ "type": "object", "additionalProperties": false, "required": ["start_mode", "status", "started_at"],
117
+ "properties": {
118
+ "start_mode": { "const": "historical_import" }, "status": { "const": "in-progress" },
119
+ "started_at": { "oneOf": [{ "const": "unknown_requires_evidence" }, { "$ref": "#/$defs/strictTimestamp" }] }
120
+ }
121
+ },
122
+ "historicalCompletion": {
123
+ "type": "object", "additionalProperties": false, "required": ["done_mode", "completed_at"],
124
+ "properties": {
125
+ "done_mode": { "const": "historical_import" },
126
+ "completed_at": { "oneOf": [{ "const": "unknown_requires_evidence" }, { "$ref": "#/$defs/strictTimestamp" }] }
127
+ }
128
+ },
129
+ "task": {
130
+ "type": "object", "additionalProperties": false,
131
+ "required": [
132
+ "task_id", "title", "lane", "design_memo", "narrative_ref", "compile_binding",
133
+ "disposition", "start", "completion", "source", "migration_context"
134
+ ],
135
+ "properties": {
136
+ "task_id": { "$ref": "#/$defs/identifier" },
137
+ "title": { "$ref": "#/$defs/text" },
138
+ "lane": { "$ref": "#/$defs/identifier" },
139
+ "design_memo": { "$ref": "#/$defs/designMemo" },
140
+ "narrative_ref": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/repoRef" }] },
141
+ "compile_binding": { "type": "null" },
142
+ "disposition": { "enum": ["register_pending", "register_in_progress", "register_done", "exclude_superseded", "exclude_compatibility_record", "unknown_requires_evidence"] },
143
+ "start": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/historicalStart" }] },
144
+ "completion": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/historicalCompletion" }] },
145
+ "source": { "$ref": "#/$defs/sourceLocation" },
146
+ "migration_context": { "$ref": "#/$defs/migrationContext" }
147
+ },
148
+ "allOf": [
149
+ {
150
+ "if": { "properties": { "disposition": { "const": "register_in_progress" } }, "required": ["disposition"] },
151
+ "then": { "properties": { "start": { "$ref": "#/$defs/historicalStart" }, "completion": { "type": "null" } } },
152
+ "else": {
153
+ "if": { "properties": { "disposition": { "const": "register_done" } }, "required": ["disposition"] },
154
+ "then": { "properties": { "start": { "type": "null" }, "completion": { "$ref": "#/$defs/historicalCompletion" } } },
155
+ "else": { "properties": { "start": { "type": "null" }, "completion": { "type": "null" } } }
156
+ }
157
+ }
158
+ ]
159
+ }
160
+ }
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.59.1",
3
+ "version": "0.60.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",
@@ -35,6 +35,7 @@
35
35
  "docs/schemas/lattice.phase_todo_revision.v3.schema.json",
36
36
  "docs/schemas/lattice.todo_extraction.v2.schema.json",
37
37
  "docs/schemas/lattice.todo_extraction.v3.schema.json",
38
+ "docs/schemas/lattice.todo_extraction.v4.schema.json",
38
39
  "docs/schemas/lattice.todo_structure_set.v1.schema.json",
39
40
  "docs/schemas/lattice.todo_structure_realization.v1.schema.json",
40
41
  "docs/schemas/lattice.todo_structure_binding.v1.schema.json",
package/src/cli-help.mjs CHANGED
@@ -91,7 +91,7 @@ Write commands:
91
91
  dashboard remove <project_id> --json # 登録簿から1件外す(対象repoの外からも叩ける)
92
92
  note --plan <key> [--task <id>] (--message <text>|--input <file>)
93
93
  # ToDoへ作業継続に必要な方針・調査結果・注意をappend-onlyで追記する
94
- migrate --input <extraction.json> [--serialization-reviewed]
94
+ migrate --input <extraction.json> [--serialization-reviewed] [--json]
95
95
  migrate --input <extraction.json> --dry-run --json [--serialization-reviewed]
96
96
  # 既存storeへplanを追加する(plan createは空store初期化専用)。
97
97
  # 依存グラフがほぼ一直線なら一度突き返し、再考後の --serialization-reviewed で通す
@@ -273,7 +273,7 @@ const SUBCOMMAND_USAGE = Object.freeze({
273
273
  'todo revise': 'todo revise --plan <key> --input <file> | --schema --json',
274
274
  'todo revise-phase': 'todo revise-phase --plan <key> --input <file> | --schema --json',
275
275
  'todo revise-set': 'todo revise-set --input <file> | --schema --json',
276
- 'todo migrate': 'todo migrate --input <extraction.json> [--serialization-reviewed] | --input <extraction.json> --dry-run --json [--serialization-reviewed] | --schema --json',
276
+ 'todo migrate': 'todo migrate --input <extraction.json> [--serialization-reviewed] [--json] | --input <extraction.json> --dry-run --json [--serialization-reviewed] | --schema --json',
277
277
  'sensor init': 'sensor init [path] --json',
278
278
  'sensor sync': 'sensor sync [path] --json',
279
279
  'sensor diff': 'sensor diff <rootA> <rootB> [--subtree-a <rel>] [--subtree-b <rel>]'
@@ -15,7 +15,10 @@ import { TodoStoreError } from './todo-store.mjs';
15
15
  const REVIEW_SCHEMA = 'lattice.plan_scope_review.v1';
16
16
  const RESULT_SCHEMA = 'lattice.plan_scope_review_result.v1';
17
17
  const PLAN_CREATE_SCHEMA = 'lattice.plan_create_input.v4';
18
- const TODO_EXTRACTION_SCHEMA = 'lattice.todo_extraction.v3';
18
+ const TODO_EXTRACTION_SCHEMAS = new Set([
19
+ 'lattice.todo_extraction.v3',
20
+ 'lattice.todo_extraction.v4',
21
+ ]);
19
22
  const MAX_INPUT_BYTES = 8_388_608;
20
23
  const JUDGMENTS = new Set(['required', 'out_of_scope']);
21
24
 
@@ -73,10 +76,10 @@ async function readJsonWithinRepo(repoRoot, inputRef) {
73
76
 
74
77
  function authoredPlan(value) {
75
78
  const digestField = value?.schema === PLAN_CREATE_SCHEMA ? 'input_digest'
76
- : value?.schema === TODO_EXTRACTION_SCHEMA ? 'extraction_digest' : null;
79
+ : TODO_EXTRACTION_SCHEMAS.has(value?.schema) ? 'extraction_digest' : null;
77
80
  if (digestField === null) {
78
81
  throw new TodoStoreError('PLAN_SCOPE_REVIEW_INVALID', 'authoring_schema_unsupported', undefined, {
79
- pointer: '/schema', expected: [PLAN_CREATE_SCHEMA, TODO_EXTRACTION_SCHEMA],
82
+ pointer: '/schema', expected: [PLAN_CREATE_SCHEMA, ...TODO_EXTRACTION_SCHEMAS],
80
83
  actual: typeof value?.schema === 'string' ? value.schema : null,
81
84
  });
82
85
  }
@@ -86,7 +89,7 @@ function authoredPlan(value) {
86
89
  pointer: `/${digestField}`,
87
90
  });
88
91
  }
89
- const selected = value.schema === TODO_EXTRACTION_SCHEMA
92
+ const selected = TODO_EXTRACTION_SCHEMAS.has(value.schema)
90
93
  ? value.tasks?.filter((task) => typeof task?.disposition === 'string'
91
94
  && task.disposition.startsWith('register_'))
92
95
  : value.tasks;
@@ -19,7 +19,10 @@ import { projectTodoStatus } from './todo-status.mjs';
19
19
  import { readTodoPlanNotesForStatus } from './todo-note-store.mjs';
20
20
  import { projectIndependenceFrontier } from './todo-independence.mjs';
21
21
  import { readTodoParallelCandidatesForStatus } from './todo-parallel-candidates.mjs';
22
- import { readTodoStructureFinalizationsForStatus } from './todo-structure-store.mjs';
22
+ import {
23
+ readTodoStructureArtifactDiagnostics,
24
+ readTodoStructureFinalizationsForStatus,
25
+ } from './todo-structure-store.mjs';
23
26
  import { isTodoIndependenceLegacyMarker } from './todo-independence-contracts.mjs';
24
27
  import { selectIndependenceGuidance } from './todo-independence-guidance.mjs';
25
28
  import { ensureTodoDashboardActivity } from './todo-dashboard-registry.mjs';
@@ -223,7 +226,7 @@ function invalidStatus({ cliVersion, repoRoot, reason, nextAction = null }) {
223
226
  * store読みはここでしか行わない——session開始経路が同じstoreを二度払っていたのが
224
227
  * ADR 0131で直した欠陥である。
225
228
  */
226
- async function resolveProjectState({ cwd, cliVersion }) {
229
+ async function resolveProjectState({ cwd, cliVersion, diagnoseStructureArtifacts = false }) {
227
230
  const repoRoot = resolveRepoRoot(cwd);
228
231
  if (repoRoot === null) {
229
232
  return { exitCode: 1, repoRoot: null, store: null, todo: null,
@@ -271,6 +274,14 @@ async function resolveProjectState({ cwd, cliVersion }) {
271
274
  }
272
275
  try {
273
276
  const store = await readTodoStore({ repoRoot });
277
+ if (diagnoseStructureArtifacts) {
278
+ const diagnostics = await readTodoStructureArtifactDiagnostics({ repoRoot, store });
279
+ if (diagnostics.length > 0) {
280
+ throw new TodoStoreError(
281
+ 'STRUCTURE_ARTIFACT_INVALID', diagnostics[0].reason, undefined, { diagnostics },
282
+ );
283
+ }
284
+ }
274
285
  const todo = projectTodoStatus(store, {
275
286
  planNotes: await readTodoPlanNotesForStatus({ repoRoot, store }),
276
287
  parallelCandidates: await readTodoParallelCandidatesForStatus({ repoRoot, store, gitHead }),
@@ -314,6 +325,7 @@ async function resolveProjectState({ cwd, cliVersion }) {
314
325
  });
315
326
  return { exitCode: 0, repoRoot, store, todo, result };
316
327
  } catch (error) {
328
+ if (error?.code === 'STRUCTURE_ARTIFACT_INVALID') throw error;
317
329
  const reason = error instanceof TodoStoreError
318
330
  ? `${error.code}:${error.detail?.reason ?? error.message}` : 'store_validation_failed';
319
331
  return invalid(reason);
@@ -322,7 +334,9 @@ async function resolveProjectState({ cwd, cliVersion }) {
322
334
 
323
335
  export async function runProjectStatus({ cwd, stdout, cliVersion, env = process.env,
324
336
  ensureDashboardActivity = ensureTodoDashboardActivity }) {
325
- const state = await resolveProjectState({ cwd, cliVersion });
337
+ const state = await resolveProjectState({
338
+ cwd, cliVersion, diagnoseStructureArtifacts: true,
339
+ });
326
340
  // dashboard活動の登録はdiscovery面の副作用として維持する(ADR 0131 Decision 4で
327
341
  // session-context側だけが持たない、と決めた面である)。
328
342
  if (state.store !== null && env.LATTICE_DASHBOARD_AUTOSTART !== '0') {
@@ -680,6 +694,15 @@ export async function runPlanShow({ cwd, planKey, stdout }) {
680
694
  }
681
695
 
682
696
  export function projectStatusFailure({ cwd, stdout, cliVersion, error }) {
697
+ if (error?.code === 'STRUCTURE_ARTIFACT_INVALID') {
698
+ const payload = {
699
+ schema: 'lattice.cli_error.v2', code: error.code,
700
+ message: error.message ?? 'structure artifact invalid',
701
+ detail: error.detail,
702
+ };
703
+ stdout.write(`${JSON.stringify(payload)}\n`);
704
+ return 1;
705
+ }
683
706
  const projectRootConflict = error?.code === 'PROJECT_ROOT_CONFLICT';
684
707
  const result = invalidStatus({
685
708
  cliVersion, repoRoot: resolveRepoRoot(cwd),
@@ -18,6 +18,7 @@ import {
18
18
  BOUNDARY_MANIFEST_SCHEMA, selfDigest, validateRuntimeBoundaryManifest, validateRuntimePlan,
19
19
  } from './runtime-contracts.mjs';
20
20
  import { TODO_INDEPENDENCE_SCHEMA } from './todo-independence-contracts.mjs';
21
+ import { pullIntakeReadinessGuidance } from './todo-independence-guidance.mjs';
21
22
  import {
22
23
  readTodoIndependenceArtifact, readTodoStore, readTodoWitnessSet,
23
24
  } from './todo-store.mjs';
@@ -315,6 +316,29 @@ function activeMember(store, planKey) {
315
316
  return member;
316
317
  }
317
318
 
319
+ async function pullPlanReadiness({ repoRoot, planKey }) {
320
+ const store = await readTodoStore({ repoRoot });
321
+ const member = activeMember(store, planKey);
322
+ const [artifact, witnessSet] = await Promise.all([
323
+ readTodoIndependenceArtifact({ repoRoot, store, planKey }),
324
+ readTodoWitnessSet({ repoRoot, planKey }),
325
+ ]);
326
+ const ready = artifact !== null
327
+ && artifact.schema === TODO_INDEPENDENCE_SCHEMA
328
+ && artifact.project_id === store.project_id
329
+ && artifact.plan_key === planKey
330
+ && artifact.plan_version === member.plan.plan_version
331
+ && artifact.topology_digest === member.plan.topology_digest
332
+ && artifact.outcome === 'compiled'
333
+ && witnessSet?.project_id === store.project_id
334
+ && witnessSet?.plan_key === planKey
335
+ && witnessSet?.witness_set_digest === artifact.witness_set_digest;
336
+ return pullIntakeReadinessGuidance({
337
+ coordinationMode: member.coordination?.mode ?? null,
338
+ ready,
339
+ });
340
+ }
341
+
318
342
  function literalEvent(member, { kind, taskId, actor }) {
319
343
  return member.journal?.events?.findLast((event) => (
320
344
  event.kind === kind && event.task_id === taskId
@@ -354,6 +378,10 @@ function pathTouches(boundary, changed) {
354
378
  }
355
379
 
356
380
  async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBase }) {
381
+ const pullRequired = pullIntakeReadinessGuidance({
382
+ coordinationMode: member.coordination?.mode ?? null,
383
+ ready: false,
384
+ });
357
385
  let artifact;
358
386
  let witnessSet;
359
387
  try {
@@ -362,7 +390,7 @@ async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBa
362
390
  readTodoWitnessSet({ repoRoot, planKey: meta.plan_key }),
363
391
  ]);
364
392
  } catch (error) {
365
- return { state: 'hold', reason: 'boundary_unverified', next_action: 'recompile_independence',
393
+ return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
366
394
  detail: { cause: error?.code ?? 'artifact_read_failed' }, artifact: null, witnessSet: null };
367
395
  }
368
396
  const bindingValid = artifact !== null
@@ -375,11 +403,11 @@ async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBa
375
403
  && witnessSet?.plan_key === meta.plan_key
376
404
  && witnessSet?.witness_set_digest === artifact.witness_set_digest;
377
405
  if (!bindingValid) {
378
- return { state: 'hold', reason: 'boundary_unverified', next_action: 'recompile_independence',
406
+ return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
379
407
  detail: { cause: 'artifact_binding_mismatch' }, artifact, witnessSet };
380
408
  }
381
409
  if (artifact.outcome !== 'compiled') {
382
- return { state: 'hold', reason: 'boundary_unverified', next_action: 'recompile_independence',
410
+ return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
383
411
  detail: { cause: 'independence_outcome_unknown' }, artifact, witnessSet };
384
412
  }
385
413
  const boundary = boundaryFor(artifact, taskId);
@@ -603,6 +631,7 @@ export async function startPullRun({ repoRoot, runDir, runId, planKey, equipment
603
631
  || !identifier(runId) || !identifier(planKey) || equipment !== 'detached-worktree') {
604
632
  fail('INVALID_PULL_START', 'pull start入力が不正');
605
633
  }
634
+ const intakeReadiness = await pullPlanReadiness({ repoRoot, planKey });
606
635
  const temporary = await mkdtemp(path.join(path.dirname(runDir), `.${runId}.pull-tmp-`));
607
636
  const meta = {
608
637
  schema: PULL_RUN_META_SCHEMA,
@@ -634,9 +663,10 @@ export async function startPullRun({ repoRoot, runDir, runId, planKey, equipment
634
663
  throw error;
635
664
  }
636
665
  return {
637
- schema: 'lattice.pull_run_start_result.v1',
666
+ schema: 'lattice.pull_run_start_result.v2',
638
667
  outcome: 'started', run_id: runId, plan_key: planKey,
639
668
  selection: 'pull', equipment, intake_count: 0,
669
+ intake_readiness: intakeReadiness,
640
670
  };
641
671
  }
642
672
 
@@ -1,14 +1,14 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
 
3
3
  export const SQLITE_EXPERIMENTAL_WARNING_FLAG = '--disable-warning=ExperimentalWarning';
4
- const RELAUNCH_GUARD_ENV = 'LATTICE_SENSOR_SQLITE_WARNING_RELAUNCHED';
4
+ const RELAUNCH_GUARD_ENV = 'LATTICE_SQLITE_WARNING_RELAUNCHED';
5
5
 
6
6
  export function sensorNodeRuntimeFlags(nodeVersion = process.versions.node) {
7
7
  const major = Number(nodeVersion.split('.')[0]);
8
8
  return major === 22 ? [SQLITE_EXPERIMENTAL_WARNING_FLAG] : [];
9
9
  }
10
10
 
11
- export function sensorRelaunchArgv(
11
+ export function node22RelaunchArgv(
12
12
  scriptPath,
13
13
  args,
14
14
  execArgv = process.execArgv,
@@ -20,20 +20,18 @@ export function sensorRelaunchArgv(
20
20
  }
21
21
 
22
22
  /**
23
- * Node 22 prints node:sqlite's ExperimentalWarning to stderr. Sensor commands
24
- * have a strict JSON-only stderr contract, so relaunch only that command
25
- * surface with Node's warning-category flag before node:sqlite is imported.
23
+ * Node 22 prints node:sqlite's ExperimentalWarning to stderr. The CLI has a
24
+ * JSON-only output contract, so relaunch before any command can import node:sqlite.
26
25
  */
27
- export function relaunchSensorForNode22IfNeeded({ args, scriptPath, env = process.env }) {
26
+ export function relaunchForNode22IfNeeded({ args, scriptPath, env = process.env }) {
28
27
  const flags = sensorNodeRuntimeFlags();
29
- if (args[0] !== 'sensor'
30
- || flags.length === 0
28
+ if (flags.length === 0
31
29
  || flags.every((flag) => process.execArgv.includes(flag))
32
30
  || env[RELAUNCH_GUARD_ENV] === '1') return null;
33
31
 
34
32
  const result = spawnSync(
35
33
  process.execPath,
36
- sensorRelaunchArgv(scriptPath, args),
34
+ node22RelaunchArgv(scriptPath, args),
37
35
  {
38
36
  cwd: process.cwd(),
39
37
  env: { ...env, [RELAUNCH_GUARD_ENV]: '1' },