@quolu/lattice 0.36.0 → 0.37.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.
package/README.ja.md CHANGED
@@ -213,6 +213,20 @@ lattice todo phase review --plan <key> --phase terminal-audit --reason <text>
213
213
  lattice todo phase accept --plan <key> --phase terminal-audit --input <file>
214
214
  ```
215
215
 
216
+ **過去の工程は監査できません。** 監査対象のコードが既に変化しているためです。そこで
217
+ 「監査なしで閉じた」という状態を別に持ちます——`accepted`(監査を通った)へは絶対に化けず、
218
+ 記録は永久に区別できます。工程図では畳まれるので監査待ちの札は外れます。
219
+
220
+ ```bash
221
+ lattice todo phase close-unaudited --plan <key> --phase terminal-audit --reason <text>
222
+ lattice todo phase baseline --reason <text> --except <監査したいplan_key> # 一括
223
+ ```
224
+
225
+ 一括の入口は、現在監査待ちで一度も監査に触れていないPhaseだけを対象にします。**自動では
226
+ 実行しません**——どれを監査し、どれを歴史として畳むかは人が決め、Latticeは宣言を受け取って
227
+ 記録するだけです。`--except`は「最近の作業でコードも生きているので本当に監査したい」planを
228
+ 残すための口です。規約は[ADR 0148](docs/adr/0148-history-closes-unaudited-not-audited.md)が正です。
229
+
216
230
  誤ってphase無しで作ったplanへ後からPhaseを被せる場合は、`revise-phase`の
217
231
  `state_policy: acquire_phase`でdone状態を保ったまま獲得できます(未割当→割当の向きだけを許し、
218
232
  既にphaseを持つtaskの付け替えは拒否します)。
package/README.md CHANGED
@@ -158,6 +158,15 @@ rejected over it; the requirement is reported instead. And the audit gate never
158
158
  phases order reviews, the ToDo DAG orders work
159
159
  ([ADR 0147](docs/adr/0147-audit-is-on-by-default.md)).
160
160
 
161
+ **History closes unaudited, never audited.** Work that finished long ago cannot be audited — the
162
+ code under review has already moved. Demanding an audit there produces either a false finding
163
+ (pointing at a later, intentional change) or a rubber stamp. So there is a third terminal state,
164
+ `closed_unaudited`: recorded with a reason, folded away like finished work, and **structurally
165
+ incapable of passing as `accepted`** — phase-accept dependencies unlock on `accepted` alone. The
166
+ bulk entry point never runs by itself, and the machine never infers "old enough to skip"; a human
167
+ decides what gets audited and what becomes history
168
+ ([ADR 0148](docs/adr/0148-history-closes-unaudited-not-audited.md)).
169
+
161
170
  ## Patent
162
171
 
163
172
  The design in this repository is the subject of a Japanese patent application:
@@ -0,0 +1,312 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/kitepon-rgb/Lattice/blob/main/docs/schemas/lattice.phase_todo_revision.v3.schema.json",
4
+ "title": "lattice.phase_todo_revision.v3",
5
+ "description": "`lattice todo revise-phase`が受理する最新契約。v1/v2のphase-audited desired state・task/phase migrationへ、runtime task migration・source inventory・per-ToDo source cutover batchを加えた12 keyの完全契約。",
6
+ "$comment": "Runtime validation (src/todo-revision.mjs validatePhaseTodoRevision) additionally enforces: canonical JSON+LF bytes; strict sort order on task_migration (by from_task_id), runtime_task_migration.entries (by predecessor_task_id), runtime_task_migration.entries[].successor_task_ids/evidence_digests, and source_inventory.excluded_tombstones/source_cutover_batch.operations (by source_ref); uniqueness constraints across all of those; desired_plan.plan_version === phaseTodoRevisionPlanVersion(...); runtime_task_migration projects onto task_migration exactly (validRuntimeTodoProjection) and its successor_task_ids cover exactly desired_plan.tasks; phase_migration's target phase set equals desired_plan.phases exactly; every reconciliation.*_digest field against its own recomputation; and revision_digest as the canonical self-digest over every other top-level field.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "schema", "project_id", "plan_key", "predecessor", "desired_plan",
11
+ "runtime_task_migration", "task_migration", "phase_migration", "source_inventory",
12
+ "reconciliation", "source_cutover_batch", "revision_digest"
13
+ ],
14
+ "properties": {
15
+ "schema": { "const": "lattice.phase_todo_revision.v3" },
16
+ "project_id": { "$ref": "#/$defs/identifier" },
17
+ "plan_key": { "$ref": "#/$defs/identifier" },
18
+ "predecessor": { "$ref": "#/$defs/predecessor" },
19
+ "desired_plan": { "$ref": "#/$defs/desiredPlanV5" },
20
+ "runtime_task_migration": { "$ref": "#/$defs/runtimeTaskMigration" },
21
+ "task_migration": {
22
+ "type": "array", "minItems": 1, "maxItems": 512,
23
+ "items": { "$ref": "#/$defs/taskMigrationEntry" }
24
+ },
25
+ "phase_migration": {
26
+ "type": "array", "minItems": 1, "maxItems": 512,
27
+ "items": { "$ref": "#/$defs/phaseMigrationEntry" }
28
+ },
29
+ "source_inventory": { "$ref": "#/$defs/sourceInventory" },
30
+ "reconciliation": {
31
+ "type": "object", "additionalProperties": false,
32
+ "required": [
33
+ "predecessor_reconciliation_digest", "source_inventory_digest", "desired_plan_digest",
34
+ "runtime_task_migration_digest", "task_migration_digest", "phase_migration_digest",
35
+ "source_cutover_batch_digest", "reconciliation_digest"
36
+ ],
37
+ "properties": {
38
+ "predecessor_reconciliation_digest": { "$ref": "#/$defs/digest" },
39
+ "source_inventory_digest": { "$ref": "#/$defs/digest" },
40
+ "desired_plan_digest": { "$ref": "#/$defs/digest" },
41
+ "runtime_task_migration_digest": { "$ref": "#/$defs/digest" },
42
+ "task_migration_digest": { "$ref": "#/$defs/digest" },
43
+ "phase_migration_digest": { "$ref": "#/$defs/digest" },
44
+ "source_cutover_batch_digest": { "$ref": "#/$defs/digest" },
45
+ "reconciliation_digest": { "$ref": "#/$defs/digest" }
46
+ }
47
+ },
48
+ "source_cutover_batch": { "$ref": "#/$defs/sourceCutoverBatch" },
49
+ "revision_digest": { "$ref": "#/$defs/digest" }
50
+ },
51
+ "$defs": {
52
+ "identifier": { "type": "string", "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$" },
53
+ "digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
54
+ "nullableDigest": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/digest" }] },
55
+ "gitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
56
+ "repoRef": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "maxLength": 1024,
60
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])[^/]+(?:/[^/]+)*$"
61
+ },
62
+ "sourceLineRef": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "maxLength": 1024,
66
+ "$comment": "repoRefと同じ形に加え#L<line>を必須にする。実際の判定はparseTodoSourceRef(src/todo-revision.mjs)。",
67
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])[^/]+(?:/[^/]+)*#L[1-9][0-9]*$"
68
+ },
69
+ "nullableRepoRef": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/repoRef" }] },
70
+ "text": { "type": "string", "minLength": 1, "maxLength": 16384 },
71
+ "nullableText": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/text" }] },
72
+ "nodeRef": {
73
+ "type": "object", "additionalProperties": false,
74
+ "required": ["project_id", "plan_key", "task_id"],
75
+ "properties": {
76
+ "project_id": { "$ref": "#/$defs/identifier" },
77
+ "plan_key": { "$ref": "#/$defs/identifier" },
78
+ "task_id": { "$ref": "#/$defs/identifier" },
79
+ "expected_topology_digest": { "$ref": "#/$defs/digest" }
80
+ }
81
+ },
82
+ "phaseRef": {
83
+ "type": "object", "additionalProperties": false,
84
+ "required": ["project_id", "plan_key", "phase_id"],
85
+ "properties": {
86
+ "project_id": { "$ref": "#/$defs/identifier" },
87
+ "plan_key": { "$ref": "#/$defs/identifier" },
88
+ "phase_id": { "$ref": "#/$defs/identifier" },
89
+ "expected_topology_digest": { "$ref": "#/$defs/digest" }
90
+ }
91
+ },
92
+ "edge": {
93
+ "type": "object", "additionalProperties": false, "required": ["from", "to"],
94
+ "properties": { "from": { "$ref": "#/$defs/nodeRef" }, "to": { "$ref": "#/$defs/nodeRef" } }
95
+ },
96
+ "phaseAcceptDependency": {
97
+ "type": "object", "additionalProperties": false, "required": ["from", "to"],
98
+ "properties": { "from": { "$ref": "#/$defs/phaseRef" }, "to": { "$ref": "#/$defs/nodeRef" } }
99
+ },
100
+ "join": {
101
+ "type": "object", "additionalProperties": false, "required": ["id", "after", "before"],
102
+ "properties": {
103
+ "id": { "$ref": "#/$defs/identifier" },
104
+ "after": { "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/nodeRef" } },
105
+ "before": { "$ref": "#/$defs/nodeRef" }
106
+ }
107
+ },
108
+ "narrativeAnchor": {
109
+ "type": "object", "additionalProperties": false,
110
+ "required": ["origin_plan_ref", "origin_line", "source_commit", "source_line_digest"],
111
+ "properties": {
112
+ "origin_plan_ref": { "$ref": "#/$defs/repoRef" },
113
+ "origin_line": { "type": "integer", "minimum": 1 },
114
+ "source_commit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$" },
115
+ "source_line_digest": { "$ref": "#/$defs/digest" }
116
+ }
117
+ },
118
+ "compileBinding": {
119
+ "oneOf": [
120
+ { "type": "null" },
121
+ {
122
+ "type": "object", "additionalProperties": false,
123
+ "required": ["boundary_manifest_digest", "compiled_plan_digest", "topology_digest", "base_sha"],
124
+ "properties": {
125
+ "boundary_manifest_digest": { "$ref": "#/$defs/digest" },
126
+ "compiled_plan_digest": { "$ref": "#/$defs/digest" },
127
+ "topology_digest": { "$ref": "#/$defs/digest" },
128
+ "base_sha": { "$ref": "#/$defs/gitSha" }
129
+ }
130
+ }
131
+ ]
132
+ },
133
+ "predecessor": {
134
+ "type": "object", "additionalProperties": false,
135
+ "required": ["plan_digest", "journal_head_digest", "plan_version"],
136
+ "properties": {
137
+ "plan_digest": { "$ref": "#/$defs/digest" },
138
+ "journal_head_digest": { "$ref": "#/$defs/digest" },
139
+ "plan_version": { "$ref": "#/$defs/identifier" }
140
+ }
141
+ },
142
+ "taskV4": {
143
+ "type": "object", "additionalProperties": false,
144
+ "required": [
145
+ "task_id", "title", "lane", "narrative_ref", "narrative_anchor", "compile_binding",
146
+ "parent_task_id", "phase_id"
147
+ ],
148
+ "properties": {
149
+ "task_id": { "$ref": "#/$defs/identifier" },
150
+ "title": { "$ref": "#/$defs/nullableText" },
151
+ "lane": { "$ref": "#/$defs/identifier" },
152
+ "narrative_ref": { "$ref": "#/$defs/nullableRepoRef" },
153
+ "narrative_anchor": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/narrativeAnchor" }] },
154
+ "compile_binding": { "$ref": "#/$defs/compileBinding" },
155
+ "parent_task_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] },
156
+ "phase_id": { "$ref": "#/$defs/identifier" }
157
+ }
158
+ },
159
+ "phase": {
160
+ "type": "object", "additionalProperties": false,
161
+ "required": ["phase_id", "title", "gate_policy", "predecessor_phase_ids", "required_evidence_slots"],
162
+ "properties": {
163
+ "phase_id": { "$ref": "#/$defs/identifier" },
164
+ "title": { "$ref": "#/$defs/nullableText" },
165
+ "gate_policy": { "$ref": "#/$defs/identifier" },
166
+ "predecessor_phase_ids": {
167
+ "type": "array", "items": { "$ref": "#/$defs/identifier" }, "uniqueItems": true
168
+ },
169
+ "required_evidence_slots": {
170
+ "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/identifier" }, "uniqueItems": true
171
+ }
172
+ }
173
+ },
174
+ "desiredPlanV5": {
175
+ "type": "object", "additionalProperties": false,
176
+ "required": [
177
+ "schema", "project_id", "plan_key", "plan_version", "predecessor_plan_digest",
178
+ "tasks", "hard_dependencies", "joins", "phases", "phase_accept_dependencies",
179
+ "topology_digest", "plan_digest"
180
+ ],
181
+ "properties": {
182
+ "schema": { "const": "lattice.todo_plan.v5" },
183
+ "project_id": { "$ref": "#/$defs/identifier" },
184
+ "plan_key": { "$ref": "#/$defs/identifier" },
185
+ "plan_version": { "$ref": "#/$defs/identifier" },
186
+ "predecessor_plan_digest": { "$ref": "#/$defs/nullableDigest" },
187
+ "tasks": { "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/taskV4" } },
188
+ "hard_dependencies": { "type": "array", "maxItems": 2048, "items": { "$ref": "#/$defs/edge" } },
189
+ "joins": { "type": "array", "maxItems": 128, "items": { "$ref": "#/$defs/join" } },
190
+ "phases": { "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/phase" } },
191
+ "phase_accept_dependencies": {
192
+ "type": "array", "maxItems": 2048, "items": { "$ref": "#/$defs/phaseAcceptDependency" }
193
+ },
194
+ "topology_digest": { "$ref": "#/$defs/digest" },
195
+ "plan_digest": { "$ref": "#/$defs/digest" }
196
+ }
197
+ },
198
+ "taskMigrationEntry": {
199
+ "type": "object", "additionalProperties": false,
200
+ "required": ["from_task_id", "to_task_id", "state_policy"],
201
+ "properties": {
202
+ "from_task_id": { "$ref": "#/$defs/identifier" },
203
+ "to_task_id": { "oneOf": [{ "const": "removed" }, { "$ref": "#/$defs/identifier" }] },
204
+ "state_policy": {
205
+ "enum": ["carry", "carry_reconciled_metadata", "reset_pending", "removed"],
206
+ "$comment": "'removed'はto_task_id==='removed'の時だけ、他の3値はto_task_idがidentifierの時だけ許される(runtime cross-check)。runtime_task_migrationのdisposition投影とも一致していなければならない(validRuntimeTodoProjection)。"
207
+ }
208
+ }
209
+ },
210
+ "phaseMigrationEntry": {
211
+ "type": "object", "additionalProperties": false,
212
+ "required": ["from_phase_id", "to_phase_id", "state_policy"],
213
+ "properties": {
214
+ "from_phase_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] },
215
+ "to_phase_id": { "oneOf": [{ "const": "removed" }, { "$ref": "#/$defs/identifier" }] },
216
+ "state_policy": {
217
+ "enum": ["carry", "reset", "removed"],
218
+ "$comment": "carryはfrom_phase_id!=null かつ to_phase_id!=='removed'の時だけ、removedはfrom_phase_id!=null かつ to_phase_id==='removed'の時だけ、resetはto_phase_id!=='removed'の時だけ許される(runtime cross-check)。"
219
+ }
220
+ }
221
+ },
222
+ "runtimeTaskMigrationEntry": {
223
+ "type": "object", "additionalProperties": false,
224
+ "required": ["predecessor_task_id", "disposition", "successor_task_ids", "reason", "evidence_digests"],
225
+ "properties": {
226
+ "predecessor_task_id": { "$ref": "#/$defs/identifier" },
227
+ "disposition": { "enum": ["carry", "stay", "replace", "split", "retire"] },
228
+ "successor_task_ids": {
229
+ "type": "array", "maxItems": 512, "items": { "$ref": "#/$defs/identifier" }, "uniqueItems": true,
230
+ "$comment": "carry/stayは要素1つでpredecessor_task_idと同一、retireは空、replace/splitは1つ以上(runtime cross-check)。全entry合算で重複禁止。"
231
+ },
232
+ "reason": { "$ref": "#/$defs/text" },
233
+ "evidence_digests": {
234
+ "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/digest" },
235
+ "uniqueItems": true
236
+ }
237
+ }
238
+ },
239
+ "runtimeTaskMigration": {
240
+ "type": "object", "additionalProperties": false,
241
+ "required": ["schema", "entries", "migration_digest"],
242
+ "properties": {
243
+ "schema": { "const": "lattice.runtime_task_migration.v1" },
244
+ "entries": {
245
+ "type": "array", "minItems": 1, "maxItems": 512,
246
+ "items": { "$ref": "#/$defs/runtimeTaskMigrationEntry" }
247
+ },
248
+ "migration_digest": { "$ref": "#/$defs/digest" }
249
+ }
250
+ },
251
+ "sourceInventoryActiveEntry": {
252
+ "type": "object", "additionalProperties": false,
253
+ "required": ["task_id", "source_ref", "source_digest"],
254
+ "properties": {
255
+ "task_id": { "$ref": "#/$defs/identifier" },
256
+ "source_ref": { "$ref": "#/$defs/sourceLineRef" },
257
+ "source_digest": { "$ref": "#/$defs/digest" }
258
+ }
259
+ },
260
+ "sourceInventoryTombstone": {
261
+ "type": "object", "additionalProperties": false,
262
+ "required": ["source_ref", "source_digest", "exclusion_reason"],
263
+ "properties": {
264
+ "source_ref": { "$ref": "#/$defs/sourceLineRef" },
265
+ "source_digest": { "$ref": "#/$defs/digest" },
266
+ "exclusion_reason": { "$ref": "#/$defs/text" }
267
+ }
268
+ },
269
+ "sourceInventory": {
270
+ "type": "object", "additionalProperties": false,
271
+ "required": ["active", "excluded_tombstones"],
272
+ "$comment": "activeはdesired_plan.tasksと同じ順序・同じtask_id集合でなければならず、各entryのsource_refはそのtaskのnarrative_refと一致する必要がある(revise-phase v3固有のrequireNarrativeRef、runtime cross-check)。",
273
+ "properties": {
274
+ "active": {
275
+ "type": "array", "maxItems": 512, "items": { "$ref": "#/$defs/sourceInventoryActiveEntry" }
276
+ },
277
+ "excluded_tombstones": {
278
+ "type": "array", "maxItems": 2048, "items": { "$ref": "#/$defs/sourceInventoryTombstone" }
279
+ }
280
+ }
281
+ },
282
+ "sourceCutoverOperation": {
283
+ "type": "object", "additionalProperties": false,
284
+ "required": ["task_id", "disposition", "source_ref", "source_digest", "live_replacement"],
285
+ "properties": {
286
+ "task_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] },
287
+ "disposition": { "enum": ["active", "excluded"] },
288
+ "source_ref": { "$ref": "#/$defs/sourceLineRef" },
289
+ "source_digest": { "$ref": "#/$defs/digest" },
290
+ "live_replacement": {
291
+ "type": "string", "minLength": 1, "maxLength": 4096,
292
+ "$comment": "改行・制御文字禁止に加え、Markdownチェックボックス行に見える文字列は拒否される(validLiveReplacement)。"
293
+ }
294
+ }
295
+ },
296
+ "sourceCutoverBatch": {
297
+ "type": "object", "additionalProperties": false,
298
+ "required": ["batch_id", "archive_ref", "operations", "batch_digest"],
299
+ "properties": {
300
+ "batch_id": { "$ref": "#/$defs/identifier" },
301
+ "archive_ref": {
302
+ "allOf": [{ "$ref": "#/$defs/repoRef" }, { "pattern": "\\.md$" }],
303
+ "$comment": "#L<line>付きのsourceLineRef形にはなり得ない(runtime cross-check)。"
304
+ },
305
+ "operations": {
306
+ "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/sourceCutoverOperation" }
307
+ },
308
+ "batch_digest": { "$ref": "#/$defs/digest" }
309
+ }
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,298 @@
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.v2.schema.json",
4
+ "title": "lattice.todo_extraction.v2",
5
+ "description": "G4 migration-wave-only intermediate artifact authored by AI. Version 2 adds fail-closed historical in-progress registration without changing v1.",
6
+ "$comment": "Runtime validation additionally enforces UTF-8 byte bounds, strict sort order, unique and resolvable task ids, and extraction_digest as the canonical self-digest. Registration separately rejects an extraction with no register_* task.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "schema",
11
+ "project_id",
12
+ "plan_key",
13
+ "plan_version",
14
+ "actor",
15
+ "recorded_at",
16
+ "tasks",
17
+ "hard_dependencies",
18
+ "joins",
19
+ "extraction_digest"
20
+ ],
21
+ "properties": {
22
+ "schema": { "const": "lattice.todo_extraction.v2" },
23
+ "project_id": { "$ref": "#/$defs/identifier" },
24
+ "plan_key": { "$ref": "#/$defs/identifier" },
25
+ "plan_version": { "$ref": "#/$defs/identifier" },
26
+ "actor": { "$ref": "#/$defs/actor" },
27
+ "recorded_at": { "$ref": "#/$defs/strictTimestamp" },
28
+ "tasks": {
29
+ "type": "array",
30
+ "minItems": 1,
31
+ "maxItems": 512,
32
+ "items": { "$ref": "#/$defs/task" }
33
+ },
34
+ "hard_dependencies": {
35
+ "type": "array",
36
+ "maxItems": 2048,
37
+ "items": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": ["from", "to"],
41
+ "properties": {
42
+ "from": { "$ref": "#/$defs/nodeRef" },
43
+ "to": { "$ref": "#/$defs/nodeRef" }
44
+ }
45
+ }
46
+ },
47
+ "joins": {
48
+ "type": "array",
49
+ "maxItems": 128,
50
+ "items": {
51
+ "type": "object",
52
+ "additionalProperties": false,
53
+ "required": ["id", "after", "before"],
54
+ "properties": {
55
+ "id": { "$ref": "#/$defs/identifier" },
56
+ "after": {
57
+ "type": "array",
58
+ "minItems": 1,
59
+ "maxItems": 512,
60
+ "items": { "$ref": "#/$defs/nodeRef" }
61
+ },
62
+ "before": { "$ref": "#/$defs/nodeRef" }
63
+ }
64
+ }
65
+ },
66
+ "extraction_digest": { "$ref": "#/$defs/digest" }
67
+ },
68
+ "$defs": {
69
+ "identifier": {
70
+ "type": "string",
71
+ "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$"
72
+ },
73
+ "digest": {
74
+ "type": "string",
75
+ "pattern": "^[0-9a-f]{64}$"
76
+ },
77
+ "repoRef": {
78
+ "type": "string",
79
+ "minLength": 1,
80
+ "maxLength": 1024,
81
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])[^/]+(?:/[^/]+)*$"
82
+ },
83
+ "text": {
84
+ "type": "string",
85
+ "minLength": 1,
86
+ "maxLength": 16384
87
+ },
88
+ "nullableText": {
89
+ "oneOf": [
90
+ { "type": "null" },
91
+ { "$ref": "#/$defs/text" }
92
+ ]
93
+ },
94
+ "strictTimestamp": {
95
+ "type": "string",
96
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
97
+ "$comment": "Runtime validation also requires Date.parse(value).toISOString() byte equality."
98
+ },
99
+ "actor": {
100
+ "type": "object",
101
+ "additionalProperties": false,
102
+ "required": ["host", "session", "agent"],
103
+ "properties": {
104
+ "host": { "$ref": "#/$defs/identifier" },
105
+ "session": { "$ref": "#/$defs/identifier" },
106
+ "agent": { "$ref": "#/$defs/identifier" }
107
+ }
108
+ },
109
+ "nodeRef": {
110
+ "type": "object",
111
+ "additionalProperties": false,
112
+ "required": ["project_id", "plan_key", "task_id"],
113
+ "properties": {
114
+ "project_id": { "$ref": "#/$defs/identifier" },
115
+ "plan_key": { "$ref": "#/$defs/identifier" },
116
+ "task_id": { "$ref": "#/$defs/identifier" },
117
+ "expected_topology_digest": { "$ref": "#/$defs/digest" }
118
+ }
119
+ },
120
+ "sourceLocation": {
121
+ "type": "object",
122
+ "additionalProperties": false,
123
+ "required": [
124
+ "origin_plan_ref",
125
+ "origin_line",
126
+ "source_commit",
127
+ "heading_path",
128
+ "markdown_depth",
129
+ "parent_task_id",
130
+ "checkbox_state"
131
+ ],
132
+ "properties": {
133
+ "origin_plan_ref": { "$ref": "#/$defs/repoRef" },
134
+ "origin_line": { "type": "integer", "minimum": 1 },
135
+ "source_commit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$" },
136
+ "heading_path": {
137
+ "type": "array",
138
+ "maxItems": 32,
139
+ "items": { "type": "string", "minLength": 1, "maxLength": 1024 }
140
+ },
141
+ "markdown_depth": { "type": "integer", "minimum": 0, "maximum": 64 },
142
+ "parent_task_id": {
143
+ "oneOf": [
144
+ { "type": "null" },
145
+ { "$ref": "#/$defs/identifier" }
146
+ ]
147
+ },
148
+ "checkbox_state": {
149
+ "enum": ["checked", "unchecked", "absent", "ambiguous"]
150
+ }
151
+ }
152
+ },
153
+ "migrationContext": {
154
+ "type": "object",
155
+ "additionalProperties": false,
156
+ "required": [
157
+ "external_canonical_ref",
158
+ "carry_over_ref",
159
+ "h_required",
160
+ "condition",
161
+ "evidence_refs",
162
+ "notes"
163
+ ],
164
+ "properties": {
165
+ "external_canonical_ref": { "$ref": "#/$defs/nullableText" },
166
+ "carry_over_ref": { "$ref": "#/$defs/nullableText" },
167
+ "h_required": { "type": "boolean" },
168
+ "condition": { "$ref": "#/$defs/nullableText" },
169
+ "evidence_refs": {
170
+ "type": "array",
171
+ "maxItems": 64,
172
+ "items": { "type": "string", "minLength": 1, "maxLength": 4096 }
173
+ },
174
+ "notes": {
175
+ "type": "array",
176
+ "maxItems": 64,
177
+ "items": { "type": "string", "minLength": 1, "maxLength": 4096 }
178
+ }
179
+ }
180
+ },
181
+ "historicalStart": {
182
+ "type": "object",
183
+ "additionalProperties": false,
184
+ "required": ["start_mode", "status", "started_at"],
185
+ "properties": {
186
+ "start_mode": { "const": "historical_import" },
187
+ "status": { "const": "in-progress" },
188
+ "started_at": {
189
+ "oneOf": [
190
+ { "const": "unknown_requires_evidence" },
191
+ { "$ref": "#/$defs/strictTimestamp" }
192
+ ]
193
+ }
194
+ }
195
+ },
196
+ "historicalCompletion": {
197
+ "type": "object",
198
+ "additionalProperties": false,
199
+ "required": ["done_mode", "completed_at"],
200
+ "properties": {
201
+ "done_mode": { "const": "historical_import" },
202
+ "completed_at": {
203
+ "oneOf": [
204
+ { "const": "unknown_requires_evidence" },
205
+ { "$ref": "#/$defs/strictTimestamp" }
206
+ ]
207
+ }
208
+ }
209
+ },
210
+ "taskBase": {
211
+ "type": "object",
212
+ "additionalProperties": false,
213
+ "required": [
214
+ "task_id",
215
+ "title",
216
+ "lane",
217
+ "narrative_ref",
218
+ "compile_binding",
219
+ "disposition",
220
+ "start",
221
+ "completion",
222
+ "source",
223
+ "migration_context"
224
+ ],
225
+ "properties": {
226
+ "task_id": { "$ref": "#/$defs/identifier" },
227
+ "title": { "$ref": "#/$defs/text" },
228
+ "lane": { "$ref": "#/$defs/identifier" },
229
+ "narrative_ref": {
230
+ "oneOf": [
231
+ { "type": "null" },
232
+ { "$ref": "#/$defs/repoRef" }
233
+ ]
234
+ },
235
+ "compile_binding": { "type": "null" },
236
+ "disposition": {
237
+ "enum": [
238
+ "register_pending",
239
+ "register_in_progress",
240
+ "register_done",
241
+ "exclude_superseded",
242
+ "exclude_compatibility_record",
243
+ "unknown_requires_evidence"
244
+ ]
245
+ },
246
+ "start": {
247
+ "oneOf": [
248
+ { "type": "null" },
249
+ { "$ref": "#/$defs/historicalStart" }
250
+ ]
251
+ },
252
+ "completion": {
253
+ "oneOf": [
254
+ { "type": "null" },
255
+ { "$ref": "#/$defs/historicalCompletion" }
256
+ ]
257
+ },
258
+ "source": { "$ref": "#/$defs/sourceLocation" },
259
+ "migration_context": { "$ref": "#/$defs/migrationContext" }
260
+ }
261
+ },
262
+ "task": {
263
+ "allOf": [
264
+ { "$ref": "#/$defs/taskBase" },
265
+ {
266
+ "if": {
267
+ "properties": { "disposition": { "const": "register_in_progress" } },
268
+ "required": ["disposition"]
269
+ },
270
+ "then": {
271
+ "properties": {
272
+ "start": { "$ref": "#/$defs/historicalStart" },
273
+ "completion": { "type": "null" }
274
+ }
275
+ },
276
+ "else": {
277
+ "if": {
278
+ "properties": { "disposition": { "const": "register_done" } },
279
+ "required": ["disposition"]
280
+ },
281
+ "then": {
282
+ "properties": {
283
+ "start": { "type": "null" },
284
+ "completion": { "$ref": "#/$defs/historicalCompletion" }
285
+ }
286
+ },
287
+ "else": {
288
+ "properties": {
289
+ "start": { "type": "null" },
290
+ "completion": { "type": "null" }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ ]
296
+ }
297
+ }
298
+ }