@cassiomc1/forgeloop 1.0.0 → 1.1.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.
Files changed (95) hide show
  1. package/.cursor/rules/project-loop.mdc +3 -2
  2. package/.github/copilot-instructions.md +1 -0
  3. package/AGENTS.md +2 -1
  4. package/CLAUDE.md +1 -0
  5. package/DOCS_INDEX.md +36 -0
  6. package/ENG/design-code-eng.md +31 -0
  7. package/ENG/documentation-quality-eng.md +400 -0
  8. package/EXECUTION_STATE.md +23 -13
  9. package/GUIDE_ROUTER.md +23 -8
  10. package/LOOP_ENGINEERING.md +77 -12
  11. package/PROTOCOL_INTEGRATION.md +9 -6
  12. package/README.md +82 -39
  13. package/docs/ARTIFACT_REFERENCE.md +442 -0
  14. package/docs/CLI_REFERENCE.md +892 -0
  15. package/docs/CROSS_HARNESS_CONTINUITY.md +198 -0
  16. package/docs/DOCUMENTATION_GUIDE.md +161 -0
  17. package/docs/GETTING_STARTED.md +348 -0
  18. package/docs/RECIPES.md +250 -0
  19. package/docs/TROUBLESHOOTING.md +345 -0
  20. package/docs/assets/forgeloop-flow.svg +1 -1
  21. package/docs/forgeloop-flow.mmd +1 -1
  22. package/package.json +13 -2
  23. package/schemas/task-descriptor.schema.json +56 -0
  24. package/schemas/work-state.schema.json +18 -1
  25. package/scripts/CI_VALIDATORS.md +7 -0
  26. package/src/cli.js +280 -388
  27. package/src/commands/advance.js +5 -2
  28. package/src/commands/audit.js +11 -1
  29. package/src/commands/clear-continuity.js +5 -2
  30. package/src/commands/clear-state.js +5 -2
  31. package/src/commands/complete.js +9 -1
  32. package/src/commands/continuity.js +5 -2
  33. package/src/commands/inspect.js +10 -2
  34. package/src/commands/next.js +5 -2
  35. package/src/commands/preflight.js +9 -1
  36. package/src/commands/prepare-completion.js +5 -2
  37. package/src/commands/reconcile-continuity.js +5 -2
  38. package/src/commands/record-check.js +7 -1
  39. package/src/commands/record-continuity.js +21 -14
  40. package/src/commands/record-terminal-result.js +7 -1
  41. package/src/commands/route.js +22 -18
  42. package/src/commands/run-check.js +52 -44
  43. package/src/commands/status.js +18 -12
  44. package/src/commands/task-create.js +94 -0
  45. package/src/commands/task-list.js +48 -0
  46. package/src/commands/task-migrate.js +34 -0
  47. package/src/commands/task-scope.js +75 -0
  48. package/src/commands/task-show.js +81 -0
  49. package/src/commands/task-unlock.js +35 -0
  50. package/src/commands/validate-protocol.js +37 -20
  51. package/src/commands/validate-state.js +24 -18
  52. package/src/config/guides.json +42 -0
  53. package/src/core/activation.js +8 -4
  54. package/src/core/artifact-registry.js +166 -0
  55. package/src/core/audit.js +65 -12
  56. package/src/core/bundles.js +76 -50
  57. package/src/core/cli-command-definitions.js +611 -0
  58. package/src/core/cli-metadata.js +23 -0
  59. package/src/core/completion-artifacts.js +161 -74
  60. package/src/core/completion.js +134 -76
  61. package/src/core/continuity.js +20 -13
  62. package/src/core/contract.js +6 -3
  63. package/src/core/error-codes.js +197 -0
  64. package/src/core/events.js +19 -14
  65. package/src/core/execution.js +38 -6
  66. package/src/core/gate-artifact.js +12 -9
  67. package/src/core/gates.js +4 -2
  68. package/src/core/guide-metadata.js +7 -11
  69. package/src/core/guide-registry.js +29 -0
  70. package/src/core/inspect.js +7 -4
  71. package/src/core/native-adapters.js +6 -0
  72. package/src/core/phase.js +85 -33
  73. package/src/core/preflight-consistency.js +24 -14
  74. package/src/core/preflight-loaders.js +16 -11
  75. package/src/core/preflight.js +44 -25
  76. package/src/core/protocol.js +2 -11
  77. package/src/core/receipt.js +1 -1
  78. package/src/core/report.js +2 -2
  79. package/src/core/repository.js +46 -12
  80. package/src/core/resumability.js +6 -4
  81. package/src/core/route-artifact.js +9 -5
  82. package/src/core/router.js +11 -7
  83. package/src/core/schema-validation.js +1 -0
  84. package/src/core/task-command.js +41 -0
  85. package/src/core/task-context.js +126 -0
  86. package/src/core/task-descriptor.js +81 -0
  87. package/src/core/task-discovery.js +116 -0
  88. package/src/core/task-identity.js +76 -0
  89. package/src/core/task-lock.js +209 -0
  90. package/src/core/task-migration-validation.js +140 -0
  91. package/src/core/task-migration.js +361 -0
  92. package/src/core/task-paths.js +96 -0
  93. package/src/core/task-scope.js +179 -0
  94. package/src/core/templates.js +3 -9
  95. package/src/core/work-state.js +24 -13
@@ -18,9 +18,13 @@ import { readPersistedRoute } from "./route-artifact.js";
18
18
  import { createReceipt, validateReceipt } from "./receipt.js";
19
19
  import { readWorkState, writeWorkState } from "./work-state.js";
20
20
  import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
21
- import { normalizeRequirements, classifyRequirement } from "./evidence-readiness.js";
21
+ import { normalizeRequirements } from "./evidence-readiness.js";
22
22
  import { classifyCommandResolution, validateVerificationAuthority } from "./verification-capability.js";
23
23
  import { readExecutionArtifact, validateExecutionBinding } from "./execution.js";
24
+ import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
25
+ import { readTaskDescriptor } from "./task-descriptor.js";
26
+ import { assertClaimsCoverChangedPaths } from "./task-scope.js";
27
+ import { discoverTasks } from "./task-discovery.js";
24
28
 
25
29
  function artifactError(code, message, artifacts = []) {
26
30
  const error = new Error(message);
@@ -36,15 +40,16 @@ function requiredString(value, label) {
36
40
  return value;
37
41
  }
38
42
 
39
- async function readCurrentReceipt(target, packageRoot) {
43
+ async function readCurrentReceipt(target, packageRoot, options = {}) {
44
+ const receiptRel = options.receiptPath ?? (options.taskId ? taskArtifactPath(options.taskId, "receipt") : ARTIFACT_PATHS.receipt);
40
45
  try {
41
- return await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
46
+ return await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
42
47
  } catch (error) {
43
48
  if (error.code === "ARTIFACT_MISSING") {
44
49
  throw artifactError(
45
50
  "E_RECEIPT_MISSING",
46
51
  "Execution receipt is missing; run forgeloop prepare-completion first",
47
- [ARTIFACT_PATHS.receipt],
52
+ [receiptRel],
48
53
  );
49
54
  }
50
55
  throw error;
@@ -92,12 +97,13 @@ export async function validateCheckExecutionProvenance(check, {
92
97
  target,
93
98
  executionRef: check.executionRef,
94
99
  packageRoot,
100
+ taskId,
95
101
  });
96
102
  if (!artifact.value) {
97
103
  throw artifactError(
98
104
  "E_EXECUTION_REF_INVALID",
99
105
  "Execution reference does not resolve to an execution artifact in this bundle",
100
- [ARTIFACT_PATHS.executionDirectory],
106
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
101
107
  );
102
108
  }
103
109
  const execution = validateExecutionBinding({
@@ -111,52 +117,51 @@ export async function validateCheckExecutionProvenance(check, {
111
117
  throw artifactError(
112
118
  "E_EXECUTION_REF_INVALID",
113
119
  "Execution artifact cwd does not match the current target",
114
- [ARTIFACT_PATHS.executionDirectory],
120
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
115
121
  );
116
122
  }
117
123
  if (check.status === "passed" && (execution.status !== "passed" || execution.exitCode !== 0)) {
118
124
  throw artifactError(
119
125
  "E_EXECUTION_REF_INVALID",
120
126
  "A passed command check must reference a successful execution artifact",
121
- [ARTIFACT_PATHS.executionDirectory],
127
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
122
128
  );
123
129
  }
124
130
  if (check.status === "failed" && execution.status !== "failed") {
125
131
  throw artifactError(
126
132
  "E_EXECUTION_REF_INVALID",
127
133
  "A failed command check must reference a failed execution artifact",
128
- [ARTIFACT_PATHS.executionDirectory],
134
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
129
135
  );
130
136
  }
131
137
  if (check.exitCode !== undefined && check.exitCode !== execution.exitCode) {
132
138
  throw artifactError(
133
139
  "E_EXECUTION_REF_INVALID",
134
140
  "Check exitCode does not match its execution artifact",
135
- [ARTIFACT_PATHS.executionDirectory],
141
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
136
142
  );
137
143
  }
138
144
  return execution;
139
145
  }
140
146
 
141
- export async function validateChecksExecutionProvenance(checks, options = {}) {
142
- const errors = [];
143
- for (const check of Array.isArray(checks) ? checks : []) {
144
- try {
145
- await validateCheckExecutionProvenance(check, options);
146
- } catch (error) {
147
- errors.push({
148
- code: error.code ?? "E_EXECUTION_REF_INVALID",
149
- message: error.message,
150
- artifacts: error.artifacts ?? [options.artifactPath ?? ARTIFACT_PATHS.receipt],
151
- checkId: check?.id,
152
- requirementId: check?.requirement,
153
- });
147
+ export async function validateChecksExecutionProvenance(checks, options) {
148
+ const executions = [];
149
+ for (const check of checks ?? []) {
150
+ if (check.kind === "command" && check.evidenceKind === "OBSERVED") {
151
+ const execution = await validateCheckExecutionProvenance(check, options);
152
+ if (execution) executions.push(execution);
154
153
  }
155
154
  }
156
- return errors;
155
+ return executions;
157
156
  }
158
157
 
159
- export async function requiredEvidenceForTarget({ target, contract, route, packageRoot, additionalEvidence = [] }) {
158
+ export async function requiredEvidenceForTarget({
159
+ target,
160
+ contract,
161
+ route,
162
+ packageRoot,
163
+ additionalEvidence = [],
164
+ }) {
160
165
  const config = await readOptionalConfig(target, packageRoot);
161
166
  const guideEvidence = await completionEvidenceForGuides(route.value.guides, packageRoot);
162
167
  return [...new Set([
@@ -167,23 +172,36 @@ export async function requiredEvidenceForTarget({ target, contract, route, packa
167
172
  ])].sort();
168
173
  }
169
174
 
170
- export async function prepareCompletion({ target, packageRoot, authorityContext, runtimeContext }) {
171
- const contract = await readContract(target, packageRoot);
172
- const route = await readPersistedRoute(target, packageRoot);
173
- const state = await readWorkState(target, packageRoot);
175
+ export async function prepareCompletion({
176
+ target,
177
+ packageRoot,
178
+ authorityContext,
179
+ runtimeContext,
180
+ taskId = null,
181
+ contractPath = null,
182
+ routePath = null,
183
+ statePath = null,
184
+ receiptPath = null,
185
+ }) {
186
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
187
+ const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
188
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
189
+ const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
190
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
191
+
174
192
  if (!state) {
175
- throw artifactError("E_STATE_MISSING", "Work state is required before preparing completion", [ARTIFACT_PATHS.state]);
193
+ throw artifactError("E_STATE_MISSING", "Work state is required before preparing completion", [stateRel]);
176
194
  }
177
195
  if (hasExecutionStarted(state.phase)) {
178
- await assertExecutionPrerequisites({ target, state, packageRoot });
196
+ await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
179
197
  }
180
198
 
181
199
  let existing = null;
182
200
  try {
183
- existing = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
201
+ existing = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
184
202
  await validateReceipt(existing.value, packageRoot, {
185
203
  target,
186
- taskId: contract?.value?.taskId,
204
+ taskId: contract.value.taskId,
187
205
  authorityContext,
188
206
  runtimeContext,
189
207
  });
@@ -191,7 +209,7 @@ export async function prepareCompletion({ target, packageRoot, authorityContext,
191
209
  if (error.code !== "ARTIFACT_MISSING") throw error;
192
210
  }
193
211
 
194
- const preflight = await evaluatePreflight({ target, packageRoot });
212
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
195
213
  const requiredEvidence = await requiredEvidenceForTarget({
196
214
  target,
197
215
  contract,
@@ -201,13 +219,46 @@ export async function prepareCompletion({ target, packageRoot, authorityContext,
201
219
  });
202
220
  const existingValue = existing?.value ?? {};
203
221
  if (existingValue.taskId && existingValue.taskId !== contract.value.taskId) {
204
- throw artifactError("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [ARTIFACT_PATHS.receipt]);
222
+ throw artifactError("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [receiptRel]);
205
223
  }
206
224
  if (existing && existingValue.stateFingerprint === undefined) {
207
- throw artifactError("E_RECEIPT_STATE_MISMATCH", "Execution receipt requires the current work-state fingerprint", [ARTIFACT_PATHS.receipt]);
225
+ throw artifactError("E_RECEIPT_STATE_MISMATCH", "Execution receipt requires the current work-state fingerprint", [receiptRel]);
208
226
  }
209
227
  assertStateIdentity({ contract, route, state });
210
- const observedPaths = await currentChangedPaths(target);
228
+
229
+ let writeClaims = [];
230
+ try {
231
+ const desc = taskId ? await readTaskDescriptor(target, taskId, packageRoot) : null;
232
+ writeClaims = desc?.value?.writeClaims ?? [];
233
+ } catch {
234
+ // descriptor may not exist
235
+ }
236
+
237
+ let observedPaths = null;
238
+ if (writeClaims.length > 0) {
239
+ observedPaths = await currentChangedPaths(target, { paths: writeClaims });
240
+ if (observedPaths !== null) {
241
+ assertClaimsCoverChangedPaths(writeClaims, observedPaths);
242
+ }
243
+ } else {
244
+ observedPaths = await currentChangedPaths(target);
245
+ }
246
+
247
+ if (observedPaths === null) {
248
+ const allTasks = await discoverTasks(target, packageRoot);
249
+ const nonComplete = allTasks.filter((t) => t.phase !== "COMPLETE");
250
+ if (nonComplete.length > 1) {
251
+ const mode = preflight.policy?.complianceMode ?? "standard";
252
+ if (mode === "standard" || mode === "strict") {
253
+ throw artifactError(
254
+ "E_TASK_CHANGE_ATTRIBUTION_UNAVAILABLE",
255
+ "Cannot attribute changed files across multiple concurrent tasks in a non-Git target",
256
+ [receiptRel],
257
+ );
258
+ }
259
+ }
260
+ }
261
+
211
262
  const changedPaths = observedPaths !== null
212
263
  ? [...observedPaths]
213
264
  : existing
@@ -264,7 +315,7 @@ export async function prepareCompletion({ target, packageRoot, authorityContext,
264
315
  });
265
316
  const written = await writeJsonArtifact(
266
317
  target,
267
- ARTIFACT_PATHS.receipt,
318
+ receiptRel,
268
319
  receipt,
269
320
  "execution-receipt",
270
321
  packageRoot,
@@ -306,24 +357,33 @@ export async function assertRecordCheckPrerequisites({
306
357
  evidenceKind,
307
358
  authorityContext,
308
359
  runtimeContext,
360
+ taskId = null,
361
+ contractPath = null,
362
+ routePath = null,
363
+ statePath = null,
364
+ receiptPath = null,
365
+ eventsPath = null,
309
366
  } = {}) {
310
- const state = await readWorkState(target, packageRoot);
311
- if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a check", [ARTIFACT_PATHS.state]);
367
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
368
+ const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
369
+ const eventsRel = eventsPath ?? (taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events);
370
+
371
+ if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a check", [stateRel]);
312
372
  if (["COMPLETE", "BLOCKED"].includes(state.phase)) {
313
- throw artifactError("E_PHASE_TRANSITION_INVALID", `Cannot record a check in ${state.phase}`, [ARTIFACT_PATHS.state]);
373
+ throw artifactError("E_PHASE_TRANSITION_INVALID", `Cannot record a check in ${state.phase}`, [stateRel]);
314
374
  }
315
375
  if (state.phase !== "VERIFYING") {
316
376
  throw artifactError(
317
377
  "E_PHASE_PREREQUISITE_MISSING",
318
378
  `record-check requires VERIFYING before review; found ${state.phase}`,
319
- [ARTIFACT_PATHS.state],
379
+ [stateRel],
320
380
  );
321
381
  }
322
- await assertExecutionPrerequisites({ target, state, packageRoot });
382
+ await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
323
383
 
324
- const contract = await readContract(target, packageRoot);
325
- const route = await readPersistedRoute(target, packageRoot);
326
- const preflight = await evaluatePreflight({ target, packageRoot });
384
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
385
+ const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
386
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
327
387
  const requiredEvidence = await requiredEvidenceForTarget({
328
388
  target,
329
389
  contract,
@@ -338,27 +398,27 @@ export async function assertRecordCheckPrerequisites({
338
398
  throw artifactError(
339
399
  "E_FUTURE_LIFECYCLE_EVIDENCE",
340
400
  `Terminal-owned requirement cannot be recorded before its authoritative result: ${requested.text}`,
341
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
401
+ [stateRel, eventsRel],
342
402
  );
343
403
  }
344
404
 
345
- const existingReceipt = await readCurrentReceipt(target, packageRoot);
405
+ const existingReceipt = await readCurrentReceipt(target, packageRoot, { taskId, receiptPath });
346
406
  await validateReceipt(existingReceipt.value, packageRoot, {
347
407
  target,
348
408
  taskId: contract.value.taskId,
349
409
  authorityContext,
350
410
  runtimeContext,
351
411
  });
352
- const ledger = await validateEventLedger(target, packageRoot);
412
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
353
413
  if (!ledger.valid) {
354
414
  const first = ledger.errors[0];
355
- throw artifactError(first.code, first.message, [ARTIFACT_PATHS.events]);
415
+ throw artifactError(first.code, first.message, [eventsRel]);
356
416
  }
357
417
  if (!ledger.events.some((event) => event.taskId === state.taskId && event.event === "VERIFICATION_STARTED")) {
358
418
  throw artifactError(
359
419
  "E_PHASE_CHRONOLOGY_INVALID",
360
420
  "record-check requires VERIFICATION_STARTED in the current task ledger",
361
- [ARTIFACT_PATHS.events],
421
+ [eventsRel],
362
422
  );
363
423
  }
364
424
  return {
@@ -388,6 +448,12 @@ export async function recordCheck({
388
448
  provenance,
389
449
  authorityContext,
390
450
  runtimeContext,
451
+ taskId = null,
452
+ contractPath = null,
453
+ routePath = null,
454
+ statePath = null,
455
+ receiptPath = null,
456
+ eventsPath = null,
391
457
  }) {
392
458
  requiredString(id, "check id");
393
459
  requiredString(kind, "check kind");
@@ -422,6 +488,12 @@ export async function recordCheck({
422
488
  evidenceKind,
423
489
  authorityContext,
424
490
  runtimeContext,
491
+ taskId,
492
+ contractPath,
493
+ routePath,
494
+ statePath,
495
+ receiptPath,
496
+ eventsPath,
425
497
  });
426
498
  const {
427
499
  state,
@@ -431,6 +503,8 @@ export async function recordCheck({
431
503
  existingReceipt,
432
504
  } = context;
433
505
 
506
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
507
+
434
508
  const commandSpec = typeof command === "string" && command.trim() !== "" ? command.trim() : undefined;
435
509
  const observedCommand = kind === "command" && evidenceKind === "OBSERVED";
436
510
  if (observedCommand && (!executionRef || provenance !== "FORGELOOP_EXECUTED")) {
@@ -525,7 +599,7 @@ export async function recordCheck({
525
599
  runtimeContext,
526
600
  });
527
601
  if (!auth.valid) {
528
- throw artifactError(auth.error.code, auth.error.message, [ARTIFACT_PATHS.receipt]);
602
+ throw artifactError(auth.error.code, auth.error.message, [receiptRel]);
529
603
  }
530
604
  }
531
605
 
@@ -582,10 +656,10 @@ export async function recordCheck({
582
656
  runtimeContext,
583
657
  });
584
658
 
585
- await writeWorkState(target, nextState, { packageRoot });
659
+ await writeWorkState(target, nextState, { packageRoot, taskId, statePath });
586
660
  const written = await writeJsonArtifact(
587
661
  target,
588
- ARTIFACT_PATHS.receipt,
662
+ receiptRel,
589
663
  nextReceipt,
590
664
  "execution-receipt",
591
665
  packageRoot,
@@ -600,7 +674,7 @@ export async function recordCheck({
600
674
  evidenceKind: check.evidenceKind,
601
675
  verificationCycle: state.verificationCycle ?? 1,
602
676
  },
603
- }, packageRoot);
677
+ }, packageRoot, { taskId, eventsPath });
604
678
  return {
605
679
  path: written.path,
606
680
  receipt: written.value,
@@ -622,6 +696,12 @@ export async function recordTerminalResult({
622
696
  details = {},
623
697
  authorityContext,
624
698
  runtimeContext,
699
+ taskId = null,
700
+ contractPath = null,
701
+ routePath = null,
702
+ statePath = null,
703
+ receiptPath = null,
704
+ eventsPath = null,
625
705
  } = {}) {
626
706
  if (!target || !requirement || !type || !status || !source || !result) {
627
707
  throw artifactError("E_CHECK_INVALID", "record-terminal-result requires target, requirement, type, status, source, and result", [ARTIFACT_PATHS.state]);
@@ -636,16 +716,19 @@ export async function recordTerminalResult({
636
716
  throw artifactError("E_CHECK_INVALID", `Invalid production readiness status for record-terminal-result: ${status}`, [ARTIFACT_PATHS.state]);
637
717
  }
638
718
 
639
- const state = await readWorkState(target, packageRoot);
640
- if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a terminal result", [ARTIFACT_PATHS.state]);
719
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
720
+ const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
721
+ const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
722
+
723
+ if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a terminal result", [stateRel]);
641
724
  if (["COMPLETE", "BLOCKED"].includes(state.phase)) {
642
- throw artifactError("E_PHASE_TRANSITION_INVALID", `Cannot record a terminal result in ${state.phase}`, [ARTIFACT_PATHS.state]);
725
+ throw artifactError("E_PHASE_TRANSITION_INVALID", `Cannot record a terminal result in ${state.phase}`, [stateRel]);
643
726
  }
644
- await assertExecutionPrerequisites({ target, state, packageRoot });
727
+ await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
645
728
 
646
- const contract = await readContract(target, packageRoot);
647
- const route = await readPersistedRoute(target, packageRoot);
648
- const preflight = await evaluatePreflight({ target, packageRoot });
729
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
730
+ const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
731
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
649
732
  const requiredEvidence = await requiredEvidenceForTarget({
650
733
  target,
651
734
  contract,
@@ -663,7 +746,7 @@ export async function recordTerminalResult({
663
746
  throw artifactError(
664
747
  "E_TERMINAL_REQUIREMENT_UNKNOWN",
665
748
  `Terminal results must reference an existing canonical terminal requirement: ${requirement}`,
666
- [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt],
749
+ [contractRelPath(contract, taskId), receiptRel],
667
750
  );
668
751
  }
669
752
 
@@ -671,7 +754,7 @@ export async function recordTerminalResult({
671
754
  throw artifactError(
672
755
  "E_TERMINAL_REQUIREMENT_NOT_TERMINAL",
673
756
  `record-terminal-result may only target terminal-owned requirements; found ${requested.type}: ${requested.text}`,
674
- [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt],
757
+ [contractRelPath(contract, taskId), receiptRel],
675
758
  );
676
759
  }
677
760
 
@@ -679,11 +762,11 @@ export async function recordTerminalResult({
679
762
  throw artifactError(
680
763
  "E_TERMINAL_REQUIREMENT_TYPE_MISMATCH",
681
764
  `Requirement ${requested.id} is ${requested.type}, not ${type}.`,
682
- [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt],
765
+ [contractRelPath(contract, taskId), receiptRel],
683
766
  );
684
767
  }
685
768
 
686
- const existingReceipt = await readCurrentReceipt(target, packageRoot);
769
+ const existingReceipt = await readCurrentReceipt(target, packageRoot, { taskId, receiptPath });
687
770
  await validateReceipt(existingReceipt.value, packageRoot, {
688
771
  target,
689
772
  taskId: contract?.value?.taskId,
@@ -704,7 +787,7 @@ export async function recordTerminalResult({
704
787
  throw artifactError(
705
788
  "E_TERMINAL_STATUS_REGRESSION",
706
789
  `Publication status cannot regress from ${prev} to ${status}.`,
707
- [ARTIFACT_PATHS.receipt],
790
+ [receiptRel],
708
791
  );
709
792
  }
710
793
  }
@@ -723,7 +806,7 @@ export async function recordTerminalResult({
723
806
  ));
724
807
 
725
808
  if (isIdentical) {
726
- const ledger = await validateEventLedger(target, packageRoot);
809
+ const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
727
810
  const matchingEvent = ledger.events.find((event) => (
728
811
  event.taskId === state.taskId
729
812
  && event.event === "TERMINAL_RESULT_RECORDED"
@@ -737,7 +820,7 @@ export async function recordTerminalResult({
737
820
 
738
821
  if (matchingEvent) {
739
822
  return {
740
- path: path.join(target, ARTIFACT_PATHS.receipt),
823
+ path: path.join(target, receiptRel),
741
824
  receipt: existingReceipt.value,
742
825
  requirementId: requested.id,
743
826
  type,
@@ -759,10 +842,10 @@ export async function recordTerminalResult({
759
842
  source: source.trim(),
760
843
  result: result.trim(),
761
844
  },
762
- }, packageRoot);
845
+ }, packageRoot, { taskId, eventsPath });
763
846
 
764
847
  return {
765
- path: path.join(target, ARTIFACT_PATHS.receipt),
848
+ path: path.join(target, receiptRel),
766
849
  receipt: existingReceipt.value,
767
850
  requirementId: requested.id,
768
851
  type,
@@ -823,10 +906,10 @@ export async function recordTerminalResult({
823
906
  runtimeContext,
824
907
  });
825
908
 
826
- await writeWorkState(target, nextState, { packageRoot });
909
+ await writeWorkState(target, nextState, { packageRoot, taskId, statePath });
827
910
  await writeJsonArtifact(
828
911
  target,
829
- ARTIFACT_PATHS.receipt,
912
+ receiptRel,
830
913
  nextReceipt,
831
914
  "execution-receipt",
832
915
  packageRoot,
@@ -843,10 +926,10 @@ export async function recordTerminalResult({
843
926
  source: source.trim(),
844
927
  result: result.trim(),
845
928
  },
846
- }, packageRoot);
929
+ }, packageRoot, { taskId, eventsPath });
847
930
 
848
931
  return {
849
- path: path.join(target, ARTIFACT_PATHS.receipt),
932
+ path: path.join(target, receiptRel),
850
933
  receipt: nextReceipt,
851
934
  requirementId: requested.id,
852
935
  type,
@@ -855,3 +938,7 @@ export async function recordTerminalResult({
855
938
  event,
856
939
  };
857
940
  }
941
+
942
+ function contractRelPath(contract, taskId) {
943
+ return taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract;
944
+ }