@fitlab-ai/agent-infra 0.9.15 → 0.10.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 (184) hide show
  1. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  3. package/dist/lib/fs/durable-write.js +2 -0
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/manual-validation.js +17 -8
  6. package/dist/lib/internal/task-artifact.js +1 -1
  7. package/dist/lib/internal/task-lifecycle.js +2 -2
  8. package/dist/lib/internal/task-operation-registry.js +0 -1
  9. package/dist/lib/internal/task-review.js +4 -4
  10. package/dist/lib/platform/issue-comments.js +16 -10
  11. package/dist/lib/platform/issues.js +1 -1
  12. package/dist/lib/platform/pr-change-report.js +5 -4
  13. package/dist/lib/platform/pr-summary.js +64 -32
  14. package/dist/lib/platform/pull-requests.js +3 -3
  15. package/dist/lib/platform/task-identities.js +2 -9
  16. package/dist/lib/platform/verification-sync.js +6 -2
  17. package/dist/lib/pr-review/evidence-grading.js +4 -8
  18. package/dist/lib/sandbox/commands/rm.js +21 -2
  19. package/dist/lib/sandbox/control/server.js +29 -9
  20. package/dist/lib/sandbox/control/state.js +7 -1
  21. package/dist/lib/sandbox/control/workflow-executor.js +4 -14
  22. package/dist/lib/sandbox/index.js +1 -2
  23. package/dist/lib/sandbox/removal.js +116 -1011
  24. package/dist/lib/sandbox/workspace-identity.js +17 -1
  25. package/dist/lib/task/artifact-command.js +6 -28
  26. package/dist/lib/task/artifact-operations.js +16 -230
  27. package/dist/lib/task/artifact-recovery.js +430 -0
  28. package/dist/lib/task/artifact-repair-intent.js +73 -39
  29. package/dist/lib/task/commit-operation.js +31 -4
  30. package/dist/lib/task/control-authority.js +38 -7
  31. package/dist/lib/task/control-recovery.js +14 -2
  32. package/dist/lib/task/create-service.js +1 -8
  33. package/dist/lib/task/delegation-receipts.js +64 -3
  34. package/dist/lib/task/events.js +164 -22
  35. package/dist/lib/task/human-override.js +5 -8
  36. package/dist/lib/task/intermediate-cleanup.js +66 -8
  37. package/dist/lib/task/lifecycle-recovery.js +549 -0
  38. package/dist/lib/task/lifecycle.js +12 -3
  39. package/dist/lib/task/local-artifact-finalization.js +148 -123
  40. package/dist/lib/task/manual-validation-receipt.js +5 -1
  41. package/dist/lib/task/orchestration.js +52 -2
  42. package/dist/lib/task/pr-delivery-fact.js +12 -56
  43. package/dist/lib/task/recovery-warning.js +19 -0
  44. package/dist/lib/task/review-command.js +1 -0
  45. package/dist/lib/task/review-finalization.js +104 -77
  46. package/dist/lib/task/short-id.js +15 -7
  47. package/dist/lib/task/task-execution-lock.js +1 -1
  48. package/dist/lib/task/verification-engine.js +3 -14
  49. package/dist/lib/task/workflow-command.js +0 -1
  50. package/dist/lib/version.js +1 -11
  51. package/dist/lifecycle-build-manifest.json +3 -3
  52. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  53. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  54. package/lib/fs/durable-write.ts +1 -0
  55. package/lib/internal/cli-route-inventory.ts +1 -1
  56. package/lib/internal/manual-validation.ts +17 -8
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +4 -3
  59. package/lib/internal/task-operation-registry.ts +0 -1
  60. package/lib/internal/task-review.ts +4 -4
  61. package/lib/platform/issue-comments.ts +18 -10
  62. package/lib/platform/issues.ts +1 -1
  63. package/lib/platform/pr-change-report.ts +7 -5
  64. package/lib/platform/pr-summary.ts +64 -32
  65. package/lib/platform/pull-requests.ts +3 -3
  66. package/lib/platform/task-identities.ts +3 -14
  67. package/lib/platform/verification-sync.ts +6 -2
  68. package/lib/pr-review/evidence-grading.ts +4 -9
  69. package/lib/sandbox/commands/rm.ts +34 -2
  70. package/lib/sandbox/control/protocol.ts +5 -0
  71. package/lib/sandbox/control/server.ts +30 -10
  72. package/lib/sandbox/control/state.ts +9 -1
  73. package/lib/sandbox/control/workflow-executor.ts +4 -14
  74. package/lib/sandbox/index.ts +1 -2
  75. package/lib/sandbox/removal.ts +126 -1128
  76. package/lib/sandbox/workspace-identity.ts +19 -3
  77. package/lib/task/artifact-command.ts +8 -27
  78. package/lib/task/artifact-operations.ts +21 -251
  79. package/lib/task/artifact-recovery.ts +519 -0
  80. package/lib/task/artifact-repair-intent.ts +99 -51
  81. package/lib/task/commit-operation.ts +35 -4
  82. package/lib/task/control-authority.ts +47 -14
  83. package/lib/task/control-recovery.ts +13 -1
  84. package/lib/task/create-service.ts +1 -10
  85. package/lib/task/delegation-receipts.ts +73 -2
  86. package/lib/task/events.ts +187 -23
  87. package/lib/task/human-override.ts +5 -8
  88. package/lib/task/intermediate-cleanup.ts +50 -12
  89. package/lib/task/lifecycle-recovery.ts +691 -0
  90. package/lib/task/lifecycle.ts +13 -4
  91. package/lib/task/local-artifact-finalization.ts +176 -132
  92. package/lib/task/manual-validation-receipt.ts +5 -1
  93. package/lib/task/orchestration.ts +65 -0
  94. package/lib/task/pr-delivery-fact.ts +15 -62
  95. package/lib/task/recovery-warning.ts +21 -0
  96. package/lib/task/review-command.ts +1 -0
  97. package/lib/task/review-finalization.ts +106 -87
  98. package/lib/task/short-id.ts +27 -5
  99. package/lib/task/task-execution-lock.ts +1 -1
  100. package/lib/task/verification-engine.ts +3 -17
  101. package/lib/task/verification-types.ts +0 -2
  102. package/lib/task/workflow-command.ts +0 -1
  103. package/lib/version.ts +1 -15
  104. package/package.json +1 -1
  105. package/templates/.agents/rules/create-issue.en.md +1 -1
  106. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  107. package/templates/.agents/rules/issue-sync.en.md +4 -2
  108. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  109. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  110. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  111. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  112. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  113. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  114. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  115. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  116. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  117. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  118. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  119. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  120. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  121. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  122. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  123. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  124. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  125. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  126. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  127. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  130. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  131. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  132. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  133. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  134. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  135. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  136. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  137. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  138. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  139. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  140. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  141. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  142. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  143. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  144. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  145. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  146. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  147. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  148. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  149. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  150. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  151. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  152. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  153. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  156. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  157. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  158. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  159. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  160. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  161. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  162. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  163. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  164. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  165. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  166. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  167. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  168. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  169. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  170. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  171. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  172. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  173. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  174. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  175. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  176. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  177. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  178. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  179. package/templates/.agents/templates/task.en.md +1 -1
  180. package/templates/.agents/templates/task.zh-CN.md +1 -1
  181. package/templates/.claude/commands/import-issue.en.md +1 -1
  182. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  183. package/templates/.claude/commands/restore-task.en.md +1 -1
  184. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
@@ -2,12 +2,13 @@ import fs from 'node:fs';
2
2
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
3
3
  import { scanVisibleMarkdown } from './markdown.js';
4
4
  import { parseArtifactName } from './artifact-name.js';
5
- import { hasOpenArtifactRound, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
5
+ import { hasOpenArtifactRound, parseCodePlanInputReference, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
7
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
8
8
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
9
9
  import { getArtifactSchema } from './artifact-schema.js';
10
- import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
10
+ import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
11
+ import { beginArtifactRecovery, commitArtifactRecovery, consumeArtifactRecovery, prepareArtifactRecoveryCommit, recordArtifactRecoveryPassed, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
11
12
  import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
12
13
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
13
14
  analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
@@ -35,14 +36,21 @@ function validFinalizerAuthority(authority, request, taskId) {
35
36
  && authority.requestId.length > 0
36
37
  && authority.operationId.length > 0;
37
38
  }
38
- function consumeLocalArtifactFinalizationIntent(repoRoot, intent) {
39
+ function recoveryInfo(context) {
40
+ return {
41
+ recoveryId: context.recoveryId,
42
+ candidatePath: context.stagingPath,
43
+ baselineSha256: context.baselineSha256,
44
+ baselineSemanticDigest: context.baselineSemanticDigest
45
+ };
46
+ }
47
+ function consumeLocalArtifactFinalizationIntent(repoRoot, intent, options = {}) {
39
48
  if (intent.state === 'consumed')
40
49
  return intent;
41
- if (intent.state !== 'passed')
42
- throw new Error('LOCAL_FINALIZATION_INTENT_INVALID: only passed provenance can be consumed');
43
- const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
44
- writeArtifactRepairIntent(repoRoot, consumed, { expected: intent });
45
- return consumed;
50
+ const resolved = resolveTaskRef(intent.taskId, { repoRoot });
51
+ if (!resolved.ok)
52
+ throw new Error(resolved.message);
53
+ return consumeArtifactRecovery(recoveryContextFromIntent(repoRoot, resolved.taskDir, intent), options);
46
54
  }
47
55
  function localDiagnosticCode(code) {
48
56
  const map = {
@@ -54,78 +62,56 @@ function localDiagnosticCode(code) {
54
62
  return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
55
63
  }
56
64
  function localDiagnostic(item) {
57
- const code = localDiagnosticCode(item.code);
58
- if (!code)
59
- return null;
60
- return {
61
- code,
62
- message: item.message,
63
- repairable: item.repairable,
64
- line: item.line,
65
- ...(item.operation?.kind === 'replace-line'
66
- ? { from: item.operation.from, to: item.operation.to, operation: 'replace-line' }
67
- : {})
68
- };
65
+ return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
69
66
  }
70
67
  function isStatusPattern(pattern) {
71
68
  return pattern === '^\\$ ';
72
69
  }
73
70
  function validateLocalArtifact(content, options) {
74
71
  const schema = getArtifactSchema(options.family);
75
- const patterns = schema.requiredPatterns;
76
72
  const diagnostics = [];
77
73
  const scanned = scanVisibleMarkdown(content);
78
74
  const structure = inspectArtifactStructure(content, schema);
79
- for (const item of structure.diagnostics) {
80
- const mapped = localDiagnostic(item);
81
- if (mapped)
82
- diagnostics.push(mapped);
83
- }
75
+ for (const item of structure.diagnostics)
76
+ diagnostics.push(localDiagnostic(item));
84
77
  const statusSection = schema.sections.find((section) => section.id === 'state-check');
85
78
  if (statusSection) {
86
79
  const statusHeading = scanned.headings.find((heading) => (heading.level === 2 && [statusSection.headings.zh, statusSection.headings.en].some((name) => (heading.text === name || heading.text === `${name}:` || heading.text === `${name}:`))));
87
80
  if (statusHeading) {
88
81
  const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
89
82
  const body = content.slice(statusHeading.end, next?.start ?? content.length);
90
- for (const pattern of patterns.filter(isStatusPattern)) {
83
+ for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
91
84
  if (!new RegExp(pattern, 'm').test(body)) {
92
- diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, repairable: false, line: content.slice(0, statusHeading.start).split('\n').length });
85
+ diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, line: content.slice(0, statusHeading.start).split('\n').length });
93
86
  }
94
87
  }
95
88
  }
96
89
  }
97
90
  const patternInspection = inspectArtifactPatterns(content, {
98
91
  ...schema,
99
- requiredPatterns: patterns.filter((item) => !isStatusPattern(item))
92
+ requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
100
93
  });
101
- for (const item of patternInspection.diagnostics) {
102
- diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, repairable: false, line: null });
103
- }
94
+ for (const item of patternInspection.diagnostics)
95
+ diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
104
96
  const decisionDetails = inspectDecisionDetailDuplicates(content);
105
- if (!decisionDetails.ok) {
106
- diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, repairable: false, line: null });
107
- }
97
+ if (!decisionDetails.ok)
98
+ diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
108
99
  if (options.taskContent !== undefined) {
109
100
  const expected = expectedQualificationRelations(options.taskContent, options.family);
110
- if (!expected.ok) {
111
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, repairable: false, line: null });
112
- }
101
+ if (!expected.ok)
102
+ diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
113
103
  const qualification = validateQualificationAudit(options.taskContent, content, {
114
- family: options.family === 'analysis' ? 'analysis' : options.family === 'plan' ? 'plan' : 'code',
104
+ family: options.family,
115
105
  artifact: options.artifact,
116
106
  expectedUpstreamRelations: expected.ok ? expected.relations : undefined
117
107
  });
118
- if (!qualification.ok) {
119
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, repairable: false, line: null });
120
- }
108
+ if (!qualification.ok)
109
+ diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
121
110
  }
122
- const repairable = structure.repair !== null && diagnostics.length === 1 && diagnostics[0]?.repairable === true;
123
- const semanticDigestValue = canonicalSemanticDigest(content, structure.repair);
124
111
  return {
125
112
  ok: diagnostics.length === 0,
126
113
  family: options.family,
127
- semanticDigest: semanticDigestValue,
128
- repairable,
114
+ semanticDigest: canonicalSemanticDigest(content),
129
115
  diagnostics
130
116
  };
131
117
  }
@@ -135,13 +121,24 @@ function failedFinalization(request, error, extra = {}) {
135
121
  taskId: null, taskDir: null,
136
122
  family: request.family, artifact: request.artifact,
137
123
  artifactSha256: null, semanticDigest: null,
138
- repairable: false, diagnostics: [], error, ...extra
124
+ diagnostics: [], error, ...extra
125
+ };
126
+ }
127
+ function tupleFor(request, taskId, authority) {
128
+ const round = parseArtifactName(request.artifact)?.round ?? 0;
129
+ return {
130
+ taskId,
131
+ family: request.family,
132
+ artifact: request.artifact,
133
+ round,
134
+ requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
135
+ phase: authority?.phase ?? 'artifact.finalize-local',
136
+ authorityDigest: authority ? authorityDigest(authority) : null
139
137
  };
140
138
  }
141
- /** Read and validate once; callers publish this content before committing provenance. */
142
139
  function prepareLocalArtifact(request, candidate, authority) {
143
- const failed = (code, message) => ({
144
- result: failedFinalization(request, { code, message }), content: candidate ?? ''
140
+ const failed = (code, message, extra = {}) => ({
141
+ result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
145
142
  });
146
143
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: request.repoRoot });
147
144
  if (!resolved.ok)
@@ -149,114 +146,142 @@ function prepareLocalArtifact(request, candidate, authority) {
149
146
  const parsed = parseArtifactName(request.artifact);
150
147
  if (!parsed || parsed.family !== request.family)
151
148
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
152
- let content;
149
+ if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
150
+ return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
151
+ }
153
152
  let taskContent;
153
+ let content;
154
+ let recovery;
154
155
  try {
155
156
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
156
- if (candidate !== undefined) {
157
- const error = validateArtifactPublication(resolved.taskDir, request.family, request.artifact);
158
- if (error)
159
- return failed(error.code, error.message);
160
- if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
161
- return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
162
- }
163
- content = candidate;
157
+ if (request.recoveryId) {
158
+ const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
159
+ if (!intent || intent.recoveryOperationId !== request.recoveryId)
160
+ return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'recovery id does not match the artifact journal');
161
+ if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state))
162
+ return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
163
+ recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
164
+ content = fs.existsSync(recovery.stagingPath)
165
+ ? fs.readFileSync(recovery.stagingPath, 'utf8')
166
+ : fs.readFileSync(recovery.formalPath, 'utf8');
164
167
  }
165
168
  else {
166
169
  const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
167
170
  if (!validated.ok)
168
171
  return failed(validated.error.code, validated.error.message);
169
- content = fs.readFileSync(validated.artifact.path, 'utf8');
172
+ content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
170
173
  }
171
174
  }
172
175
  catch (error) {
173
176
  return failed('ARTIFACT_NOT_READABLE', String(error));
174
177
  }
178
+ if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
179
+ return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
180
+ }
175
181
  const validation = validateLocalArtifact(content, {
176
182
  family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
177
183
  });
178
184
  const artifactSha256 = sha256Content(content);
179
- const { semanticDigest, repairable, diagnostics } = validation;
180
185
  const result = failedFinalization(request, {
181
- code: 'LOCAL_ARTIFACT_INVALID', message: diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
182
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest, repairable, diagnostics });
183
- const preparedBase = { result, content, repoRoot: resolved.repoRoot };
184
- if (authority !== undefined && !validFinalizerAuthority(authority, request, resolved.taskId)) {
186
+ code: 'LOCAL_ARTIFACT_INVALID',
187
+ message: validation.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
188
+ }, {
189
+ taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
190
+ semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
191
+ });
192
+ if (request.recoveryId) {
193
+ if (!validation.ok)
194
+ return { result, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
185
195
  return {
186
- ...preparedBase,
187
- result: { ...result, repairable: false, error: {
188
- code: 'LOCAL_EXECUTION_AUTHORITY_INVALID',
189
- message: 'finalizer authority does not match the artifact tuple'
190
- }, diagnostics: [] }
196
+ result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(recovery) },
197
+ content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
191
198
  };
192
199
  }
193
- const reject = (code, message) => ({
194
- ...prepared, result: { ...result, repairable: false, error: { code, message },
195
- diagnostics: [{ code, message, repairable: false, line: null }] }
196
- });
197
- let intent;
198
- try {
199
- intent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
200
- }
201
- catch (error) {
202
- return { ...preparedBase, result: { ...result, repairable: false, diagnostics: [],
203
- error: { code: 'LOCAL_FINALIZATION_INTENT_INVALID', message: String(error) } } };
204
- }
205
- const prepared = { ...preparedBase, expectedIntent: intent };
206
- if (intent && intent.recoveryOperationId !== null
207
- && (!authority || intent.recoveryOperationId !== authority.operationId)) {
208
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'local repair provenance is owned by another recovery operation');
209
- }
210
- if (repairable) {
211
- if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== semanticDigest)) {
212
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'a different local repair baseline is already recorded for this artifact');
200
+ const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
201
+ if (existing?.state === 'passed' || existing?.state === 'consumed') {
202
+ if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
203
+ return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
213
204
  }
214
- if (intent && !authority)
215
- return prepared;
205
+ const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
206
+ return {
207
+ result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
208
+ content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
209
+ };
216
210
  }
217
- else {
218
- if (!validation.ok)
219
- return prepared;
220
- if (intent?.state === 'awaiting-repair' && intent.baselineSemanticDigest !== semanticDigest) {
221
- return reject('LOCAL_REPAIR_BASELINE_MISMATCH', 'the repaired artifact semantic digest does not match the recorded repair baseline');
222
- }
223
- if ((intent?.state === 'passed' || intent?.state === 'consumed')
224
- && (intent.artifactSha256 !== artifactSha256 || intent.semanticDigest !== semanticDigest)) {
225
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'the artifact changed after its finalization provenance was recorded');
211
+ try {
212
+ if (validation.ok) {
213
+ const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
214
+ repoRoot: resolved.repoRoot,
215
+ taskDir: resolved.taskDir,
216
+ expectedFinalSha256: artifactSha256,
217
+ expectedFinalSemanticDigest: validation.semanticDigest,
218
+ ...(authority ? { recoveryId: authority.operationId } : {}),
219
+ lockAlreadyHeld: request.lockAlreadyHeld
220
+ });
221
+ recovery = context;
222
+ return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
226
223
  }
227
- prepared.result = { ...result, status: 'passed', error: null };
228
- if (intent?.state === 'consumed')
229
- return prepared;
224
+ const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
225
+ repoRoot: resolved.repoRoot,
226
+ taskDir: resolved.taskDir,
227
+ ...(authority ? { recoveryId: authority.operationId } : {}),
228
+ lockAlreadyHeld: request.lockAlreadyHeld
229
+ });
230
+ recovery = context;
231
+ return {
232
+ result: { ...result, recovery: recoveryInfo(context) },
233
+ content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
234
+ };
235
+ }
236
+ catch (error) {
237
+ return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
230
238
  }
231
- const timestamp = Date.now();
232
- return { ...prepared, provenance: {
233
- version: 2, taskId: resolved.taskId, family: request.family, artifact: request.artifact,
234
- state: repairable ? 'awaiting-repair' : 'passed',
235
- baselineSemanticDigest: repairable ? semanticDigest : intent?.baselineSemanticDigest ?? null,
236
- artifactSha256, semanticDigest,
237
- recoveryOperationId: authority?.operationId ?? intent?.recoveryOperationId ?? null,
238
- phase: authority?.phase ?? intent?.phase ?? null,
239
- authorityDigest: authority ? authorityDigest(authority) : intent?.authorityDigest ?? null,
240
- requestId: authority?.lifecycleRequestId ?? intent?.requestId ?? `local-finalize:${resolved.taskId}`,
241
- createdAt: intent?.createdAt ?? timestamp,
242
- updatedAt: timestamp
243
- } };
244
239
  }
245
240
  function commitLocalArtifactProvenance(prepared) {
241
+ if (!prepared.recovery || !prepared.repoRoot || prepared.result.status === 'failed' && !prepared.result.recovery)
242
+ return prepared.result;
243
+ if (prepared.result.status === 'failed')
244
+ return prepared.result;
246
245
  try {
247
- if (prepared.provenance) {
248
- writeArtifactRepairIntent(prepared.repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
246
+ const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
247
+ if (!intent)
248
+ throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
249
+ let committed;
250
+ if (intent.state === 'passed' || intent.state === 'consumed') {
251
+ return {
252
+ ...prepared.result,
253
+ artifactSha256: intent.finalArtifactSha256,
254
+ semanticDigest: intent.finalSemanticDigest,
255
+ error: null
256
+ };
249
257
  }
250
- return prepared.result;
258
+ else if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
259
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
260
+ }
261
+ else {
262
+ const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
263
+ prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
264
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
265
+ }
266
+ if (committed.state !== 'passed')
267
+ throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
268
+ return {
269
+ ...prepared.result,
270
+ status: 'passed',
271
+ artifactSha256: committed.finalArtifactSha256,
272
+ semanticDigest: committed.finalSemanticDigest,
273
+ error: null
274
+ };
251
275
  }
252
276
  catch (error) {
253
277
  return { ...prepared.result, status: 'failed', error: {
254
- code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED', message: String(error)
278
+ code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
255
279
  } };
256
280
  }
257
281
  }
258
282
  function finalizeLocalArtifact(request, authority, options = {}) {
259
- const result = commitLocalArtifactProvenance(prepareLocalArtifact(request, undefined, authority));
283
+ const prepared = prepareLocalArtifact(request, undefined, authority);
284
+ const result = commitLocalArtifactProvenance(prepared);
260
285
  if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
261
286
  try {
262
287
  consumeLifecycleRecoveryAttestation(authority);
@@ -270,5 +295,5 @@ function finalizeLocalArtifact(request, authority, options = {}) {
270
295
  }
271
296
  return result;
272
297
  }
273
- export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRepairIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
298
+ export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
274
299
  //# sourceMappingURL=local-artifact-finalization.js.map
@@ -28,7 +28,11 @@ function manualValidationReceiptDigest(receipt) {
28
28
  }
29
29
  function manualValidationFinalSummaryDigest(body) {
30
30
  const heading = /^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.exec(body);
31
- const projection = heading ? body.slice(heading.index).replace(/\s+$/u, '') : body;
31
+ const afterHeading = heading ? body.slice(heading.index + heading[0].length) : '';
32
+ const nextSection = heading ? /^#{1,3}\s/mu.exec(afterHeading) : null;
33
+ const projection = heading
34
+ ? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
35
+ : body;
32
36
  const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
33
37
  return createHash('sha256').update(preimage, 'utf8').digest('hex');
34
38
  }
@@ -7,7 +7,7 @@ import { validateCurrentTaskContract } from './current-contract.js';
7
7
  import { parseReviewSummary } from './review-artifacts.js';
8
8
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
9
  import { resolveTaskRef } from './resolve-ref.js';
10
- import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
10
+ import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
12
  import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
13
13
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
@@ -21,6 +21,7 @@ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './del
21
21
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
23
  import { invalidationBlocks } from './invalidation.js';
24
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
24
25
  function supportsLifecycleDelegation(client) {
25
26
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
26
27
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -1090,6 +1091,55 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1090
1091
  return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1091
1092
  }
1092
1093
  }
1094
+ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
1095
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1096
+ if (!resolved.ok)
1097
+ return failed(resolved.code, resolved.message, resolved.taskId);
1098
+ const run = readRun(resolved.taskDir, options);
1099
+ if (!run)
1100
+ return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
1101
+ const matches = [
1102
+ ...(run.pendingDelegation ? [run.pendingDelegation] : []),
1103
+ ...run.receipts.filter((receipt) => receipt.id === event.receiptId)
1104
+ ];
1105
+ const receipt = matches.find((candidate) => candidate.id === event.receiptId);
1106
+ if (!receipt)
1107
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
1108
+ if (receipt.taskId !== resolved.taskId
1109
+ || receipt.stage !== event.stage
1110
+ || receipt.round !== event.round
1111
+ || receipt.artifact !== event.artifact
1112
+ || receipt.childId !== event.childId
1113
+ || normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)) {
1114
+ return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
1115
+ }
1116
+ if (receipt.status === 'aborted' && run.pendingDelegation === null) {
1117
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1118
+ }
1119
+ if (run.pendingDelegation?.id !== receipt.id || receipt.status !== 'activated') {
1120
+ return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'recovery requires one matching activated pending delegation', resolved.taskId);
1121
+ }
1122
+ if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
1123
+ return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
1124
+ }
1125
+ const aborted = abortActivatedDelegation(receipt, {
1126
+ childId: event.childId,
1127
+ stopRevision: event.stopRevision,
1128
+ consumer: event.consumer,
1129
+ consumedAt: event.consumedAt
1130
+ });
1131
+ if (!aborted.ok)
1132
+ return failed(aborted.code, aborted.message, resolved.taskId);
1133
+ const updated = withUpdatedRun(run, {
1134
+ status: 'running',
1135
+ pause: null,
1136
+ nextStage: null,
1137
+ pendingDelegation: null,
1138
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
1139
+ }, options.now);
1140
+ saveRun(resolved.taskDir, updated);
1141
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1142
+ }
1093
1143
  function completeOrchestrationStage(taskRef, event, options = {}) {
1094
1144
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1095
1145
  if (!resolved.ok)
@@ -1204,5 +1254,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1204
1254
  saveRun(resolved.taskDir, updated);
1205
1255
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1206
1256
  }
1207
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, statusOrchestration, };
1257
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegationUnderLock, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, statusOrchestration, };
1208
1258
  //# sourceMappingURL=orchestration.js.map
@@ -1,4 +1,3 @@
1
- import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.js';
2
1
  import { isResourceIdentity, parseResourceIdentity, resourceIdentityNumber, serializeResourceIdentity } from '../platform/resource-identity.js';
3
2
  const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
4
3
  function factError(message) {
@@ -28,11 +27,6 @@ function timestamp(value, label) {
28
27
  throw factError(`${label} must be a valid timestamp`);
29
28
  return new Date(result).toISOString();
30
29
  }
31
- function positiveNumber(value, label) {
32
- if (!Number.isSafeInteger(value) || value <= 0)
33
- throw factError(`${label} must be a positive integer`);
34
- return value;
35
- }
36
30
  function parseRef(value, label) {
37
31
  if (!isRecord(value))
38
32
  throw factError(`${label} must be an object`);
@@ -51,36 +45,17 @@ function canonicalIdentity(value) {
51
45
  base: parseRef(value.base, 'identity.base')
52
46
  };
53
47
  }
54
- function decodeLegacyIdentity(value) {
55
- if (!isRecord(value))
56
- throw factError('legacy identity must be an object');
57
- // TODO(compat): Remove the v1 identity decoder before the first stable v1.0.0 release.
58
- exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'legacy identity');
59
- const legacy = {
60
- number: positiveNumber(value.number, 'legacy identity.number'),
61
- repository: text(value.repository, 'identity.repository'),
62
- nodeId: text(value.nodeId, 'legacy identity.nodeId'),
63
- url: text(value.url, 'identity.url'),
64
- head: parseRef(value.head, 'identity.head'),
65
- base: parseRef(value.base, 'identity.base')
66
- };
67
- return {
68
- resource: { kind: 'number', value: legacy.number },
69
- repository: legacy.repository,
70
- url: legacy.url,
71
- head: legacy.head,
72
- base: legacy.base
73
- };
74
- }
75
48
  function provenanceForSource(source) { return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source; }
76
- function parseFact(value, runtimeVersion = VERSION) {
49
+ function legacyFactError() {
50
+ return Object.assign(new Error('PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: v1 PR delivery fact is unsupported; use the previous tool version to view this task or migrate its fact to version 2 before using this version'), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
51
+ }
52
+ function parseFact(value) {
77
53
  if (!isRecord(value))
78
54
  throw factError('fact must be an object');
79
- if ((value.version !== 1 && value.version !== 2) || typeof value.state !== 'string')
55
+ if (value.version === 1)
56
+ throw legacyFactError();
57
+ if (value.version !== 2 || typeof value.state !== 'string')
80
58
  throw factError('version or state is invalid');
81
- if (value.version === 1 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
82
- throw legacyCompatibilityError('v1 PR delivery fact', runtimeVersion);
83
- }
84
59
  if (value.state === 'unbound') {
85
60
  exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
86
61
  if (value.reason !== 'initial')
@@ -95,25 +70,6 @@ function parseFact(value, runtimeVersion = VERSION) {
95
70
  }
96
71
  if (value.state !== 'bound')
97
72
  throw factError('state is invalid');
98
- if (value.version === 1) {
99
- // TODO(compat): Remove this v1-to-v2 shape conversion before the first stable v1.0.0 release.
100
- exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'legacy bound fact');
101
- if (!isRecord(value.binding) || !isRecord(value.provenance))
102
- throw factError('legacy binding or provenance is invalid');
103
- exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'legacy binding');
104
- const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'legacy binding.issueNumber');
105
- return parseFact({
106
- version: 2,
107
- state: 'bound',
108
- identity: decodeLegacyIdentity(value.identity),
109
- binding: {
110
- status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'),
111
- issueIdentity: issueNumber === null ? null : { kind: 'number', value: issueNumber }, remoteState: value.binding.remoteState,
112
- mergedAt: value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt'), mergeCommitSha: nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha')
113
- },
114
- provenance: value.provenance
115
- }, runtimeVersion);
116
- }
117
73
  exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
118
74
  if (!isRecord(value.binding) || !isRecord(value.provenance))
119
75
  throw factError('binding or provenance is invalid');
@@ -142,7 +98,7 @@ function parseFact(value, runtimeVersion = VERSION) {
142
98
  provenance: { establishedBy: value.provenance.establishedBy }
143
99
  };
144
100
  }
145
- function decodePrDeliveryFact(value, runtimeVersion = VERSION) {
101
+ function decodePrDeliveryFact(value, _runtimeVersion) {
146
102
  if (typeof value === 'string') {
147
103
  let decoded;
148
104
  try {
@@ -151,16 +107,16 @@ function decodePrDeliveryFact(value, runtimeVersion = VERSION) {
151
107
  catch {
152
108
  throw factError('value is not valid JSON');
153
109
  }
154
- return parseFact(decoded, runtimeVersion);
110
+ return parseFact(decoded);
155
111
  }
156
- return parseFact(value, runtimeVersion);
112
+ return parseFact(value);
157
113
  }
158
114
  function encodePrDeliveryFact(fact) { return JSON.stringify(parseFact(fact)); }
159
- function readPrDeliveryFact(metadata, runtimeVersion = VERSION) {
115
+ function readPrDeliveryFact(metadata, _runtimeVersion) {
160
116
  if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '')
161
117
  return { status: 'missing', fact: null };
162
118
  try {
163
- return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY], runtimeVersion) };
119
+ return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY]) };
164
120
  }
165
121
  catch (error) {
166
122
  return { status: 'invalid', fact: null, error: asFactError(error) };
@@ -0,0 +1,19 @@
1
+ function warningText(value) {
2
+ return typeof value === 'string' && value.length > 0 && value.trim() === value && !/[\r\n]/u.test(value);
3
+ }
4
+ export function isRecoveryWarning(value) {
5
+ if (!value || typeof value !== 'object' || Array.isArray(value))
6
+ return false;
7
+ const warning = value;
8
+ return Object.keys(warning).sort().join(',') === 'action,code,message'
9
+ && warningText(warning.code)
10
+ && warningText(warning.message)
11
+ && warningText(warning.action);
12
+ }
13
+ export function sameRecoveryWarning(left, right) {
14
+ return isRecoveryWarning(left) && isRecoveryWarning(right)
15
+ && left.code === right.code
16
+ && left.message === right.message
17
+ && left.action === right.action;
18
+ }
19
+ //# sourceMappingURL=recovery-warning.js.map
@@ -4,6 +4,7 @@ export function parseReviewCommand(args) {
4
4
  const values = {};
5
5
  const flags = {
6
6
  '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
7
+ '--recovery-id': 'recoveryId',
7
8
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
8
9
  };
9
10
  for (let index = 2; index < args.length; index += 1) {