@fitlab-ai/agent-infra 0.11.9 → 0.11.11

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 (219) hide show
  1. package/dist/bin/internal-cli.js +0 -6
  2. package/dist/lib/agent-clients/adapter.js +20 -0
  3. package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
  4. package/dist/lib/agent-clients/adapters/codex.js +4 -0
  5. package/dist/lib/git/workflow.js +1 -1
  6. package/dist/lib/internal/cli-route-inventory.js +0 -3
  7. package/dist/lib/internal/manual-validation.js +2 -2
  8. package/dist/lib/internal/platform-pr.js +4 -5
  9. package/dist/lib/internal/sandbox-control.js +30 -5
  10. package/dist/lib/internal/task-artifact.js +1 -1
  11. package/dist/lib/internal/task-create.js +15 -1
  12. package/dist/lib/internal/task-finalization.js +14 -25
  13. package/dist/lib/internal/task-operation-registry.js +6 -141
  14. package/dist/lib/internal/task-platform-recovery.js +9 -4
  15. package/dist/lib/platform/completion-backfill.js +2 -1
  16. package/dist/lib/platform/context.js +4 -2
  17. package/dist/lib/platform/github-client.js +20 -17
  18. package/dist/lib/platform/github-provider.js +42 -22
  19. package/dist/lib/platform/github-release-notes.js +4 -2
  20. package/dist/lib/platform/in-label-sync.js +2 -1
  21. package/dist/lib/platform/issue-comments.js +13 -9
  22. package/dist/lib/platform/operation-coordinator.js +1 -1
  23. package/dist/lib/platform/pr-review.js +2 -1
  24. package/dist/lib/platform/pr-summary.js +45 -8
  25. package/dist/lib/platform/provider-bridge.js +4 -5
  26. package/dist/lib/platform/provider-contract.d.ts +3 -6
  27. package/dist/lib/platform/provider-contract.js +3 -15
  28. package/dist/lib/platform/provider-validation.js +9 -4
  29. package/dist/lib/platform/pull-requests.js +83 -16
  30. package/dist/lib/platform/verification-sync.js +17 -14
  31. package/dist/lib/sandbox/commands/create.js +18 -2
  32. package/dist/lib/sandbox/commands/enter.js +47 -33
  33. package/dist/lib/sandbox/control/client.js +99 -17
  34. package/dist/lib/sandbox/control/executor.js +12 -14
  35. package/dist/lib/sandbox/control/host-recovery.js +218 -0
  36. package/dist/lib/sandbox/control/protocol.js +2 -3
  37. package/dist/lib/sandbox/control/server.js +42 -2
  38. package/dist/lib/sandbox/environment.js +150 -0
  39. package/dist/lib/sandbox/recovery.js +8 -0
  40. package/dist/lib/sandbox/removal.js +0 -1
  41. package/dist/lib/sandbox/workspace-identity.js +0 -14
  42. package/dist/lib/sandbox/workspace-view.js +33 -6
  43. package/dist/lib/task/artifact-command.js +18 -3
  44. package/dist/lib/task/artifact-lifecycle.js +121 -63
  45. package/dist/lib/task/artifact-selection.js +1 -1
  46. package/dist/lib/task/capabilities.js +59 -22
  47. package/dist/lib/task/commands/status.js +0 -27
  48. package/dist/lib/task/commit-identity.js +22 -7
  49. package/dist/lib/task/commit-intent.js +67 -26
  50. package/dist/lib/task/commit-operation.js +19 -8
  51. package/dist/lib/task/completion-facts.js +32 -10
  52. package/dist/lib/task/control-authority.js +2 -6
  53. package/dist/lib/task/current-contract.js +1 -5
  54. package/dist/lib/task/events.js +9 -275
  55. package/dist/lib/task/execution-inputs.js +14 -0
  56. package/dist/lib/task/finalization-envelope.js +4 -0
  57. package/dist/lib/task/finalization.js +43 -86
  58. package/dist/lib/task/frontmatter.js +25 -2
  59. package/dist/lib/task/ledger-intents.js +6 -8
  60. package/dist/lib/task/orchestration.js +11 -10
  61. package/dist/lib/task/platform-operation-journal.js +29 -10
  62. package/dist/lib/task/platform-operation-recovery.js +109 -35
  63. package/dist/lib/task/resolve-ref.js +8 -3
  64. package/dist/lib/task/review-fingerprint.js +4 -26
  65. package/dist/lib/task/verification-engine.js +95 -6
  66. package/dist/lib/task/write.js +0 -11
  67. package/dist/lifecycle-build-manifest.json +3 -3
  68. package/lib/agent-clients/adapter.ts +22 -0
  69. package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
  70. package/lib/agent-clients/adapters/codex.ts +4 -0
  71. package/lib/git/workflow.ts +1 -1
  72. package/lib/internal/cli-route-inventory.ts +0 -2
  73. package/lib/internal/manual-validation.ts +2 -2
  74. package/lib/internal/platform-pr.ts +3 -4
  75. package/lib/internal/sandbox-control.ts +39 -6
  76. package/lib/internal/task-artifact.ts +1 -1
  77. package/lib/internal/task-create.ts +15 -2
  78. package/lib/internal/task-finalization.ts +16 -26
  79. package/lib/internal/task-operation-registry.ts +6 -153
  80. package/lib/internal/task-platform-recovery.ts +9 -4
  81. package/lib/platform/completion-backfill.ts +2 -1
  82. package/lib/platform/context.ts +8 -4
  83. package/lib/platform/github-client.ts +24 -20
  84. package/lib/platform/github-provider.ts +47 -26
  85. package/lib/platform/github-release-notes.ts +5 -3
  86. package/lib/platform/in-label-sync.ts +2 -1
  87. package/lib/platform/issue-comments.ts +14 -9
  88. package/lib/platform/operation-coordinator.ts +1 -1
  89. package/lib/platform/pr-review.ts +2 -1
  90. package/lib/platform/pr-summary.ts +43 -7
  91. package/lib/platform/provider-bridge.ts +10 -9
  92. package/lib/platform/provider-contract.ts +6 -21
  93. package/lib/platform/provider-loader.ts +7 -6
  94. package/lib/platform/provider-validation.ts +30 -8
  95. package/lib/platform/pull-requests.ts +78 -15
  96. package/lib/platform/repository-metadata.ts +3 -2
  97. package/lib/platform/security-alerts.ts +1 -1
  98. package/lib/platform/types.ts +2 -0
  99. package/lib/platform/verification-sync.ts +18 -14
  100. package/lib/sandbox/commands/create.ts +24 -2
  101. package/lib/sandbox/commands/enter.ts +42 -39
  102. package/lib/sandbox/control/client.ts +107 -19
  103. package/lib/sandbox/control/executor.ts +13 -15
  104. package/lib/sandbox/control/host-recovery.ts +195 -0
  105. package/lib/sandbox/control/protocol.ts +4 -5
  106. package/lib/sandbox/control/server.ts +42 -1
  107. package/lib/sandbox/environment.ts +162 -0
  108. package/lib/sandbox/recovery.ts +10 -0
  109. package/lib/sandbox/removal.ts +2 -10
  110. package/lib/sandbox/tool-types.ts +1 -0
  111. package/lib/sandbox/workspace-identity.ts +0 -26
  112. package/lib/sandbox/workspace-view.ts +33 -8
  113. package/lib/task/artifact-command.ts +19 -3
  114. package/lib/task/artifact-lifecycle.ts +102 -53
  115. package/lib/task/artifact-selection.ts +1 -1
  116. package/lib/task/capabilities.ts +52 -26
  117. package/lib/task/commands/status.ts +0 -31
  118. package/lib/task/commit-identity.ts +24 -7
  119. package/lib/task/commit-intent.ts +58 -24
  120. package/lib/task/commit-operation.ts +17 -8
  121. package/lib/task/completion-facts.ts +31 -9
  122. package/lib/task/control-authority.ts +1 -8
  123. package/lib/task/current-contract.ts +1 -6
  124. package/lib/task/events.ts +9 -248
  125. package/lib/task/execution-inputs.ts +25 -0
  126. package/lib/task/finalization-envelope.ts +21 -0
  127. package/lib/task/finalization.ts +40 -82
  128. package/lib/task/frontmatter.ts +26 -2
  129. package/lib/task/ledger-intents.ts +6 -8
  130. package/lib/task/orchestration.ts +11 -17
  131. package/lib/task/platform-operation-journal.ts +47 -13
  132. package/lib/task/platform-operation-recovery.ts +102 -37
  133. package/lib/task/resolve-ref.ts +8 -3
  134. package/lib/task/review-fingerprint.ts +4 -26
  135. package/lib/task/verification-engine.ts +103 -9
  136. package/lib/task/write.ts +0 -19
  137. package/package.json +1 -1
  138. package/templates/.agents/rules/README.en.md +1 -1
  139. package/templates/.agents/rules/README.zh-CN.md +1 -1
  140. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  141. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  142. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  143. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  144. package/templates/.agents/rules/issue-sync.en.md +1 -1
  145. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  146. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  147. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  148. package/templates/.agents/rules/review-handshake.en.md +3 -0
  149. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
  150. package/templates/.agents/rules/task-management.en.md +1 -1
  151. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  152. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  153. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  158. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
  159. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
  160. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
  161. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
  162. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  163. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +27 -10
  164. package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
  165. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
  166. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  167. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  168. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
  169. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
  170. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  171. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  172. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  173. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  174. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  175. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  176. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  177. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  178. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  179. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  180. package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
  181. package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
  182. package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
  183. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
  184. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  189. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  190. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  191. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  192. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  193. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  194. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
  195. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
  196. package/templates/.agents/skills/test/SKILL.en.md +4 -1
  197. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
  198. package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
  199. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
  200. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -2
  201. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -2
  202. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
  203. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
  204. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  205. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  206. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  207. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  208. package/templates/.agents/templates/task.zh-CN.md +0 -12
  209. package/templates/.github/workflows/pr-label.yml +1 -2
  210. package/dist/lib/internal/task-invalidation.js +0 -63
  211. package/dist/lib/task/finalization-handoff.js +0 -93
  212. package/dist/lib/task/intermediate-cleanup.js +0 -327
  213. package/dist/lib/task/invalidation-command.js +0 -88
  214. package/dist/lib/task/invalidation.js +0 -242
  215. package/lib/internal/task-invalidation.ts +0 -41
  216. package/lib/task/finalization-handoff.ts +0 -105
  217. package/lib/task/intermediate-cleanup.ts +0 -393
  218. package/lib/task/invalidation-command.ts +0 -88
  219. package/lib/task/invalidation.ts +0 -313
@@ -1,7 +1,6 @@
1
1
  import { isValidAgentInfraVersion } from '../version.js';
2
2
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
3
3
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, validateLedgerRows } from './ledger.js';
4
- import { parseInvalidationDocument } from './invalidation.js';
5
4
  function validateCurrentTaskContract(content) {
6
5
  let metadata;
7
6
  try {
@@ -30,10 +29,7 @@ function validateCurrentTaskContract(content) {
30
29
  const invalid = validateLedgerRows(ledger.rows);
31
30
  if (invalid)
32
31
  return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalid.code}: ${invalid.message}` };
33
- const invalidation = parseInvalidationDocument(content);
34
- if (!invalidation.ok)
35
- return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalidation.code}: ${invalidation.message}` };
36
- return { ok: true, metadata, ledger, invalidation: invalidation.document };
32
+ return { ok: true, metadata, ledger };
37
33
  }
38
34
  export { validateCurrentTaskContract };
39
35
  //# sourceMappingURL=current-contract.js.map
@@ -19,10 +19,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
19
19
  import { captureTaskWriteMetadata, writeTask } from './write.js';
20
20
  import { validateLocalArtifact } from './local-artifact-finalization.js';
21
21
  import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
22
- import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
23
- import { reconcileTaskInvalidation } from './invalidation-command.js';
24
22
  import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
25
- import { ARTIFACT_FAMILIES, parseQualificationAudit, parseTaskQualification } from './qualification-audit.js';
23
+ import { parseTaskQualification } from './qualification-audit.js';
26
24
  import { getArtifactSchema } from './artifact-schema.js';
27
25
  import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
28
26
  import { inspectReviewIdentity } from './review-identity.js';
@@ -115,7 +113,7 @@ function validateTaskEventRequest(request) {
115
113
  return { code: 'EVENT_PAYLOAD_INVALID', message: 'initiator is invalid' };
116
114
  if (request.requestId !== undefined && (!request.requestId.trim() || /[\r\n]/.test(request.requestId)))
117
115
  return { code: 'EVENT_PAYLOAD_INVALID', message: 'requestId must be a non-empty single line' };
118
- if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'].includes(request.reasonCode))
116
+ if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'manual-review-supplement', 'retry', 'validation-rerun'].includes(request.reasonCode))
119
117
  return { code: 'EVENT_PAYLOAD_INVALID', message: 'reasonCode is invalid' };
120
118
  for (const [name, value] of [['sourceFinding', request.sourceFinding], ['sourceArtifact', request.sourceArtifact], ['sourceSha256', request.sourceSha256]]) {
121
119
  if (value !== undefined && (!value.trim() || /[\r\n]/.test(value)))
@@ -370,235 +368,6 @@ function eventTrigger(request, family) {
370
368
  explicitRequest: request.requestId !== undefined || request.reasonCode !== undefined || request.initiator !== undefined || request.orchestrated === true
371
369
  };
372
370
  }
373
- function receiptGraphHasCycle(receipts) {
374
- const edges = new Map();
375
- for (const receipt of receipts) {
376
- const outputs = edges.get(receipt.input) ?? [];
377
- outputs.push(receipt.output);
378
- edges.set(receipt.input, outputs);
379
- }
380
- const visiting = new Set();
381
- const visited = new Set();
382
- const visit = (node) => {
383
- if (visiting.has(node))
384
- return true;
385
- if (visited.has(node))
386
- return false;
387
- visiting.add(node);
388
- for (const output of edges.get(node) ?? [])
389
- if (visit(output))
390
- return true;
391
- visiting.delete(node);
392
- visited.add(node);
393
- return false;
394
- };
395
- return [...edges.keys()].some(visit);
396
- }
397
- function qualificationInvalidationSeeds(content, taskDir, inventory, baselineDigest) {
398
- const task = parseTaskQualification(content);
399
- if (!task.ok)
400
- return { changed: true, safe: false, seeds: [] };
401
- if (baselineDigest !== undefined) {
402
- if (!/^[a-f0-9]{64}$/u.test(baselineDigest))
403
- return { changed: true, safe: false, seeds: [] };
404
- if (baselineDigest === task.qualification.taskInputDigest)
405
- return { changed: false, safe: true, seeds: [] };
406
- }
407
- const audits = [];
408
- let observedQualification = false;
409
- for (const artifact of inventory) {
410
- let artifactContent;
411
- try {
412
- artifactContent = fs.readFileSync(path.join(taskDir, artifact.name), 'utf8');
413
- }
414
- catch {
415
- return { changed: true, safe: false, seeds: [] };
416
- }
417
- const parsed = parseQualificationAudit(artifactContent);
418
- if (!parsed.ok)
419
- return { changed: true, safe: false, seeds: [] };
420
- if (!parsed.audit.present)
421
- continue;
422
- observedQualification = true;
423
- const snapshot = parsed.audit.snapshot;
424
- if (!snapshot)
425
- return { changed: true, safe: false, seeds: [] };
426
- audits.push({
427
- name: artifact.name,
428
- snapshotTaskDigest: snapshot.taskInputDigest,
429
- snapshotNonConstraintDigest: snapshot.nonConstraintInputDigest,
430
- dependencies: parsed.audit.constraintDependencies,
431
- candidates: parsed.audit.candidateQualifications
432
- });
433
- }
434
- if (!task.qualification.present)
435
- return { changed: false, safe: true, seeds: [] };
436
- const allAuditsPresent = observedQualification && audits.length === inventory.length;
437
- if (!allAuditsPresent)
438
- return { changed: true, safe: false, seeds: [] };
439
- if (baselineDigest === undefined && audits.every((audit) => audit.snapshotTaskDigest === task.qualification.taskInputDigest)) {
440
- return { changed: false, safe: true, seeds: [] };
441
- }
442
- const candidateMap = new Map(task.qualification.candidates.map((candidate) => [candidate.candidateId, candidate]));
443
- const currentConstraints = new Map(task.qualification.constraints.map((constraint) => [constraint.constraintId, constraint.digest]));
444
- const snapshotsMatch = audits.every((audit) => audit.snapshotNonConstraintDigest === task.qualification.nonConstraintInputDigest);
445
- const candidatesMatch = audits.every((audit) => audit.candidates.length === candidateMap.size
446
- && audit.candidates.every((row) => {
447
- const current = candidateMap.get(row.candidateId);
448
- return Boolean(current && row.status === current.status && row.impact === current.impact
449
- && row.evidence === current.evidence && row.constraintIds.join(',') === current.constraintIds.join(','));
450
- }));
451
- const changedConstraints = new Set();
452
- let referencesValid = true;
453
- for (const audit of audits) {
454
- for (const dependency of audit.dependencies) {
455
- const currentDigest = currentConstraints.get(dependency.constraintId);
456
- if (!currentDigest)
457
- referencesValid = false;
458
- else if (currentDigest !== dependency.constraintDigest)
459
- changedConstraints.add(dependency.constraintId);
460
- }
461
- }
462
- const safe = allAuditsPresent && snapshotsMatch && candidatesMatch && referencesValid && changedConstraints.size > 0;
463
- if (!safe)
464
- return { changed: true, safe: false, seeds: [] };
465
- const seeds = new Set();
466
- for (const audit of audits) {
467
- if (audit.dependencies.some((dependency) => changedConstraints.has(dependency.constraintId)))
468
- seeds.add(audit.name);
469
- }
470
- return { changed: true, safe: true, seeds: [...seeds] };
471
- }
472
- function invalidationMutationForCompletion(content, taskDir, family, artifact, timestamp, frontmatter) {
473
- if (!['analyze', 'plan', 'code'].includes(family))
474
- return { mutation: null };
475
- const parsed = parseInvalidationDocument(content);
476
- if (!parsed.ok)
477
- return { error: parsed.message };
478
- const downstream = {
479
- analyze: ['review-analysis', 'plan', 'review-plan', 'code', 'review-code'],
480
- plan: ['review-plan', 'code', 'review-code'],
481
- code: ['review-code']
482
- };
483
- const qualificationFallback = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
484
- const sourceFamily = family;
485
- const sourceArtifactFamily = sourceFamily === 'analyze' ? 'analysis' : sourceFamily;
486
- let receipts = [];
487
- try {
488
- receipts = parseArtifactReceipts(content).rows;
489
- }
490
- catch (error) {
491
- return { error: error instanceof Error ? error.message : String(error) };
492
- }
493
- const inventory = fs.readdirSync(taskDir).flatMap((name) => {
494
- const identity = parseArtifactName(name);
495
- if (!identity || !ARTIFACT_FAMILIES.includes(identity.family))
496
- return [];
497
- try {
498
- const stat = fs.lstatSync(path.join(taskDir, name));
499
- if (!stat.isFile() || stat.isSymbolicLink())
500
- return [];
501
- return [{ family: identity.family, name, round: identity.round, sha256: sha256File(path.join(taskDir, name)) }];
502
- }
503
- catch (error) {
504
- throw new Error(`cannot inspect invalidation artifact '${name}': ${error instanceof Error ? error.message : String(error)}`);
505
- }
506
- });
507
- const nodeMap = new Map(inventory.map((node) => [`${node.family}/${node.name}`, node]));
508
- let graphUsable = receipts.length > 0;
509
- for (const receipt of receipts) {
510
- const output = parseArtifactName(receipt.output);
511
- const input = nodeMap.get(`${parseArtifactName(receipt.input)?.family}/${receipt.input}`);
512
- if (!output || !nodeMap.has(`${output.family}/${receipt.output}`) || !input || input.sha256 !== receipt.inputSha256) {
513
- graphUsable = false;
514
- break;
515
- }
516
- }
517
- if (graphUsable && receiptGraphHasCycle(receipts))
518
- graphUsable = false;
519
- if (inventory.some((node) => node.family !== 'analysis' && node.name !== artifact.name
520
- && !receipts.some((receipt) => receipt.output === node.name)))
521
- graphUsable = false;
522
- const selected = new Set();
523
- if (graphUsable) {
524
- const currentIdentity = parseArtifactName(artifact.name);
525
- const previousName = currentIdentity && currentIdentity.round > 1 ? artifactName(sourceArtifactFamily, currentIdentity.round - 1) : null;
526
- const previous = previousName ? inventory.find((node) => node.family === sourceArtifactFamily && node.name === previousName) : null;
527
- const previousFact = previousName ? parseCompletionFacts(frontmatter.completion_facts).find((fact) => fact.output === previousName && fact.event === `${sourceFamily === 'analyze' ? 'analysis' : sourceFamily}.completed`) : null;
528
- if (!previous || !previousFact || previousFact.outputSha256 !== previous.sha256)
529
- graphUsable = false;
530
- if (graphUsable && previous) {
531
- selected.add(`${previous.family}/${previous.name}`);
532
- let changed = true;
533
- while (changed) {
534
- changed = false;
535
- for (const receipt of receipts) {
536
- const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
537
- if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
538
- selected.add(consumer);
539
- changed = true;
540
- }
541
- }
542
- }
543
- selected.delete(`${previous.family}/${previous.name}`);
544
- }
545
- }
546
- const qualification = qualificationInvalidationSeeds(content, taskDir, inventory, typeof frontmatter.qualification_input_digest === 'string' ? frontmatter.qualification_input_digest : undefined);
547
- let qualificationChange = qualification.changed;
548
- if (qualificationChange && (!graphUsable || !qualification.safe))
549
- graphUsable = false;
550
- if (qualificationChange && graphUsable) {
551
- for (const seed of qualification.seeds)
552
- selected.add(`${parseArtifactName(seed)?.family}/${seed}`);
553
- let changed = true;
554
- while (changed) {
555
- changed = false;
556
- for (const receipt of receipts) {
557
- const consumer = `${parseArtifactName(receipt.output).family}/${receipt.output}`;
558
- if (selected.has(`${parseArtifactName(receipt.input).family}/${receipt.input}`) && !selected.has(consumer)) {
559
- selected.add(consumer);
560
- changed = true;
561
- }
562
- }
563
- }
564
- }
565
- const reasonCode = qualificationChange ? 'qualification-changed' : 'upstream-replaced';
566
- const fallbackFamilies = qualificationChange ? qualificationFallback : downstream[sourceFamily];
567
- const targetNodes = (graphUsable
568
- ? inventory.filter((node) => selected.has(`${node.family}/${node.name}`))
569
- : inventory.filter((node) => fallbackFamilies.includes(node.family)))
570
- .filter((node) => !(node.family === FAMILY[family].artifact && node.name === artifact.name));
571
- const targets = targetNodes.flatMap((node) => {
572
- const shapes = [
573
- { targetKind: 'artifact', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 }
574
- ];
575
- for (const receipt of receipts.filter((candidate) => candidate.output === node.name)) {
576
- shapes.push({ targetKind: 'receipt', targetFamily: node.family, targetArtifact: node.name, targetInput: receipt.input, targetRound: node.round, targetSha256: receipt.inputSha256 });
577
- }
578
- if (node.family.startsWith('review-'))
579
- shapes.push({ targetKind: 'approval', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
580
- if (node.family === 'review-code')
581
- shapes.push({ targetKind: 'reviewed-snapshot', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
582
- return shapes.map((targetShape) => ({ ...targetShape, targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending', reasonCode, updatedAt: timestamp }));
583
- });
584
- if (targets.length === 0)
585
- return { mutation: null };
586
- const source = {
587
- sourceFamily: lifecycleAction(family), sourceArtifact: artifact.name,
588
- sourceRound: parseArtifactName(artifact.name)?.round ?? 1, sourceSha256: sha256File(artifact.path),
589
- createdAt: timestamp, updatedAt: timestamp
590
- };
591
- const operation = createInvalidationOperation(source);
592
- const normalizedTargets = targets.map((target) => ({
593
- ...target, operationId: operation.operationId,
594
- targetId: targetIdFor(operation.operationId, target)
595
- }));
596
- const operationWithTotal = createInvalidationOperation(source, normalizedTargets);
597
- const next = upsertInvalidation(parsed.document, operationWithTotal, normalizedTargets);
598
- if (!next.ok)
599
- return { error: next.message };
600
- return { mutation: next.changed ? invalidationMutation(content, next.document) : null };
601
- }
602
371
  function reworkIntentMutationForCompletion(content, taskDir, family, artifact, timestamp) {
603
372
  const parsed = parseReworkIntentDocument(content);
604
373
  if (!parsed.ok)
@@ -606,23 +375,15 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
606
375
  const hash = sha256File(artifact.path);
607
376
  let next = parsed.intents;
608
377
  if (family === 'analyze' || family === 'plan' || family === 'code') {
609
- const hashes = Object.fromEntries(fs.readdirSync(taskDir).flatMap((name) => {
610
- if (!/^review-(?:analysis|plan|code)(?:-r\d+)?\.md$/.test(name))
611
- return [];
612
- try {
613
- return [[name, sha256File(path.join(taskDir, name))]];
614
- }
615
- catch {
616
- return [];
617
- }
378
+ const hashes = Object.fromEntries(['review-analysis', 'review-plan', 'review-code'].flatMap((reviewFamily) => {
379
+ const inventory = inspectArtifactDirectory(taskDir, reviewFamily);
380
+ return inventory.status === 'ready' ? inventory.completed.map((review) => [review.name, sha256File(review.path)]) : [];
618
381
  }));
619
382
  const action = family === 'analyze' ? 'analysis' : family;
620
383
  let target = action;
621
384
  if (family === 'analyze') {
622
- const previousAnalysis = fs.readdirSync(taskDir)
623
- .map((name) => parseArtifactName(name))
624
- .filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
625
- .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
385
+ const previousAnalysis = inspectArtifactDirectory(taskDir, 'analysis').completed
386
+ .filter((identity) => identity.name !== artifact.name).at(-1);
626
387
  const pathState = previousAnalysis
627
388
  ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
628
389
  : undefined;
@@ -764,23 +525,6 @@ function applyTaskEventUnlocked(request, options = {}) {
764
525
  return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
765
526
  }
766
527
  const initialParts = eventParts(request.event);
767
- if (initialParts.phase === 'started' && !request.dryRun) {
768
- const reconciled = reconcileTaskInvalidation(request.taskRef, { repoRoot: resolved.repoRoot });
769
- if (reconciled.status === 'failed') {
770
- return failed(request, {
771
- code: 'EVENT_TRANSITION_INVALID',
772
- message: `INVALIDATION_RECONCILE_FAILED: ${reconciled.error?.code ?? 'UNKNOWN'}: ${reconciled.error?.message ?? ''}`
773
- }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
774
- }
775
- if (reconciled.changed) {
776
- try {
777
- content = fs.readFileSync(resolved.taskMdPath, 'utf8');
778
- }
779
- catch (error) {
780
- return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
781
- }
782
- }
783
- }
784
528
  let frontmatter;
785
529
  try {
786
530
  frontmatter = parseTypedTaskFrontmatter(content);
@@ -946,8 +690,7 @@ function applyTaskEventUnlocked(request, options = {}) {
946
690
  const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
947
691
  || capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
948
692
  if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
949
- const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
950
- return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
693
+ return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
951
694
  }
952
695
  }
953
696
  const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
@@ -985,7 +728,6 @@ function applyTaskEventUnlocked(request, options = {}) {
985
728
  }
986
729
  let completionReceipts = [];
987
730
  let currentFact = null;
988
- let completionInvalidation = null;
989
731
  let completionRework = null;
990
732
  if (eventIdentity.phase === 'completed' && completedArtifact) {
991
733
  const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter, normalized);
@@ -1006,10 +748,6 @@ function applyTaskEventUnlocked(request, options = {}) {
1006
748
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, completionReceipts[0]?.completedAt ?? metadata.timestamp, frontmatter, artifactContext);
1007
749
  }
1008
750
  try {
1009
- const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
1010
- if ('error' in invalidation)
1011
- return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: invalidation.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
1012
- completionInvalidation = invalidation.mutation;
1013
751
  const rework = reworkIntentMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
1014
752
  if ('error' in rework)
1015
753
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: rework.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
@@ -1104,8 +842,6 @@ function applyTaskEventUnlocked(request, options = {}) {
1104
842
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
1105
843
  }
1106
844
  }
1107
- if (completionInvalidation)
1108
- mutations.push(completionInvalidation);
1109
845
  if (completionRework)
1110
846
  mutations.push(completionRework);
1111
847
  if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
@@ -1123,9 +859,7 @@ function applyTaskEventUnlocked(request, options = {}) {
1123
859
  });
1124
860
  }
1125
861
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
1126
- const sourceCompletion = eventIdentity.phase === 'completed'
1127
- && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
1128
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
862
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
1129
863
  if (result.status === 'failed')
1130
864
  return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1131
865
  if (!normalized.dryRun && orchestrationCompletion) {
@@ -0,0 +1,14 @@
1
+ import { pathIncludes } from './lifecycle-path.js';
2
+ function expectedExecutionInputFamilies(stage, pathState) {
3
+ if (pathState.status !== 'valid')
4
+ return [];
5
+ const input = stage === 'plan' ? 'analysis' : pathIncludes(pathState, 'plan') ? 'plan' : 'analysis';
6
+ const review = input === 'plan' ? 'review-plan' : 'review-analysis';
7
+ return [input, ...(pathIncludes(pathState, review) ? [review] : [])];
8
+ }
9
+ function executionInputsMatchLatest(stage, pathState, latest, captured) {
10
+ const expected = expectedExecutionInputFamilies(stage, pathState);
11
+ return expected.every((family) => Boolean(latest[family] && captured.includes(latest[family])));
12
+ }
13
+ export { expectedExecutionInputFamilies, executionInputsMatchLatest };
14
+ //# sourceMappingURL=execution-inputs.js.map
@@ -0,0 +1,4 @@
1
+ export function serializeTaskFinalizationEnvelope(envelope) {
2
+ return `${JSON.stringify({ version: 2, ...envelope })}\n`;
3
+ }
4
+ //# sourceMappingURL=finalization-envelope.js.map
@@ -10,22 +10,45 @@ import { resourceIdentityEquals } from '../platform/resource-identity.js';
10
10
  import { parseTaskFrontmatter } from './frontmatter.js';
11
11
  import { recoverPlatformOperations } from './platform-operation-recovery.js';
12
12
  import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
13
- import { resolveTaskRef } from './resolve-ref.js';
13
+ import { enumerateAllTaskDirs, resolveTaskRef } from './resolve-ref.js';
14
14
  import { inspectShortIdRegistry } from './short-id.js';
15
15
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
16
16
  import { verifyTaskEvent } from './verification.js';
17
17
  import { projectFinalizationWarning } from './workflow-warning-intents.js';
18
18
  import { mergeOperationWarnings } from './operation-outcome.js';
19
- import { readTaskFinalizationHandoff } from './finalization-handoff.js';
20
19
  import { readSandboxControlManifest } from '../sandbox/control/lifecycle.js';
21
20
  import { validateSandboxControlIdentity } from '../sandbox/control/identity-sentinel.js';
22
21
  const RECEIPT_VERSION = 4;
23
22
  const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
24
- function finalizationRoot(repoRoot) {
25
- return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
23
+ export function resolveTaskFinalizationDirectory(repoRoot, taskId) {
24
+ const tasks = enumerateAllTaskDirs(repoRoot).filter((task) => task.taskId === taskId);
25
+ if (tasks.length === 0)
26
+ return null;
27
+ if (tasks.length !== 1)
28
+ throw new Error(`FINALIZATION_TASK_IDENTITY_AMBIGUOUS: ${taskId}`);
29
+ const task = tasks[0];
30
+ let canonicalDir;
31
+ try {
32
+ canonicalDir = fs.realpathSync.native(task.taskDir);
33
+ }
34
+ catch (error) {
35
+ throw new Error(`FINALIZATION_TASK_IDENTITY_UNAVAILABLE: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
36
+ }
37
+ let frontmatter;
38
+ try {
39
+ frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(canonicalDir, 'task.md'), 'utf8'));
40
+ }
41
+ catch (error) {
42
+ throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
43
+ }
44
+ if (task.taskId !== taskId || frontmatter.id !== taskId) {
45
+ throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: task directory and task.md must identify ${taskId}`);
46
+ }
47
+ return canonicalDir;
26
48
  }
27
49
  function receiptPath(repoRoot, taskId) {
28
- return path.join(finalizationRoot(repoRoot), `${taskId}.json`);
50
+ const taskDir = resolveTaskFinalizationDirectory(repoRoot, taskId);
51
+ return taskDir ? path.join(taskDir, '.task-finalization.json') : null;
29
52
  }
30
53
  function now() {
31
54
  return new Date().toISOString();
@@ -89,7 +112,7 @@ function validWarning(value) {
89
112
  && (warning.status === 'open' || warning.status === 'resolved')
90
113
  && (warning.resolvedAt === null || typeof warning.resolvedAt === 'string');
91
114
  }
92
- function validateReceipt(value, taskId) {
115
+ export function validateTaskFinalizationReceipt(value, taskId) {
93
116
  if (!value || typeof value !== 'object' || Array.isArray(value))
94
117
  throw new Error('receipt must be an object');
95
118
  const receipt = value;
@@ -128,18 +151,18 @@ function validateReceipt(value, taskId) {
128
151
  }
129
152
  function readReceipt(repoRoot, taskId) {
130
153
  const file = receiptPath(repoRoot, taskId);
131
- if (!fs.existsSync(file))
154
+ if (!file || !fs.existsSync(file))
132
155
  return null;
133
156
  const value = JSON.parse(fs.readFileSync(file, 'utf8'));
134
- return validateReceipt(value, taskId);
157
+ return validateTaskFinalizationReceipt(value, taskId);
135
158
  }
136
159
  function readTaskFinalizationReceipt(repoRoot, taskId) {
137
160
  return readReceipt(path.resolve(repoRoot), taskId);
138
161
  }
139
162
  function writeReceipt(repoRoot, receipt) {
140
- const directory = finalizationRoot(repoRoot);
141
- fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
142
163
  const target = receiptPath(repoRoot, receipt.taskId);
164
+ if (!target)
165
+ throw new Error(`TASK_FINALIZATION_TASK_NOT_FOUND: ${receipt.taskId}`);
143
166
  const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
144
167
  try {
145
168
  fs.writeFileSync(temporary, `${JSON.stringify(receipt)}\n`, { mode: 0o600, flag: 'wx' });
@@ -670,7 +693,7 @@ async function prepareUnderLock(request, taskId, options) {
670
693
  let receipt;
671
694
  try {
672
695
  const file = receiptPath(repoRoot, taskId);
673
- const existed = fs.existsSync(file);
696
+ const existed = file !== null && fs.existsSync(file);
674
697
  receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId, options.controlBinding);
675
698
  if (!existed)
676
699
  writeReceipt(repoRoot, receipt);
@@ -678,16 +701,6 @@ async function prepareUnderLock(request, taskId, options) {
678
701
  catch (error) {
679
702
  return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
680
703
  }
681
- if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
682
- || receipt.controlBinding.requestId !== options.controlBinding.requestId)) {
683
- const state = resolveTaskRef(taskId, { repoRoot });
684
- if (!(receipt.lifecycle === 'done' && state.ok && state.state === 'completed')) {
685
- return failed(taskId, {
686
- code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
687
- message: 'canonical receipt is not bound to the current sandbox request', retryable: false
688
- });
689
- }
690
- }
691
704
  const preflightState = resolveTaskRef(taskId, { repoRoot });
692
705
  if (options.preflight && preflightState.ok && preflightState.state === 'active') {
693
706
  try {
@@ -710,7 +723,7 @@ async function prepareUnderLock(request, taskId, options) {
710
723
  if (preflightState.ok && preflightState.state === 'active') {
711
724
  const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
712
725
  if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
713
- return failed(taskId, recovery.error ?? {
726
+ return failed(taskId, recovery.error ? { ...recovery.error, retryable: true } : {
714
727
  code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
715
728
  message: 'Pending platform operations remain unresolved',
716
729
  retryable: true
@@ -720,12 +733,10 @@ async function prepareUnderLock(request, taskId, options) {
720
733
  const taskFrontmatter = parseTaskFrontmatter(taskContent);
721
734
  const issueIdentity = taskIssueIdentity(taskFrontmatter);
722
735
  if (issueIdentity) {
723
- const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
724
736
  let synced;
725
737
  try {
726
738
  synced = await issueSync(taskId, {
727
- agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
728
- ...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
739
+ agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true, dependency: 'required'
729
740
  });
730
741
  }
731
742
  catch (error) {
@@ -733,7 +744,7 @@ async function prepareUnderLock(request, taskId, options) {
733
744
  return failed(taskId, detail);
734
745
  }
735
746
  const issueOperations = synced.operations;
736
- const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
747
+ const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'].includes(operation.name)
737
748
  && operation.status !== 'applied' && operation.status !== 'no-op'
738
749
  && !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
739
750
  if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
@@ -759,7 +770,7 @@ async function prepareUnderLock(request, taskId, options) {
759
770
  retryable: true
760
771
  });
761
772
  }
762
- for (const name of ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])]) {
773
+ for (const name of ['requirements', 'issue-type', 'fields']) {
763
774
  const operation = issueOperations?.find((candidate) => candidate.name === name);
764
775
  if (!operation)
765
776
  return failed(taskId, {
@@ -782,14 +793,6 @@ async function prepareUnderLock(request, taskId, options) {
782
793
  code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
783
794
  });
784
795
  }
785
- else if (name === 'labels:in') {
786
- const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
787
- if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
788
- !== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
789
- return failed(taskId, {
790
- code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
791
- });
792
- }
793
796
  else {
794
797
  const expected = operation.value && typeof operation.value === 'object'
795
798
  ? operation.value
@@ -989,70 +992,24 @@ async function commitPreparedTaskFinalization(request, options) {
989
992
  }
990
993
  }
991
994
  let receipt = readReceipt(repoRoot, resolved.taskId);
992
- if (!receipt && options.controlBinding && options.handoffDirectory && request.handoffSha256) {
993
- try {
994
- const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
995
- receipt = validateReceipt(handoff.receipt, resolved.taskId);
996
- if (receipt.lifecycle !== 'pending')
997
- throw new Error('handoff lifecycle must be pending');
998
- if (inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent) === 'unknown') {
999
- throw new Error('TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE');
1000
- }
1001
- writeReceipt(repoRoot, receipt);
1002
- try {
1003
- handoff.cleanup();
1004
- }
1005
- catch { /* keep the persisted canonical receipt usable */ }
1006
- }
1007
- catch (error) {
1008
- return failed(resolved.taskId, {
1009
- code: 'TASK_FINALIZATION_HANDOFF_INVALID',
1010
- message: error instanceof Error ? error.message : String(error), retryable: false
1011
- });
1012
- }
1013
- }
1014
995
  if (!receipt)
1015
996
  return failed(resolved.taskId, {
1016
997
  code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
1017
998
  });
999
+ if (options.controlBinding && !receipt.controlBinding) {
1000
+ receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
1001
+ }
1018
1002
  if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
1019
1003
  return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
1020
1004
  }
1021
1005
  if (options.controlBinding) {
1022
1006
  const proof = inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent);
1023
- const oldBinding = receipt.controlBinding;
1024
- if (!oldBinding || oldBinding.generation !== options.controlBinding.generation || proof === 'unknown') {
1007
+ if (proof === 'unknown') {
1025
1008
  return failed(resolved.taskId, {
1026
1009
  code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
1027
- message: 'canonical receipt has no valid host recovery proof', retryable: false
1010
+ message: 'task lifecycle progress cannot be verified', retryable: false
1028
1011
  });
1029
1012
  }
1030
- if (oldBinding.requestId !== options.controlBinding.requestId && proof === 'not-started') {
1031
- if (receipt.lifecycle !== 'pending' || !options.handoffDirectory || !request.handoffSha256) {
1032
- return failed(resolved.taskId, {
1033
- code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
1034
- message: 'pending receipt cannot be rebound without a current handoff', retryable: false
1035
- });
1036
- }
1037
- try {
1038
- const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
1039
- const nextReceipt = validateReceipt(handoff.receipt, resolved.taskId);
1040
- if (nextReceipt.receiptId !== receipt.receiptId || nextReceipt.intent !== receipt.intent
1041
- || nextReceipt.lifecycle !== 'pending')
1042
- throw new Error('handoff is not continuous with canonical receipt');
1043
- receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
1044
- try {
1045
- handoff.cleanup();
1046
- }
1047
- catch { /* keep the persisted canonical receipt usable */ }
1048
- }
1049
- catch (error) {
1050
- return failed(resolved.taskId, {
1051
- code: 'TASK_FINALIZATION_HANDOFF_INVALID',
1052
- message: error instanceof Error ? error.message : String(error), retryable: false
1053
- });
1054
- }
1055
- }
1056
1013
  }
1057
1014
  if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
1058
1015
  return failed(resolved.taskId, {