@fitlab-ai/agent-infra 0.11.2 → 0.11.4

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 (162) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +7 -2
  4. package/dist/lib/agent-clients/adapter.js +3 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  8. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  9. package/dist/lib/internal/cli-route-inventory.js +1 -1
  10. package/dist/lib/internal/manual-validation.js +22 -27
  11. package/dist/lib/internal/sandbox-control.js +1 -43
  12. package/dist/lib/internal/task-artifact.js +1 -1
  13. package/dist/lib/internal/task-finalization.js +41 -1
  14. package/dist/lib/internal/task-ledger.js +13 -5
  15. package/dist/lib/internal/task-lifecycle.js +1 -1
  16. package/dist/lib/internal/task-operation-registry.js +7 -2
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/issue-comments.js +2 -31
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/sandbox/control/client.js +3 -44
  21. package/dist/lib/sandbox/control/executor.js +14 -79
  22. package/dist/lib/sandbox/control/protocol.js +5 -64
  23. package/dist/lib/sandbox/control/server.js +49 -343
  24. package/dist/lib/sandbox/control/state.js +1 -7
  25. package/dist/lib/sandbox/control/task-view.js +1 -2
  26. package/dist/lib/sandbox/task-cleanup.js +13 -20
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +1 -26
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +1 -0
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +37 -38
  36. package/dist/lib/task/control-recovery.js +3 -42
  37. package/dist/lib/task/decision-intents.js +12 -0
  38. package/dist/lib/task/delegation-receipts.js +2 -2
  39. package/dist/lib/task/delivery.js +1 -43
  40. package/dist/lib/task/events.js +80 -64
  41. package/dist/lib/task/finalization-artifacts.js +1 -3
  42. package/dist/lib/task/finalization-state.js +1 -1
  43. package/dist/lib/task/finalization.js +319 -234
  44. package/dist/lib/task/ledger-intents.js +157 -15
  45. package/dist/lib/task/lifecycle-path.js +75 -0
  46. package/dist/lib/task/lifecycle.js +11 -1
  47. package/dist/lib/task/local-artifact-finalization.js +1 -20
  48. package/dist/lib/task/manual-validation-receipt.js +8 -3
  49. package/dist/lib/task/orchestration.js +29 -13
  50. package/dist/lib/task/review-finalization.js +3 -16
  51. package/dist/lib/task/rework-intent.js +70 -4
  52. package/dist/lib/task/verification-engine.js +20 -30
  53. package/dist/lib/task/verification.js +1 -0
  54. package/dist/lib/task/workflow-warning-intents.js +21 -6
  55. package/dist/lib/task/workspace-snapshot.js +7 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/agent-clients/adapter.ts +46 -0
  58. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  60. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  61. package/lib/agent-clients/adapters/codex.ts +10 -0
  62. package/lib/internal/cli-route-inventory.ts +1 -1
  63. package/lib/internal/manual-validation.ts +22 -26
  64. package/lib/internal/sandbox-control.ts +0 -42
  65. package/lib/internal/task-artifact.ts +1 -1
  66. package/lib/internal/task-finalization.ts +40 -1
  67. package/lib/internal/task-ledger.ts +12 -5
  68. package/lib/internal/task-lifecycle.ts +2 -2
  69. package/lib/internal/task-operation-registry.ts +8 -4
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/issue-comments.ts +1 -35
  72. package/lib/platform/pr-summary.ts +10 -2
  73. package/lib/sandbox/control/client.ts +5 -68
  74. package/lib/sandbox/control/executor.ts +14 -81
  75. package/lib/sandbox/control/protocol.ts +7 -83
  76. package/lib/sandbox/control/server.ts +47 -362
  77. package/lib/sandbox/control/state.ts +1 -9
  78. package/lib/sandbox/control/task-view.ts +1 -2
  79. package/lib/sandbox/task-cleanup.ts +12 -20
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +1 -26
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +1 -0
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +41 -48
  89. package/lib/task/control-recovery.ts +3 -43
  90. package/lib/task/decision-intents.ts +9 -0
  91. package/lib/task/delegation-receipts.ts +2 -2
  92. package/lib/task/delivery.ts +1 -40
  93. package/lib/task/events.ts +82 -64
  94. package/lib/task/finalization-artifacts.ts +1 -3
  95. package/lib/task/finalization-state.ts +1 -1
  96. package/lib/task/finalization.ts +310 -216
  97. package/lib/task/ledger-intents.ts +155 -15
  98. package/lib/task/lifecycle-path.ts +98 -0
  99. package/lib/task/lifecycle.ts +15 -2
  100. package/lib/task/local-artifact-finalization.ts +2 -25
  101. package/lib/task/manual-validation-receipt.ts +8 -2
  102. package/lib/task/orchestration.ts +56 -27
  103. package/lib/task/review-finalization.ts +3 -16
  104. package/lib/task/rework-intent.ts +75 -5
  105. package/lib/task/verification-engine.ts +19 -30
  106. package/lib/task/verification.ts +2 -1
  107. package/lib/task/workflow-warning-intents.ts +18 -6
  108. package/lib/task/workspace-snapshot.ts +3 -2
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  111. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  112. package/templates/.agents/rules/review-handshake.en.md +4 -4
  113. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  114. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  115. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  116. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  117. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  118. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  119. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  120. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  121. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  122. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  123. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  124. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  125. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  128. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  129. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  130. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  131. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  132. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  135. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  136. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  137. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  138. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  139. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  140. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  141. package/templates/.agents/templates/task.en.md +2 -2
  142. package/templates/.agents/templates/task.zh-CN.md +2 -2
  143. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  144. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  145. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  146. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  147. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  148. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  149. package/dist/lib/sandbox/control/finalization-response.js +0 -35
  150. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  151. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  152. package/dist/lib/task/lifecycle-recovery.js +0 -549
  153. package/dist/lib/task/recovery-warning.js +0 -19
  154. package/dist/lib/task/workflow-command.js +0 -21
  155. package/dist/lib/task/workflow-dispatch.js +0 -213
  156. package/lib/sandbox/control/finalization-response.ts +0 -37
  157. package/lib/sandbox/control/task-workflow.ts +0 -95
  158. package/lib/sandbox/control/workflow-executor.ts +0 -156
  159. package/lib/task/lifecycle-recovery.ts +0 -691
  160. package/lib/task/recovery-warning.ts +0 -21
  161. package/lib/task/workflow-command.ts +0 -22
  162. package/lib/task/workflow-dispatch.ts +0 -214
@@ -7,7 +7,10 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SE
7
7
  import { resolveTaskRef } from './resolve-ref.js';
8
8
  import { writeTask } from './write.js';
9
9
  import { allowsManualOverride } from './guard-override.js';
10
- import { parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.js';
10
+ import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.js';
11
+ import { extractSection, extractSubSection } from './sections.js';
12
+ import { parseLifecyclePathDecision } from './lifecycle-path.js';
13
+ import { scanVisibleMarkdown } from './markdown.js';
11
14
  const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
12
15
  const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
13
16
  const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
@@ -23,15 +26,50 @@ function ledgerReadErrorCode(error) {
23
26
  return code;
24
27
  return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
25
28
  }
26
- function maxHandshakeRounds(repoRoot) {
27
- try {
28
- const parsed = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
29
- const value = parsed.review?.maxHandshakeRounds;
30
- return Number.isInteger(value) && Number(value) > 0 ? Number(value) : 3;
31
- }
32
- catch {
33
- return 3;
29
+ const CLASSIFICATION_TARGET = {
30
+ 'scope-or-requirement': 'analysis', design: 'plan', implementation: 'code',
31
+ 'human-decision': 'pause', 'insufficient-evidence': 'pause'
32
+ };
33
+ function semanticDigest(value) {
34
+ return createHash('sha256').update(value.normalize('NFKC').replace(/\s+/g, ' ').trim()).digest('hex');
35
+ }
36
+ function normalizedTaskFact(value) {
37
+ return value.normalize('NFKC').replace(/\s+/g, ' ').trim();
38
+ }
39
+ function semanticFindingEvidence(reviewContent, heading, end) {
40
+ const title = heading.text.replace(/^\d+[.、::]\s*/, '').trim();
41
+ return `${title}\n${reviewContent.slice(heading.end, end)}`;
42
+ }
43
+ function findingEvidence(reviewContent, evidence) {
44
+ const anchor = evidence.split('#')[1];
45
+ if (!anchor)
46
+ return null;
47
+ const markdown = scanVisibleMarkdown(reviewContent);
48
+ const explicit = markdown.anchors.find((candidate) => candidate.id === anchor);
49
+ if (explicit) {
50
+ const heading = markdown.headings.find((candidate) => candidate.start > explicit.start);
51
+ const next = heading && markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
52
+ const end = next?.start ?? reviewContent.length;
53
+ return heading ? semanticFindingEvidence(reviewContent, heading, end) : reviewContent.slice(explicit.end, end);
34
54
  }
55
+ const heading = markdown.headings.find((candidate) => {
56
+ const escapedAnchor = anchor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
+ return candidate.text === anchor || new RegExp(`^${escapedAnchor}(?:\\s|[.、::])`).test(candidate.text);
58
+ });
59
+ if (!heading)
60
+ return null;
61
+ const next = markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
62
+ return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
63
+ }
64
+ function taskFactDigest(taskDir, content) {
65
+ const analysisNames = fs.readdirSync(taskDir).filter((name) => /^analysis(?:-r\d+)?\.md$/.test(name));
66
+ const latest = analysisNames.map((name) => ({ name, round: parseArtifactName(name)?.round ?? 0 })).sort((a, b) => b.round - a.round)[0];
67
+ const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
68
+ return semanticDigest(JSON.stringify({
69
+ taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
70
+ requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
71
+ flow: flow?.status === 'valid' ? flow.decision.semanticDigest : flow?.status ?? 'missing'
72
+ }));
35
73
  }
36
74
  function nextFindingId(rows, stage) {
37
75
  const prefix = PREFIX[stage];
@@ -89,12 +127,15 @@ function applyLedgerIntent(intent, options = {}) {
89
127
  return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: nextHdId(rows), before: null, after: null, operations: [], error: null };
90
128
  }
91
129
  if (intent.kind === 'rework-intent-upsert') {
92
- if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !['analysis', 'plan', 'code'].includes(intent.target) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
93
- return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, target, or source hash is invalid', resolved.taskId, intent.intentId);
130
+ if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !(intent.classification in CLASSIFICATION_TARGET) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
131
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, classification, or source hash is invalid', resolved.taskId, intent.intentId);
94
132
  }
95
133
  const finding = rows.find((row) => row.id === intent.findingId);
96
134
  if (!finding || finding.severity === 'decision')
97
135
  return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
136
+ if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
137
+ return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId, intent.findingId);
138
+ }
98
139
  const sourceFromEvidence = finding.evidence.split('#')[0];
99
140
  if (sourceFromEvidence !== intent.sourceArtifact || parseArtifactName(intent.sourceArtifact)?.family !== `review-${finding.stage}`) {
100
141
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
@@ -106,12 +147,35 @@ function applyLedgerIntent(intent, options = {}) {
106
147
  const parsed = parseReworkIntentDocument(content);
107
148
  if (!parsed.ok)
108
149
  return failed(intent, parsed.code, parsed.message, resolved.taskId, intent.intentId);
109
- const nextIntent = {
150
+ const reviewContent = fs.readFileSync(path.join(resolved.taskDir, intent.sourceArtifact), 'utf8');
151
+ const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
152
+ if (!evidenceBlock)
153
+ return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId, intent.intentId);
154
+ const evidenceDigest = semanticDigest(evidenceBlock);
155
+ const factsDigest = taskFactDigest(resolved.taskDir, content);
156
+ const requestedIntent = {
110
157
  intentId: intent.intentId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact,
111
- sourceSha256: intent.sourceSha256, target: intent.target, status: 'pending',
158
+ sourceSha256: intent.sourceSha256, target: CLASSIFICATION_TARGET[intent.classification], classification: intent.classification,
159
+ evidenceDigest, taskFactDigest: factsDigest, status: 'pending',
112
160
  declaredAt: new Date().toISOString(), consumedAt: ''
113
161
  };
114
- const next = upsertReworkIntent(parsed.intents, nextIntent);
162
+ if (parsed.intents.some((candidate) => candidate.intentId === intent.intentId)) {
163
+ const replay = upsertReworkIntent(parsed.intents, requestedIntent);
164
+ if (!replay.changed) {
165
+ return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
166
+ }
167
+ }
168
+ const same = parsed.intents.find((candidate) => candidate.findingId === intent.findingId
169
+ && candidate.evidenceDigest === evidenceDigest && candidate.taskFactDigest === factsDigest
170
+ && candidate.classification === intent.classification && candidate.target === CLASSIFICATION_TARGET[intent.classification]);
171
+ const classification = same ? 'insufficient-evidence' : intent.classification;
172
+ const nextIntent = {
173
+ ...requestedIntent, target: CLASSIFICATION_TARGET[classification], classification
174
+ };
175
+ const now = nextIntent.declaredAt;
176
+ const superseded = parsed.intents.map((candidate) => candidate.status === 'pending'
177
+ ? { ...candidate, status: 'superseded', consumedAt: now } : candidate);
178
+ const next = upsertReworkIntent(superseded, nextIntent);
115
179
  if (!next.changed)
116
180
  return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
117
181
  const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next.intents)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
@@ -123,6 +187,84 @@ function applyLedgerIntent(intent, options = {}) {
123
187
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', error instanceof Error ? error.message : String(error), resolved.taskId, intent.intentId);
124
188
  }
125
189
  }
190
+ if (intent.kind === 'rework-intent-rebuild') {
191
+ const beforeSha256 = createHash('sha256').update(content).digest('hex');
192
+ const supplied = [intent.findingId, intent.sourceArtifact, intent.sourceSha256, intent.classification].filter((value) => value !== undefined).length;
193
+ if (supplied !== 0 && supplied !== 4)
194
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild binding options must be provided together', resolved.taskId);
195
+ const current = parseReworkIntentDocument(content);
196
+ if (current.ok && current.present) {
197
+ if (supplied !== 0)
198
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'current rework intent format does not accept rebuild binding options', resolved.taskId);
199
+ return {
200
+ status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: null,
201
+ before: null, after: null, operations: [], error: null,
202
+ rebuild: { mode: 'format-only', beforeSha256, afterSha256: beforeSha256, converted: current.intents.length, newIntentId: null }
203
+ };
204
+ }
205
+ const legacy = parseLegacyReworkIntentDocument(content);
206
+ if (!legacy.ok)
207
+ return failed(intent, legacy.code, legacy.message, resolved.taskId);
208
+ const pending = legacy.intents.filter((row) => row.status === 'pending');
209
+ if (pending.length === 0 && supplied !== 0)
210
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'format-only rebuild does not accept finding or classification options', resolved.taskId);
211
+ if (pending.length > 0 && supplied !== 4)
212
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'legacy pending intents require finding, review, hash, and classification', resolved.taskId);
213
+ const now = new Date().toISOString();
214
+ let next = legacy.intents.map((row) => row.status === 'pending' ? { ...row, status: 'superseded', consumedAt: now } : row);
215
+ let entityId = null;
216
+ if (pending.length > 0) {
217
+ const finding = rows.find((row) => row.id === intent.findingId);
218
+ if (!finding || finding.severity === 'decision' || finding.evidence.split('#')[0] !== intent.sourceArtifact)
219
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild binding does not match a review finding', resolved.taskId);
220
+ if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
221
+ return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId);
222
+ }
223
+ const sourceIdentity = parseArtifactName(intent.sourceArtifact);
224
+ const expectedFamily = `review-${finding.stage}`;
225
+ const latestSource = fs.readdirSync(resolved.taskDir)
226
+ .map((name) => parseArtifactName(name))
227
+ .filter((identity) => identity?.family === expectedFamily)
228
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
229
+ if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
230
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
231
+ }
232
+ const sourcePath = path.join(resolved.taskDir, intent.sourceArtifact);
233
+ const actualHash = createHash('sha256').update(fs.readFileSync(sourcePath)).digest('hex');
234
+ if (actualHash !== intent.sourceSha256)
235
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact hash does not match', resolved.taskId);
236
+ const max = next.reduce((value, row) => Math.max(value, Number(row.intentId.slice(3))), 0);
237
+ entityId = `RI-${max + 1}`;
238
+ const reviewContent = fs.readFileSync(sourcePath, 'utf8');
239
+ const classification = intent.classification;
240
+ if (!(classification in CLASSIFICATION_TARGET))
241
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild classification is invalid', resolved.taskId);
242
+ const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
243
+ if (!evidenceBlock)
244
+ return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId);
245
+ next = [...next, {
246
+ intentId: entityId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact, sourceSha256: intent.sourceSha256,
247
+ target: CLASSIFICATION_TARGET[classification], classification,
248
+ evidenceDigest: semanticDigest(evidenceBlock),
249
+ taskFactDigest: taskFactDigest(resolved.taskDir, content), status: 'pending', declaredAt: now, consumedAt: ''
250
+ }];
251
+ }
252
+ const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
253
+ if (result.status === 'failed')
254
+ return failed(intent, result.error.code, result.error.message, result.taskId, entityId);
255
+ const afterContent = result.status === 'applied' || result.status === 'no-op' ? fs.readFileSync(resolved.taskMdPath, 'utf8') : content;
256
+ return {
257
+ status: result.status, changed: result.changed, intent: intent.kind, taskId: result.taskId, entityId,
258
+ before: null, after: null, operations: result.operations, error: null,
259
+ rebuild: {
260
+ mode: pending.length > 0 ? 'pending-reclassification' : 'format-only',
261
+ beforeSha256,
262
+ afterSha256: createHash('sha256').update(afterContent).digest('hex'),
263
+ converted: legacy.intents.length,
264
+ newIntentId: entityId
265
+ }
266
+ };
267
+ }
126
268
  let before = null;
127
269
  let after;
128
270
  if (intent.kind === 'finding-upsert') {
@@ -199,7 +341,7 @@ function applyLedgerIntent(intent, options = {}) {
199
341
  ? new Set(['confirmed', 'open', 'needs-human-decision'])
200
342
  : before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set();
201
343
  const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
202
- if (((!sameRoundMinorClose && !allowed.has(intent.status)) || (intent.status === 'open' && Number(before.round) >= maxHandshakeRounds(resolved.repoRoot)))
344
+ if ((!sameRoundMinorClose && !allowed.has(intent.status))
203
345
  && !allowsManualOverride(options.manualOverride, 'ledger-intent', 'LEDGER_TRANSITION_INVALID')) {
204
346
  return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
205
347
  }
@@ -0,0 +1,75 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { extractSection } from './sections.js';
3
+ const PATH_STAGES = {
4
+ streamlined: ['analysis', 'code', 'review-code'],
5
+ standard: ['analysis', 'plan', 'code', 'review-code'],
6
+ full: ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']
7
+ };
8
+ const FIELD_ALIASES = {
9
+ path: ['本任务路径', 'Path'],
10
+ basis: ['判定依据', 'Basis'],
11
+ unmetHigherConditions: ['未满足的更高路径条件', '未满足的较低路径条件', 'Unmet Higher-path Conditions'],
12
+ upgradeTriggers: ['升级触发条件', 'Upgrade Triggers']
13
+ };
14
+ function normalizeText(value) {
15
+ return value.normalize('NFKC').replace(/\s+/g, ' ').trim().replace(/[。.]$/, '');
16
+ }
17
+ function digest(value) {
18
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
19
+ }
20
+ function pathToken(value) {
21
+ const normalized = normalizeText(value).toLowerCase().replace(/[`*_]/g, '');
22
+ if (/^(?:streamlined|精简路径|精简)$/.test(normalized))
23
+ return 'streamlined';
24
+ if (/^(?:standard|标准路径|标准)$/.test(normalized))
25
+ return 'standard';
26
+ if (/^(?:full|完整路径|完整)$/.test(normalized))
27
+ return 'full';
28
+ return null;
29
+ }
30
+ function fieldValues(body, aliases) {
31
+ const wanted = new Set(aliases);
32
+ const values = [];
33
+ for (const line of body.split(/\r?\n/)) {
34
+ const match = /^\s*[-*]\s+\*\*([^*]+)\*\*[::]\s*(.*?)\s*$/.exec(line);
35
+ if (match && wanted.has(match[1].trim()))
36
+ values.push(match[2].trim());
37
+ }
38
+ return values;
39
+ }
40
+ function parseLifecyclePathDecision(analysisContent, sourceArtifact = '', sourceSha256 = '') {
41
+ const body = extractSection(analysisContent, ['流程裁定', 'Flow Decision']);
42
+ if (!body)
43
+ return { status: 'missing', decision: null, message: 'analysis flow decision section is missing' };
44
+ const fields = Object.fromEntries(Object.entries(FIELD_ALIASES).map(([key, aliases]) => [key, fieldValues(body, aliases)]));
45
+ const invalid = Object.entries(fields).find(([, values]) => values.length !== 1);
46
+ if (invalid)
47
+ return { status: 'invalid', decision: null, message: `flow decision field '${invalid[0]}' must appear exactly once` };
48
+ const path = pathToken(fields.path[0]);
49
+ if (!path)
50
+ return { status: 'invalid', decision: null, message: `unknown lifecycle path '${fields.path[0]}'` };
51
+ const basis = normalizeText(fields.basis[0]);
52
+ const unmetHigherConditions = normalizeText(fields.unmetHigherConditions[0]);
53
+ const upgradeTriggers = normalizeText(fields.upgradeTriggers[0]);
54
+ if (!basis || !unmetHigherConditions || !upgradeTriggers)
55
+ return { status: 'invalid', decision: null, message: 'flow decision fields must be non-empty' };
56
+ return {
57
+ status: 'valid',
58
+ message: null,
59
+ decision: {
60
+ path,
61
+ stages: PATH_STAGES[path],
62
+ basis,
63
+ unmetHigherConditions,
64
+ upgradeTriggers,
65
+ semanticDigest: digest({ path, basis, unmetHigherConditions, upgradeTriggers }),
66
+ sourceArtifact,
67
+ sourceSha256
68
+ }
69
+ };
70
+ }
71
+ function pathIncludes(state, stage) {
72
+ return state?.status === 'valid' && state.decision.stages.includes(stage);
73
+ }
74
+ export { PATH_STAGES, parseLifecyclePathDecision, pathIncludes };
75
+ //# sourceMappingURL=lifecycle-path.js.map
@@ -151,7 +151,7 @@ function actionAndNote(request, restoredFiles = 0) {
151
151
  if (request.intent === 'close-dependabot')
152
152
  return { action: 'Close Dependabot', note: `Dependabot alert #${request.alertNumber} dismissed: ${request.reason}` };
153
153
  if (request.intent === 'recover-started')
154
- return { action: 'Recover Started', note: request.reason };
154
+ return { action: 'Recover Started', note: 'automatic recovery' };
155
155
  return { action: 'Restore Task', note: `Restored ${restoredFiles} files from Issue #${request.issueNumber}` };
156
156
  }
157
157
  function matchingCompletion(content, request, restoredFiles = 0) {
@@ -578,6 +578,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
578
578
  return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
579
579
  }
580
580
  }
581
+ if (options.prepareOnly && !completed.has('directory-moved')) {
582
+ return {
583
+ ...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
584
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
585
+ task: { operations: taskOperations }, directory: { effect: 'unchanged', changed: false },
586
+ shortId: { effect: 'unchanged', shortId: null, changed: false },
587
+ timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
588
+ journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
589
+ };
590
+ }
581
591
  if (fs.existsSync(targetPath) && !fs.existsSync(sourcePath))
582
592
  completed.add('directory-moved');
583
593
  if (!completed.has('directory-moved')) {
@@ -3,9 +3,7 @@ import path from 'node:path';
3
3
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
4
4
  import { scanVisibleMarkdown } from './markdown.js';
5
5
  import { parseArtifactName } from './artifact-name.js';
6
- import { hasOpenArtifactRound, } from './artifact-lifecycle.js';
7
6
  import { resolveTaskRef } from './resolve-ref.js';
8
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
9
7
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
10
8
  import { getArtifactSchema } from './artifact-schema.js';
11
9
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
@@ -57,21 +55,6 @@ function validateLocalArtifact(content, options) {
57
55
  const decisionDetails = inspectDecisionDetailDuplicates(content);
58
56
  if (!decisionDetails.ok)
59
57
  diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
60
- if (options.taskContent !== undefined) {
61
- const identity = options.artifact ? parseArtifactName(options.artifact) : null;
62
- const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
63
- ? expectedQualificationRelations(options.taskContent, options.family)
64
- : undefined;
65
- if (expected && !expected.ok)
66
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
67
- const qualification = validateQualificationAudit(options.taskContent, content, {
68
- family: options.family,
69
- artifact: options.artifact,
70
- expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
71
- });
72
- if (!qualification.ok)
73
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
74
- }
75
58
  return {
76
59
  ok: diagnostics.length === 0,
77
60
  family: options.family,
@@ -98,10 +81,8 @@ function prepareLocalArtifact(request, candidate) {
98
81
  const parsed = parseArtifactName(request.artifact);
99
82
  if (!parsed || parsed.family !== request.family)
100
83
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
101
- let taskContent;
102
84
  let content;
103
85
  try {
104
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
105
86
  const artifactPath = path.join(resolved.taskDir, request.artifact);
106
87
  if (!fs.statSync(artifactPath).isFile())
107
88
  return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
@@ -114,7 +95,7 @@ function prepareLocalArtifact(request, candidate) {
114
95
  return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
115
96
  }
116
97
  const validation = validateLocalArtifact(content, {
117
- family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
98
+ family: request.family, requiredSections: request.requiredSections
118
99
  });
119
100
  const artifactSha256 = sha256Content(content);
120
101
  const result = failedFinalization(request, {
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
4
4
  import { MANUAL_VALIDATION_ARTIFACT as ARTIFACT, MANUAL_VALIDATION_SHA40 as SHA40, MANUAL_VALIDATION_SHA64 as SHA64, MANUAL_VALIDATION_TASK_ID as TASK_ID, exactKeys, isRecord, validTimestamp, writeJsonAtomic } from './manual-validation-shared.js';
5
5
  const MANUAL_VALIDATION_RECEIPT_SCHEMA = 'agent-infra/manual-validation-receipt';
6
6
  const MANUAL_VALIDATION_RECEIPT_VERSION = 1;
7
+ const MANUAL_VALIDATION_RECEIPT_PLACEHOLDER = '[[manual-validation-receipt]]';
7
8
  function invalid(message) {
8
9
  return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message } };
9
10
  }
@@ -33,12 +34,16 @@ function manualValidationFinalSummaryDigest(body) {
33
34
  const projection = heading
34
35
  ? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
35
36
  : body;
36
- const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
37
+ const preimage = projection
38
+ .replace(/\n*<!--\s*canonical-pr-change-report\s*-->\s*/gu, '')
39
+ .replace(/<!--\s*manual-validation-receipt:\s*[\s\S]*?-->/gu, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER)
40
+ .replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
37
41
  return createHash('sha256').update(preimage, 'utf8').digest('hex');
38
42
  }
39
43
  function manualValidationFinalSummaryProjectionMatches(body, receipt) {
40
44
  const identity = `transaction=${receipt.transactionId}; receipt=${receipt.receiptDigest}; evidence=${receipt.evidenceDigest}; head=${receipt.prHeadSha}`;
41
- return body.includes(identity) && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
45
+ return (body.includes(identity) || body.includes(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER))
46
+ && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
42
47
  }
43
48
  function validateManualValidationReceipt(value, expected) {
44
49
  if (!isRecord(value))
@@ -101,5 +106,5 @@ function readManualValidationReceipt(taskDir, expected) {
101
106
  }
102
107
  return validateManualValidationReceipt(value, expected);
103
108
  }
104
- export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
109
+ export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
105
110
  //# sourceMappingURL=manual-validation-receipt.js.map
@@ -22,6 +22,7 @@ import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js'
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
23
  import { reconcileTaskInvalidation } from './invalidation-command.js';
24
24
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
25
+ import { resolveArtifactContext } from './artifact-lifecycle.js';
25
26
  function supportsLifecycleDelegation(client) {
26
27
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
27
28
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -479,17 +480,13 @@ function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead)
479
480
  function routeFromFacts(facts) {
480
481
  const recommendation = recommendNext(facts);
481
482
  if (!recommendation.action)
482
- return { completion: true };
483
+ return recommendation.reasonCode === 'LIFECYCLE_REVIEWED' ? { completion: true } : null;
483
484
  const action = recommendation.action;
484
485
  if (action === 'manual-validation' || action === 'validation-run')
485
486
  return null;
486
- const family = action === 'analysis' ? 'analysis' : action;
487
- const round = maxArtifactRound([
488
- ...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])
489
- ], family) + 1;
490
487
  const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
491
488
  const role = action.startsWith('review-') ? 'reviewer' : 'executor';
492
- return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
489
+ return { action: command, role, stage: action };
493
490
  }
494
491
  function routeOrchestration(taskRef, options = {}) {
495
492
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -619,8 +616,19 @@ function routeOrchestration(taskRef, options = {}) {
619
616
  return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
620
617
  }
621
618
  const action = routed.stage;
619
+ const family = action === 'analysis' ? 'analysis' : action;
620
+ const artifactContext = resolveArtifactContext(taskRef, family, {
621
+ repoRoot: resolved.repoRoot
622
+ });
623
+ if (artifactContext.status !== 'ready' || !artifactContext.selection) {
624
+ return failed(artifactContext.error?.code ?? 'ORCHESTRATION_ARTIFACT_SELECTION_FAILED', artifactContext.error?.message ?? 'cannot determine artifact selection', resolved.taskId);
625
+ }
626
+ if (artifactContext.selection.disposition === 'reuse') {
627
+ return failed('ORCHESTRATION_REUSE_STALLED', `lifecycle still routes to ${action} after reusing ${artifactContext.selection.artifact.name}`, resolved.taskId);
628
+ }
629
+ const selected = artifactContext.selection.artifact;
622
630
  const capability = canStart(action, facts.facts, {
623
- initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
631
+ initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${selected.round}`,
624
632
  requestedAction: action, reasonCode: 'user-request', explicitRequest: true
625
633
  });
626
634
  if (!capability.allowed)
@@ -628,6 +636,8 @@ function routeOrchestration(taskRef, options = {}) {
628
636
  const policy = run ? rolePolicy(run, routed.role) : null;
629
637
  const next = {
630
638
  ...routed,
639
+ round: selected.round,
640
+ artifact: selected.name,
631
641
  requestedModel: policy?.model ?? null,
632
642
  requestedReasoningEffort: policy?.reasoningEffort ?? null
633
643
  };
@@ -1075,6 +1085,14 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1075
1085
  return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1076
1086
  }
1077
1087
  }
1088
+ function recoveryReceiptMatches(receipt, taskId, event) {
1089
+ return receipt.taskId === taskId
1090
+ && receipt.stage === event.stage
1091
+ && receipt.round === event.round
1092
+ && receipt.artifact === event.artifact
1093
+ && receipt.childId === event.childId
1094
+ && normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
1095
+ }
1078
1096
  function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
1079
1097
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1080
1098
  if (!resolved.ok)
@@ -1089,12 +1107,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
1089
1107
  const receipt = matches.find((candidate) => candidate.id === event.receiptId);
1090
1108
  if (!receipt)
1091
1109
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
1092
- if (receipt.taskId !== resolved.taskId
1093
- || receipt.stage !== event.stage
1094
- || receipt.round !== event.round
1095
- || receipt.artifact !== event.artifact
1096
- || receipt.childId !== event.childId
1097
- || normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)) {
1110
+ if (!recoveryReceiptMatches(receipt, resolved.taskId, event)) {
1098
1111
  return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
1099
1112
  }
1100
1113
  if (receipt.status === 'aborted' && run.pendingDelegation === null) {
@@ -1106,6 +1119,9 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
1106
1119
  if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
1107
1120
  return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
1108
1121
  }
1122
+ if (run.status !== 'running' || run.pause !== null) {
1123
+ return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'activated recovery cannot replace an unrelated orchestration pause', resolved.taskId);
1124
+ }
1109
1125
  const aborted = abortActivatedDelegation(receipt, {
1110
1126
  childId: event.childId,
1111
1127
  stopRevision: event.stopRevision,
@@ -12,7 +12,6 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
12
12
  import { allowsManualOverride } from './guard-override.js';
13
13
  import { getArtifactSchema } from './artifact-schema.js';
14
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
16
15
  const STAGES = {
17
16
  analysis: { family: 'review-analysis' },
18
17
  plan: { family: 'review-plan' },
@@ -66,10 +65,8 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
66
65
  const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
67
66
  if (publicationError)
68
67
  return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
69
- let taskContent;
70
68
  let content;
71
69
  try {
72
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
73
70
  const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
74
71
  if (!validated.ok)
75
72
  return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
@@ -89,22 +86,12 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
89
86
  const semanticDigest = canonicalSemanticDigest(content);
90
87
  const detail = inspectDecisionDetailDuplicates(content);
91
88
  const structure = inspectArtifactContract(content, getArtifactSchema(spec.family));
92
- const expected = expectedQualificationRelations(taskContent, spec.family);
93
- if (!expected.ok)
94
- return preflightFailed(request, 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', `${expected.code}: ${expected.message}`, resolved.taskId, { artifactSha256, semanticDigest });
95
- const qualification = validateQualificationAudit(taskContent, content, {
96
- family: spec.family,
97
- artifact: request.artifact,
98
- expectedUpstreamRelations: expected.relations
99
- });
100
- const qualificationError = qualification.ok ? null : qualification;
101
- if (!detail.ok || !structure.ok || qualificationError) {
89
+ if (!detail.ok || !structure.ok) {
102
90
  const diagnostics = [
103
91
  ...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
104
- ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
105
- ...(qualificationError ? [`${qualificationError.code}: ${qualificationError.message}`] : [])
92
+ ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`)
106
93
  ].join('; ');
107
- return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
94
+ return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : 'REVIEW_ARTIFACT_STRUCTURE_INVALID', diagnostics, resolved.taskId, {
108
95
  artifactSha256, semanticDigest,
109
96
  });
110
97
  }