@fitlab-ai/agent-infra 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +4 -131
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -0
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +120 -13
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +161 -156
- package/dist/lib/sandbox/control/executor.js +0 -38
- package/dist/lib/sandbox/control/finalization-response.js +10 -2
- package/dist/lib/sandbox/control/task-view.js +3 -1
- package/dist/lib/sandbox/control/workflow-executor.js +32 -41
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +27 -1
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +1 -8
- package/dist/lib/task/delegation-receipts.js +14 -76
- package/dist/lib/task/events.js +147 -278
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +31 -245
- package/dist/lib/task/orchestration.js +8 -24
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +47 -218
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +4 -1
- package/dist/lib/task/workflow-dispatch.js +55 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +3 -145
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -0
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +132 -13
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +178 -179
- package/lib/sandbox/control/executor.ts +0 -44
- package/lib/sandbox/control/finalization-response.ts +10 -2
- package/lib/sandbox/control/task-view.ts +3 -1
- package/lib/sandbox/control/workflow-executor.ts +31 -48
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +27 -1
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +1 -8
- package/lib/task/delegation-receipts.ts +12 -79
- package/lib/task/events.ts +155 -287
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +29 -285
- package/lib/task/orchestration.ts +12 -41
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +46 -200
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- package/lib/task/workflow-command.ts +4 -1
- package/lib/task/workflow-dispatch.ts +46 -3
- package/package.json +2 -2
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
- package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/block-task/config/verify.json +1 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -5
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
- package/templates/.agents/skills/create-pr/config/verify.json +1 -10
- package/templates/.agents/skills/create-task/config/verify.json +1 -4
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
2
4
|
import { parseArtifactName } from './artifact-name.js';
|
|
3
|
-
import { validateCompletedArtifact,
|
|
5
|
+
import { validateCompletedArtifact, validateArtifactPublication } from './artifact-lifecycle.js';
|
|
4
6
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
5
7
|
import { finalizeReviewSummaryContent } from './review-artifacts.js';
|
|
6
8
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
@@ -11,8 +13,6 @@ import { allowsManualOverride } from './guard-override.js';
|
|
|
11
13
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
12
14
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
13
15
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
|
|
14
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
15
|
-
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
16
16
|
const STAGES = {
|
|
17
17
|
analysis: { family: 'review-analysis' },
|
|
18
18
|
plan: { family: 'review-plan' },
|
|
@@ -47,7 +47,7 @@ function preflightFailed(request, code, message, taskId = null, extra = {}) {
|
|
|
47
47
|
stageStatus: null
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
/** Validate
|
|
50
|
+
/** Validate the current review draft before any finding-ledger write. */
|
|
51
51
|
function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
52
52
|
const stage = request.stage;
|
|
53
53
|
const spec = STAGES[stage];
|
|
@@ -68,39 +68,16 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
68
68
|
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
|
|
69
69
|
let taskContent;
|
|
70
70
|
let content;
|
|
71
|
-
let recovery;
|
|
72
|
-
let existingIntent;
|
|
73
71
|
try {
|
|
74
72
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready'].includes(intent.state))
|
|
80
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`, resolved.taskId);
|
|
81
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
82
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
83
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
84
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
88
|
-
if (!validated.ok)
|
|
89
|
-
return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
90
|
-
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
91
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
|
|
92
|
-
existingIntent = intent;
|
|
93
|
-
if (intent?.state === 'preflight-ready')
|
|
94
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
95
|
-
if (intent?.state === 'consumed')
|
|
96
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'review artifact was already finalized', resolved.taskId);
|
|
97
|
-
}
|
|
73
|
+
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
74
|
+
if (!validated.ok)
|
|
75
|
+
return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
76
|
+
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
98
77
|
}
|
|
99
78
|
catch (error) {
|
|
100
79
|
return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
101
80
|
}
|
|
102
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round))
|
|
103
|
-
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
|
|
104
81
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
105
82
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
106
83
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -122,17 +99,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
122
99
|
});
|
|
123
100
|
const qualificationError = qualification.ok ? null : qualification;
|
|
124
101
|
if (!detail.ok || !structure.ok || qualificationError) {
|
|
125
|
-
if (!recovery && !request.dryRun) {
|
|
126
|
-
try {
|
|
127
|
-
recovery = beginArtifactRecovery({
|
|
128
|
-
taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
129
|
-
requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
|
|
130
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
102
|
const diagnostics = [
|
|
137
103
|
...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
|
|
138
104
|
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
|
|
@@ -140,36 +106,18 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
140
106
|
].join('; ');
|
|
141
107
|
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
|
|
142
108
|
artifactSha256, semanticDigest,
|
|
143
|
-
...(recovery ? { recovery: recoveryInfo(recovery) } : {})
|
|
144
109
|
});
|
|
145
110
|
}
|
|
146
|
-
if (
|
|
111
|
+
if (request.dryRun) {
|
|
147
112
|
return {
|
|
148
113
|
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
149
114
|
status: 'passed', changed: false, error: null
|
|
150
115
|
};
|
|
151
116
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
recovery ??= beginArtifactRecovery({
|
|
160
|
-
taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
161
|
-
requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
|
|
162
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
163
|
-
const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
164
|
-
prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
165
|
-
return {
|
|
166
|
-
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, recovery: recoveryInfo(recovery) }),
|
|
167
|
-
status: 'passed', changed: false, error: null
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, { artifactSha256, semanticDigest });
|
|
172
|
-
}
|
|
117
|
+
return {
|
|
118
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
119
|
+
status: 'passed', changed: false, error: null
|
|
120
|
+
};
|
|
173
121
|
}
|
|
174
122
|
function preflightReviewSummary(request, options = {}) {
|
|
175
123
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
@@ -186,14 +134,6 @@ function preflightReviewSummary(request, options = {}) {
|
|
|
186
134
|
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
187
135
|
}
|
|
188
136
|
}
|
|
189
|
-
function recoveryInfo(recovery) {
|
|
190
|
-
return {
|
|
191
|
-
recoveryId: recovery.recoveryId,
|
|
192
|
-
candidatePath: recovery.stagingPath,
|
|
193
|
-
baselineSha256: recovery.baselineSha256,
|
|
194
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
137
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
198
138
|
function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
199
139
|
let taskId = null;
|
|
@@ -227,45 +167,9 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
227
167
|
catch (error) {
|
|
228
168
|
return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
|
|
229
169
|
}
|
|
230
|
-
let recovery;
|
|
231
|
-
if (request.recoveryId) {
|
|
232
|
-
try {
|
|
233
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
234
|
-
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
235
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
236
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
237
|
-
return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
238
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
239
|
-
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
240
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
241
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
170
|
const artifactSha256 = sha256Content(artifactContent);
|
|
248
171
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
249
172
|
const digests = { artifactSha256, semanticDigest };
|
|
250
|
-
let repairIntent;
|
|
251
|
-
try {
|
|
252
|
-
repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
256
|
-
}
|
|
257
|
-
if (repairIntent?.state === 'consumed') {
|
|
258
|
-
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
259
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (repairIntent?.state === 'passed'
|
|
263
|
-
&& (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
|
|
264
|
-
repairIntent = undefined;
|
|
265
|
-
}
|
|
266
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
267
|
-
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
268
|
-
}
|
|
269
173
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
270
174
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
271
175
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -292,65 +196,16 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
292
196
|
const schema = getArtifactSchema(spec.family);
|
|
293
197
|
const structure = inspectArtifactContract(artifactContent, schema);
|
|
294
198
|
if (!structure.ok) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
299
|
-
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
300
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return {
|
|
307
|
-
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
308
|
-
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
309
|
-
...(recovery ? { recovery: {
|
|
310
|
-
recoveryId: recovery.recoveryId,
|
|
311
|
-
candidatePath: recovery.stagingPath,
|
|
312
|
-
baselineSha256: recovery.baselineSha256,
|
|
313
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
314
|
-
} } : {})
|
|
315
|
-
}),
|
|
316
|
-
...(recovery ? { recovery } : {})
|
|
317
|
-
};
|
|
199
|
+
return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
200
|
+
artifactSha256, semanticDigest: structure.semanticDigest
|
|
201
|
+
});
|
|
318
202
|
}
|
|
319
|
-
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts
|
|
203
|
+
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
|
|
204
|
+
refreshNumericCounts: true
|
|
205
|
+
});
|
|
320
206
|
if (!transformed.ok)
|
|
321
207
|
return reject(transformed.code, transformed.message);
|
|
322
208
|
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
323
|
-
if (!recovery && repairIntent?.state === 'commit-started') {
|
|
324
|
-
if (repairIntent.finalArtifactSha256 !== finalDigests.artifactSha256 || repairIntent.finalSemanticDigest !== finalDigests.semanticDigest) {
|
|
325
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'formal review artifact does not match the interrupted recovery journal', finalDigests);
|
|
326
|
-
}
|
|
327
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, repairIntent);
|
|
328
|
-
}
|
|
329
|
-
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
330
|
-
if (transformed.changed)
|
|
331
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
332
|
-
return {
|
|
333
|
-
content: transformed.content,
|
|
334
|
-
result: {
|
|
335
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
336
|
-
status: 'no-op',
|
|
337
|
-
changed: false,
|
|
338
|
-
error: null
|
|
339
|
-
},
|
|
340
|
-
lockAlreadyHeld: options.lockAlreadyHeld
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
344
|
-
try {
|
|
345
|
-
recovery = beginArtifactRecovery({
|
|
346
|
-
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
347
|
-
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
348
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
209
|
return {
|
|
355
210
|
content: transformed.content,
|
|
356
211
|
result: {
|
|
@@ -360,65 +215,26 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
360
215
|
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
361
216
|
error: null
|
|
362
217
|
},
|
|
363
|
-
...(recovery ? { recovery } : {}),
|
|
364
218
|
lockAlreadyHeld: options.lockAlreadyHeld
|
|
365
219
|
};
|
|
366
220
|
}
|
|
367
221
|
function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
|
|
368
|
-
|
|
369
|
-
|
|
222
|
+
return prepared.result;
|
|
223
|
+
}
|
|
224
|
+
function replaceCurrentArtifactAtomically(file, content) {
|
|
225
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
370
226
|
try {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
...prepared.result,
|
|
378
|
-
status: 'no-op',
|
|
379
|
-
changed: false,
|
|
380
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
381
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
382
|
-
error: null
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
else if (intent.state === 'commit-started') {
|
|
386
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
387
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
388
|
-
afterPublish: options.afterPublish
|
|
389
|
-
});
|
|
390
|
-
committed = reconciled.intent;
|
|
391
|
-
}
|
|
392
|
-
else if (intent.state === 'awaiting-preflight-recovery') {
|
|
393
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
394
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
395
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
396
|
-
}
|
|
397
|
-
else if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
398
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
399
|
-
}
|
|
400
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
401
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
402
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
227
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
228
|
+
fs.renameSync(temporary, file);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
try {
|
|
232
|
+
fs.unlinkSync(temporary);
|
|
403
233
|
}
|
|
404
|
-
|
|
405
|
-
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error.code !== 'ENOENT')
|
|
236
|
+
throw error;
|
|
406
237
|
}
|
|
407
|
-
if (!committed || committed.state !== 'passed')
|
|
408
|
-
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
409
|
-
return {
|
|
410
|
-
...prepared.result,
|
|
411
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
412
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
413
|
-
error: null
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
catch (error) {
|
|
417
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
418
|
-
throw error;
|
|
419
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
420
|
-
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
421
|
-
} };
|
|
422
238
|
}
|
|
423
239
|
}
|
|
424
240
|
function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
@@ -443,10 +259,23 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
|
443
259
|
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
444
260
|
if (request.dryRun)
|
|
445
261
|
return prepared.result;
|
|
446
|
-
|
|
262
|
+
if (prepared.result.status === 'failed' || !prepared.result.changed)
|
|
263
|
+
return prepared.result;
|
|
264
|
+
try {
|
|
265
|
+
replaceCurrentArtifactAtomically(validated.artifact.path, prepared.content);
|
|
266
|
+
return {
|
|
267
|
+
...prepared.result,
|
|
268
|
+
status: 'applied',
|
|
269
|
+
artifactSha256: sha256Content(prepared.content),
|
|
270
|
+
semanticDigest: canonicalSemanticDigest(prepared.content)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
return failed(request, 'REVIEW_RECOVERY_COMMIT_FAILED', `cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`, resolved.taskId);
|
|
275
|
+
}
|
|
447
276
|
}
|
|
448
277
|
function finalizeReviewSummary(request, options = {}) {
|
|
449
|
-
const effectiveOptions =
|
|
278
|
+
const effectiveOptions = options;
|
|
450
279
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
451
280
|
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
452
281
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
@@ -29,6 +29,7 @@ import { validateLocalArtifact } from "./local-artifact-finalization.js";
|
|
|
29
29
|
import { validateQualificationAudit } from "./qualification-audit.js";
|
|
30
30
|
import { getArtifactSchema } from "./artifact-schema.js";
|
|
31
31
|
import { inspectArtifactContract } from "./artifact-operations.js";
|
|
32
|
+
import { normalizeVerificationRecord } from './gate-policy.js';
|
|
32
33
|
import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
|
|
33
34
|
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
34
35
|
import { sha256File } from "./artifact-receipts.js";
|
|
@@ -1198,19 +1199,19 @@ function interpolate(template, taskDir, artifactFile) {
|
|
|
1198
1199
|
.replace(/\{artifact-stem\}/g, artifactStem);
|
|
1199
1200
|
}
|
|
1200
1201
|
function summarizeGate(checks) {
|
|
1201
|
-
if (checks.some((check) => check.
|
|
1202
|
+
if (checks.some((check) => check.effectiveStatus === "blocked")) {
|
|
1202
1203
|
return "blocked";
|
|
1203
1204
|
}
|
|
1204
|
-
if (checks.some((check) => check.
|
|
1205
|
+
if (checks.some((check) => check.effectiveStatus === "fail")) {
|
|
1205
1206
|
return "fail";
|
|
1206
1207
|
}
|
|
1207
1208
|
return "pass";
|
|
1208
1209
|
}
|
|
1209
1210
|
function summarizeChecks(checks) {
|
|
1210
1211
|
const counts = {
|
|
1211
|
-
pass: checks.filter((check) => check.
|
|
1212
|
-
fail: checks.filter((check) => check.
|
|
1213
|
-
blocked: checks.filter((check) => check.
|
|
1212
|
+
pass: checks.filter((check) => check.effectiveStatus === "pass").length,
|
|
1213
|
+
fail: checks.filter((check) => check.effectiveStatus === "fail").length,
|
|
1214
|
+
blocked: checks.filter((check) => check.effectiveStatus === "blocked").length
|
|
1214
1215
|
};
|
|
1215
1216
|
if (counts.blocked > 0) {
|
|
1216
1217
|
return `${counts.pass} passed, ${counts.fail} failed, ${counts.blocked} blocked`;
|
|
@@ -1221,7 +1222,7 @@ function buildAction(gate, checks) {
|
|
|
1221
1222
|
if (gate === "pass") {
|
|
1222
1223
|
return "All declared checks passed";
|
|
1223
1224
|
}
|
|
1224
|
-
const firstFailure = checks.find((check) => check.
|
|
1225
|
+
const firstFailure = checks.find((check) => check.effectiveStatus !== "pass");
|
|
1225
1226
|
if (!firstFailure) {
|
|
1226
1227
|
return "Review validation output";
|
|
1227
1228
|
}
|
|
@@ -1277,16 +1278,16 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
|
|
|
1277
1278
|
for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
|
|
1278
1279
|
if (checkConfig === null)
|
|
1279
1280
|
continue;
|
|
1280
|
-
const result = await runCheck(type, {
|
|
1281
|
+
const result = normalizeVerificationRecord(await runCheck(type, {
|
|
1281
1282
|
mode,
|
|
1282
1283
|
skillName,
|
|
1283
1284
|
taskDir: path.resolve(taskDir),
|
|
1284
1285
|
artifactFile,
|
|
1285
1286
|
repositoryRoot: repoRoot,
|
|
1286
1287
|
config: checkConfig
|
|
1287
|
-
}, shared);
|
|
1288
|
-
checks.push(result);
|
|
1289
|
-
if (
|
|
1288
|
+
}, shared));
|
|
1289
|
+
checks.push(...(result.subchecks?.map(normalizeVerificationRecord) ?? [result]));
|
|
1290
|
+
if (checks.at(-1)?.effectiveStatus === "blocked")
|
|
1290
1291
|
break;
|
|
1291
1292
|
}
|
|
1292
1293
|
const gate = summarizeGate(checks);
|
|
@@ -1298,15 +1299,25 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
|
|
|
1298
1299
|
return { skill: skillName, ...failResult(type, `Unknown check type '${type}' for skill '${skillName}'.`) };
|
|
1299
1300
|
if (config === null)
|
|
1300
1301
|
return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
|
|
1302
|
+
const result = normalizeVerificationRecord(await runCheck(type, {
|
|
1303
|
+
skillName,
|
|
1304
|
+
taskDir: path.resolve(taskDir),
|
|
1305
|
+
artifactFile,
|
|
1306
|
+
repositoryRoot: repoRoot,
|
|
1307
|
+
config
|
|
1308
|
+
}, shared));
|
|
1309
|
+
if (!result.subchecks?.length)
|
|
1310
|
+
return { skill: skillName, ...result };
|
|
1311
|
+
const checks = result.subchecks.map(normalizeVerificationRecord);
|
|
1312
|
+
const gate = summarizeGate(checks);
|
|
1301
1313
|
return {
|
|
1302
1314
|
skill: skillName,
|
|
1303
|
-
...
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
}, shared)
|
|
1315
|
+
...result,
|
|
1316
|
+
status: gate,
|
|
1317
|
+
effectiveStatus: gate,
|
|
1318
|
+
reason: gate === 'pass' ? 'OK' : checks.find((check) => check.effectiveStatus !== 'pass')?.reason ?? 'CHECK_FAILED',
|
|
1319
|
+
action: buildAction(gate, checks),
|
|
1320
|
+
checks
|
|
1310
1321
|
};
|
|
1311
1322
|
}
|
|
1312
1323
|
export { verifyInProcess };
|
|
@@ -80,7 +80,7 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
80
80
|
else {
|
|
81
81
|
for (const check of spec.checks ?? []) {
|
|
82
82
|
const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
|
|
83
|
-
const status = payload.status;
|
|
83
|
+
const status = (payload.effectiveStatus ?? payload.status);
|
|
84
84
|
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
85
85
|
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
|
|
86
86
|
break;
|
|
@@ -103,19 +103,35 @@ function statusLabel(status) {
|
|
|
103
103
|
function renderPayload(payload) {
|
|
104
104
|
const lines = [];
|
|
105
105
|
if (Array.isArray(payload.checks)) {
|
|
106
|
-
|
|
106
|
+
const gateMode = typeof payload.gate === 'string';
|
|
107
|
+
lines.push(gateMode
|
|
108
|
+
? `Verification: ${payload.gate} | Skill: ${payload.skill}`
|
|
109
|
+
: `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
107
110
|
for (const check of payload.checks) {
|
|
108
|
-
|
|
111
|
+
const raw = statusLabel(check.status);
|
|
112
|
+
const effective = statusLabel(check.effectiveStatus ?? check.status);
|
|
113
|
+
const reason = check.reason ?? 'CHECK_FAILED';
|
|
114
|
+
const message = check.message ?? reason;
|
|
115
|
+
lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
|
|
109
116
|
}
|
|
110
|
-
|
|
117
|
+
const summary = payload.summary ?? (() => {
|
|
118
|
+
const checks = payload.checks;
|
|
119
|
+
const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
|
|
120
|
+
return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
|
|
121
|
+
})();
|
|
122
|
+
lines.push('', `Result: ${summary} - ${payload.action}`);
|
|
111
123
|
}
|
|
112
124
|
else {
|
|
113
125
|
lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
const raw = statusLabel(payload.status);
|
|
127
|
+
const effective = statusLabel(payload.effectiveStatus ?? payload.status);
|
|
128
|
+
const reason = payload.reason ?? 'CHECK_FAILED';
|
|
129
|
+
lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
|
|
130
|
+
const status = payload.effectiveStatus ?? payload.status;
|
|
131
|
+
const summary = status === 'pass' ? '1 passed, 0 failed'
|
|
132
|
+
: status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
|
|
133
|
+
const action = status === 'pass' ? 'Requested check passed'
|
|
134
|
+
: status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
|
|
119
135
|
: `Fix ${payload.type} issues and re-run check`;
|
|
120
136
|
lines.push('', `Result: ${summary} - ${action}`);
|
|
121
137
|
}
|
|
@@ -9,10 +9,13 @@ export const TASK_WORKFLOW_COMMANDS = Object.freeze({
|
|
|
9
9
|
'ledger-finding-response': ['task-ledger', 'finding-respond'],
|
|
10
10
|
'ledger-finding-review': ['task-ledger', 'finding-review'],
|
|
11
11
|
'ledger-finding-upsert': ['task-ledger', 'finding-upsert'],
|
|
12
|
+
'ledger-stage-status': ['task-ledger', 'stage-status'],
|
|
12
13
|
'decision-next-id': ['task-ledger', 'decision-next-id'],
|
|
13
14
|
'decision-upsert': ['task-ledger', 'decision-upsert'],
|
|
14
15
|
'invalidation-reconcile': ['task-invalidation', 'reconcile'],
|
|
15
|
-
'warning-add': ['task-warning', 'add']
|
|
16
|
+
'warning-add': ['task-warning', 'add'],
|
|
17
|
+
'warning-list': ['task-warning', 'list'],
|
|
18
|
+
'warning-set-status': ['task-warning', 'set-status']
|
|
16
19
|
});
|
|
17
20
|
export const TASK_WORKFLOW_OPERATIONS = Object.freeze(Object.keys(TASK_WORKFLOW_COMMANDS));
|
|
18
21
|
//# sourceMappingURL=workflow-command.js.map
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
2
3
|
import { applyLedgerIntent } from './ledger-intents.js';
|
|
4
|
+
import { isReviewStage, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
5
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
3
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
4
7
|
import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
|
|
5
8
|
function failed(code, message) {
|
|
@@ -102,8 +105,27 @@ function ledgerIntent(operation, args) {
|
|
|
102
105
|
}
|
|
103
106
|
function warningIntent(args) {
|
|
104
107
|
const [taskRef, kind] = args;
|
|
105
|
-
if (!taskRef || kind
|
|
108
|
+
if (!taskRef || !['add', 'list', 'set-status'].includes(kind ?? '')) {
|
|
106
109
|
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
|
|
110
|
+
}
|
|
111
|
+
if (kind === 'list') {
|
|
112
|
+
const values = options(args, { '--status': 'status' });
|
|
113
|
+
if (isFailure(values))
|
|
114
|
+
return values;
|
|
115
|
+
return { kind, taskRef, ...(value(values, '--status') ? { status: value(values, '--status') } : {}) };
|
|
116
|
+
}
|
|
117
|
+
if (kind === 'set-status') {
|
|
118
|
+
const values = options(args, { '--id': 'id', '--status': 'status', '--resolution': 'resolution', '--dry-run': 'dryRun' });
|
|
119
|
+
if (isFailure(values))
|
|
120
|
+
return values;
|
|
121
|
+
const missing = required(values, ['--id', '--status', '--resolution']);
|
|
122
|
+
if (missing)
|
|
123
|
+
return missing;
|
|
124
|
+
return {
|
|
125
|
+
kind, taskRef, id: value(values, '--id'), status: value(values, '--status'),
|
|
126
|
+
resolution: value(values, '--resolution'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
127
|
+
};
|
|
128
|
+
}
|
|
107
129
|
const values = options(args, {
|
|
108
130
|
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
109
131
|
'--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
|
|
@@ -119,6 +141,32 @@ function warningIntent(args) {
|
|
|
119
141
|
action: value(values, '--action'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
120
142
|
};
|
|
121
143
|
}
|
|
144
|
+
function stageStatus(repoRoot, args) {
|
|
145
|
+
const [taskRef, kind] = args;
|
|
146
|
+
if (!taskRef || kind !== 'stage-status')
|
|
147
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
|
|
148
|
+
const values = options(args, { '--stage': 'stage' });
|
|
149
|
+
if (isFailure(values))
|
|
150
|
+
return values;
|
|
151
|
+
const stage = value(values, '--stage');
|
|
152
|
+
if (!stage || !isReviewStage(stage))
|
|
153
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'stage-status requires a valid review stage');
|
|
154
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
155
|
+
if (!resolved.ok)
|
|
156
|
+
return failed(resolved.code, resolved.message);
|
|
157
|
+
try {
|
|
158
|
+
const ledger = parseLedgerDocument(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
159
|
+
if (!ledger.present)
|
|
160
|
+
return failed('LEDGER_SECTION_MISSING', 'review disagreement ledger section is missing');
|
|
161
|
+
const invalid = validateLedgerRows(ledger.rows);
|
|
162
|
+
if (invalid)
|
|
163
|
+
return failed(invalid.code, invalid.message);
|
|
164
|
+
return { status: 'ready', changed: false, taskId: resolved.taskId, stageStatus: summarizeLedgerStage(ledger.rows, stage), error: null };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return failed('LEDGER_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
122
170
|
/**
|
|
123
171
|
* The caller has already selected and authorized the execution boundary.
|
|
124
172
|
* This function deliberately invokes task domains in that process: starting a
|
|
@@ -143,11 +191,15 @@ export function dispatchWorkflowCommand(repoRoot, operation, args) {
|
|
|
143
191
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
144
192
|
}));
|
|
145
193
|
}
|
|
146
|
-
if (operation === '
|
|
194
|
+
if (operation === 'ledger-stage-status')
|
|
195
|
+
return stageStatus(repoRoot, args);
|
|
196
|
+
if (operation === 'warning-add' || operation === 'warning-list' || operation === 'warning-set-status') {
|
|
147
197
|
const intent = warningIntent(args);
|
|
148
198
|
if (isFailure(intent))
|
|
149
199
|
return intent;
|
|
150
|
-
|
|
200
|
+
if (intent.kind === 'list')
|
|
201
|
+
return applyWorkflowWarningIntent(intent, { repoRoot });
|
|
202
|
+
return withTaskExecutionLock(repoRoot, taskRef, `task-warning.${intent.kind}`, () => applyWorkflowWarningIntent(intent, { repoRoot }));
|
|
151
203
|
}
|
|
152
204
|
const intent = ledgerIntent(operation, args);
|
|
153
205
|
if (isFailure(intent))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.2",
|
|
4
|
+
"sourceInputHash": "0a1ffa8b90a0d157f9b2aabea6a43f946d68a622bde337befe71059f63b3812b",
|
|
5
|
+
"internalExecutableBuildHash": "b6742e1b5d1b997bf7e21e644bfde2bafaa608f7ede7ca308ff61c2cd19da831"
|
|
6
6
|
}
|
|
@@ -46,7 +46,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
46
46
|
'platform-security': ['read', 'dismiss'],
|
|
47
47
|
'platform-metadata': ['init-labels', 'init-milestones'],
|
|
48
48
|
'platform-context': ['resolve'],
|
|
49
|
-
'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
|
|
49
|
+
'platform-comment': ['list', 'recover', 'owner', 'backfill', 'stage-summary', 'sync'],
|
|
50
50
|
'platform-issue': ['inspect', 'create', 'bind', 'sync'],
|
|
51
51
|
'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
|
|
52
52
|
'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
|