@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReviewFinalizationRequest } from './review-finalization.ts';
|
|
2
|
+
|
|
3
|
+
export function parseReviewCommand(args: readonly string[]): ReviewFinalizationRequest & {
|
|
4
|
+
overrideTicket?: string; overrideTarget?: string; overrideScope?: string;
|
|
5
|
+
} {
|
|
6
|
+
if (!args[0] || args[1] !== 'finalize-summary') throw new Error('task ref and finalize-summary are required');
|
|
7
|
+
const values: Record<string, string | boolean> = {};
|
|
8
|
+
const flags = {
|
|
9
|
+
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
|
|
10
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
11
|
+
} as const;
|
|
12
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
13
|
+
const flag = args[index]!;
|
|
14
|
+
if (!Object.hasOwn(flags, flag)) throw new Error(`unknown option '${flag}'`);
|
|
15
|
+
const key = flags[flag as keyof typeof flags];
|
|
16
|
+
if (Object.hasOwn(values, key)) throw new Error(`duplicate option '${flag}'`);
|
|
17
|
+
const value = flag === '--orchestrated' || flag === '--dry-run' ? true : args[++index];
|
|
18
|
+
if (!value || typeof value === 'string' && value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
|
|
19
|
+
values[key] = value;
|
|
20
|
+
}
|
|
21
|
+
if (!values.stage || !values.artifact) throw new Error("options '--stage' and '--artifact' are required");
|
|
22
|
+
return { taskRef: args[0], ...values } as ReturnType<typeof parseReviewCommand>;
|
|
23
|
+
}
|
|
@@ -2,8 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
6
|
+
import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.ts';
|
|
7
7
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
8
8
|
import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
9
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
@@ -18,6 +18,7 @@ import { getArtifactSchema } from './artifact-schema.ts';
|
|
|
18
18
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
|
|
19
19
|
import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
20
20
|
import type { ArtifactRepairOperation } from './artifact-operations.ts';
|
|
21
|
+
import type { ArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
21
22
|
|
|
22
23
|
type ReviewFinalizationErrorCode =
|
|
23
24
|
| ResolveTaskRefErrorCode
|
|
@@ -79,10 +80,10 @@ type ReviewFinalizationOptions = {
|
|
|
79
80
|
lockAlreadyHeld?: boolean;
|
|
80
81
|
};
|
|
81
82
|
|
|
82
|
-
const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code'
|
|
83
|
-
analysis: { family: 'review-analysis'
|
|
84
|
-
plan: { family: 'review-plan'
|
|
85
|
-
code: { family: 'review-code'
|
|
83
|
+
const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
|
|
84
|
+
analysis: { family: 'review-analysis' },
|
|
85
|
+
plan: { family: 'review-plan' },
|
|
86
|
+
code: { family: 'review-code' }
|
|
86
87
|
};
|
|
87
88
|
const DEFAULT_FILE_SYSTEM: ReviewFileSystem = {
|
|
88
89
|
readFileSync: (file) => fs.readFileSync(file, 'utf8'),
|
|
@@ -119,15 +120,6 @@ function failed(
|
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
function openReviewRound(content: string, action: string, round: number): boolean {
|
|
123
|
-
const section = locateActivityLog(content);
|
|
124
|
-
if (!section) return false;
|
|
125
|
-
const expected = `${action} (Round ${round})`;
|
|
126
|
-
return startedBackedRows(pairEntries(section.entries)).filter((row) => (
|
|
127
|
-
row.step === expected && !row.done
|
|
128
|
-
)).length === 1;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
123
|
function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
|
|
132
124
|
try {
|
|
133
125
|
fileSystem.unlinkSync(tempPath);
|
|
@@ -136,288 +128,167 @@ function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
|
|
|
136
128
|
}
|
|
137
129
|
}
|
|
138
130
|
|
|
139
|
-
|
|
131
|
+
type ReviewSummaryCandidatePreparation = Readonly<{
|
|
132
|
+
result: ReviewFinalizationResult;
|
|
133
|
+
content: string;
|
|
134
|
+
provenance?: Parameters<typeof writeArtifactRepairIntent>[1];
|
|
135
|
+
expectedIntent?: ArtifactRepairIntent | null;
|
|
136
|
+
}>;
|
|
137
|
+
|
|
138
|
+
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
139
|
+
function prepareReviewSummaryCandidate(
|
|
140
140
|
request: ReviewFinalizationRequest,
|
|
141
|
+
artifactContent: string,
|
|
141
142
|
options: ReviewFinalizationOptions = {}
|
|
142
|
-
):
|
|
143
|
+
): ReviewSummaryCandidatePreparation {
|
|
144
|
+
let taskId: string | null = null;
|
|
145
|
+
let stageStatus: LedgerStageStatus | null = null;
|
|
146
|
+
const reject = (code: ReviewFinalizationErrorCode, message: string, extra: Partial<ReviewFinalizationResult> = {}): ReviewSummaryCandidatePreparation => ({
|
|
147
|
+
result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
|
|
148
|
+
});
|
|
143
149
|
const stage = request.stage as ReviewStage;
|
|
144
150
|
const spec = STAGES[stage];
|
|
145
|
-
if (!spec) return
|
|
146
|
-
if (!request.taskRef || !request.artifact)
|
|
147
|
-
return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
|
|
148
|
-
}
|
|
151
|
+
if (!spec) return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
|
|
152
|
+
if (!request.taskRef || !request.artifact) return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
|
|
149
153
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
150
|
-
|
|
154
|
+
taskId = resolved.taskId ?? null;
|
|
155
|
+
if (!resolved.ok) return reject(resolved.code, resolved.message);
|
|
151
156
|
if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
const parsedArtifact = parseArtifactName(request.artifact);
|
|
155
|
-
if (!parsedArtifact || parsedArtifact.family !== spec.family) {
|
|
156
|
-
return failed(
|
|
157
|
-
request,
|
|
158
|
-
'REVIEW_ARTIFACT_IDENTITY_INVALID',
|
|
159
|
-
`artifact '${request.artifact}' does not match ${spec.family}`,
|
|
160
|
-
resolved.taskId
|
|
161
|
-
);
|
|
157
|
+
return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
|
|
162
158
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
parsedArtifact.round
|
|
168
|
-
);
|
|
169
|
-
if (!validated.ok) {
|
|
170
|
-
const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
|
|
171
|
-
? 'REVIEW_ARTIFACT_NOT_REGULAR'
|
|
172
|
-
: 'REVIEW_ARTIFACT_IDENTITY_INVALID';
|
|
173
|
-
return failed(request, code, validated.error.message, resolved.taskId);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
159
|
+
const parsed = parseArtifactName(request.artifact);
|
|
160
|
+
if (!parsed || parsed.family !== spec.family) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
|
|
161
|
+
const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
|
|
162
|
+
if (publicationError) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
|
|
177
163
|
let taskContent: string;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
taskContent = fileSystem.readFileSync(resolved.taskMdPath);
|
|
181
|
-
artifactContent = fileSystem.readFileSync(validated.artifact.path);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
184
|
-
}
|
|
185
|
-
const schema = getArtifactSchema(spec.family);
|
|
186
|
-
const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
|
|
164
|
+
try { taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath); }
|
|
165
|
+
catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
|
|
187
166
|
const artifactSha256 = sha256Content(artifactContent);
|
|
188
|
-
const
|
|
167
|
+
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
168
|
+
const digests = { artifactSha256, semanticDigest };
|
|
189
169
|
let repairIntent;
|
|
190
|
-
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
semanticDigest: artifactSemanticDigest
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
if (structure && !structure.ok) {
|
|
170
|
+
try { repairIntent = readArtifactRepairIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
|
|
171
|
+
catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
|
|
172
|
+
const schema = getArtifactSchema(spec.family)!;
|
|
173
|
+
const structure = inspectArtifactContract(artifactContent, schema);
|
|
174
|
+
if (!structure.ok) {
|
|
199
175
|
const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
|
|
200
|
-
if (repairable
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
repairIntent.artifactSha256 !== artifactSha256 ||
|
|
204
|
-
repairIntent.semanticDigest !== structure.semanticDigest
|
|
205
|
-
)) {
|
|
206
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
|
|
207
|
-
artifactSha256,
|
|
208
|
-
semanticDigest: structure.semanticDigest
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
try {
|
|
212
|
-
if (!repairIntent) {
|
|
213
|
-
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
214
|
-
version: 1,
|
|
215
|
-
taskId: resolved.taskId,
|
|
216
|
-
family: spec.family,
|
|
217
|
-
artifact: request.artifact,
|
|
218
|
-
state: 'awaiting-repair',
|
|
219
|
-
baselineSemanticDigest: structure.semanticDigest,
|
|
220
|
-
artifactSha256,
|
|
221
|
-
semanticDigest: structure.semanticDigest
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
|
|
226
|
-
artifactSha256,
|
|
227
|
-
semanticDigest: structure.semanticDigest,
|
|
228
|
-
operation: structure.repair
|
|
229
|
-
});
|
|
230
|
-
}
|
|
176
|
+
if (repairable && repairIntent && (repairIntent.state !== 'awaiting-repair'
|
|
177
|
+
|| repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== structure.semanticDigest)) {
|
|
178
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', digests);
|
|
231
179
|
}
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
artifactSha256,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
180
|
+
return {
|
|
181
|
+
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
182
|
+
artifactSha256, semanticDigest: structure.semanticDigest, repairable, operation: structure.repair
|
|
183
|
+
}),
|
|
184
|
+
...(repairable && !repairIntent ? { provenance: {
|
|
185
|
+
version: 2 as const, taskId: taskId!, family: spec.family, artifact: request.artifact,
|
|
186
|
+
state: 'awaiting-repair' as const, baselineSemanticDigest: structure.semanticDigest,
|
|
187
|
+
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
188
|
+
recoveryOperationId: null, phase: null, authorityDigest: null,
|
|
189
|
+
requestId: `review-finalize:${taskId!}`, createdAt: Date.now(), updatedAt: Date.now()
|
|
190
|
+
}, expectedIntent: null } : {})
|
|
191
|
+
};
|
|
245
192
|
}
|
|
246
|
-
if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !==
|
|
247
|
-
return
|
|
248
|
-
artifactSha256,
|
|
249
|
-
semanticDigest: artifactSemanticDigest
|
|
250
|
-
});
|
|
193
|
+
if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== semanticDigest) {
|
|
194
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', digests);
|
|
251
195
|
}
|
|
252
196
|
if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
|
|
253
|
-
&& (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !==
|
|
254
|
-
return
|
|
255
|
-
artifactSha256,
|
|
256
|
-
semanticDigest: artifactSemanticDigest
|
|
257
|
-
});
|
|
197
|
+
&& (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== semanticDigest)) {
|
|
198
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
258
199
|
}
|
|
259
|
-
if (!
|
|
260
|
-
return
|
|
261
|
-
request,
|
|
262
|
-
'REVIEW_ARTIFACT_IDENTITY_INVALID',
|
|
263
|
-
`${request.artifact} does not have one matching open started review event`,
|
|
264
|
-
resolved.taskId
|
|
265
|
-
);
|
|
200
|
+
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
201
|
+
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
266
202
|
}
|
|
267
203
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
268
204
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
269
|
-
identity: {
|
|
270
|
-
stage: spec.family,
|
|
271
|
-
round: parsedArtifact.round,
|
|
272
|
-
artifact: request.artifact,
|
|
273
|
-
role: 'reviewer'
|
|
274
|
-
},
|
|
205
|
+
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
275
206
|
dryRun: request.dryRun
|
|
276
207
|
}, { repoRoot: options.repoRoot });
|
|
277
208
|
if (!execution.ok) {
|
|
278
|
-
return failed
|
|
279
|
-
request,
|
|
280
|
-
'REVIEW_PROVENANCE_INVALID',
|
|
281
|
-
`${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`,
|
|
282
|
-
resolved.taskId
|
|
283
|
-
);
|
|
209
|
+
return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
|
|
284
210
|
}
|
|
285
|
-
|
|
286
|
-
let rows;
|
|
287
211
|
try {
|
|
288
212
|
const ledger = parseLedgerDocument(taskContent);
|
|
289
|
-
if (!ledger.present) {
|
|
290
|
-
|
|
213
|
+
if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
214
|
+
const error = validateLedgerRows(ledger.rows);
|
|
215
|
+
if (error) return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
|
|
216
|
+
stageStatus = summarizeLedgerStage(ledger.rows, stage);
|
|
217
|
+
} catch (error) { return reject('REVIEW_LEDGER_INVALID', String(error)); }
|
|
218
|
+
const detail = inspectDecisionDetailDuplicates(artifactContent);
|
|
219
|
+
if (!detail.ok) return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
|
|
220
|
+
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
|
|
221
|
+
if (!transformed.ok) return reject(transformed.code, transformed.message);
|
|
222
|
+
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
223
|
+
return {
|
|
224
|
+
content: transformed.content,
|
|
225
|
+
result: {
|
|
226
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
227
|
+
status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
|
|
228
|
+
changed: transformed.changed,
|
|
229
|
+
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
230
|
+
error: null
|
|
231
|
+
},
|
|
232
|
+
...(repairIntent?.state === 'awaiting-repair' ? {
|
|
233
|
+
provenance: { ...repairIntent, state: 'passed' as const, ...finalDigests },
|
|
234
|
+
expectedIntent: repairIntent
|
|
235
|
+
} : {})
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function commitReviewSummaryProvenance(
|
|
240
|
+
prepared: ReviewSummaryCandidatePreparation,
|
|
241
|
+
repoRoot: string
|
|
242
|
+
): ReviewFinalizationResult {
|
|
243
|
+
try {
|
|
244
|
+
if (prepared.provenance) {
|
|
245
|
+
writeArtifactRepairIntent(repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
|
|
291
246
|
}
|
|
292
|
-
|
|
247
|
+
return prepared.result;
|
|
293
248
|
} catch (error) {
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (ledgerError) {
|
|
298
|
-
return failed(
|
|
299
|
-
request,
|
|
300
|
-
'REVIEW_LEDGER_INVALID',
|
|
301
|
-
`${ledgerError.code}: ${ledgerError.message}`,
|
|
302
|
-
resolved.taskId
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
const stageStatus = summarizeLedgerStage(rows, stage);
|
|
306
|
-
const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
|
|
307
|
-
if (!detailInspection.ok) {
|
|
308
|
-
return failed(
|
|
309
|
-
request,
|
|
310
|
-
'REVIEW_DECISION_DETAIL_INVALID',
|
|
311
|
-
`${detailInspection.code}: ${detailInspection.message}`,
|
|
312
|
-
resolved.taskId,
|
|
313
|
-
stageStatus
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
const transformed = finalizeReviewSummaryContent(
|
|
317
|
-
artifactContent,
|
|
318
|
-
stageStatus.unresolvedFindingCounts
|
|
319
|
-
);
|
|
320
|
-
if (!transformed.ok) {
|
|
321
|
-
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
322
|
-
}
|
|
323
|
-
if (!transformed.changed) {
|
|
324
|
-
if (repairIntent?.state === 'awaiting-repair') {
|
|
325
|
-
try {
|
|
326
|
-
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
327
|
-
...repairIntent,
|
|
328
|
-
state: 'passed',
|
|
329
|
-
artifactSha256,
|
|
330
|
-
semanticDigest: artifactSemanticDigest
|
|
331
|
-
});
|
|
332
|
-
} catch (error) {
|
|
333
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
|
|
334
|
-
artifactSha256,
|
|
335
|
-
semanticDigest: artifactSemanticDigest
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return {
|
|
340
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
|
|
341
|
-
artifactSha256,
|
|
342
|
-
semanticDigest: artifactSemanticDigest
|
|
343
|
-
}),
|
|
344
|
-
status: 'no-op',
|
|
345
|
-
error: null
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
|
|
349
|
-
if (request.dryRun) {
|
|
350
|
-
return {
|
|
351
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
|
|
352
|
-
artifactSha256: sha256Content(transformed.content),
|
|
353
|
-
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
354
|
-
}),
|
|
355
|
-
status: 'planned',
|
|
356
|
-
changed: true,
|
|
357
|
-
operations,
|
|
358
|
-
error: null
|
|
359
|
-
};
|
|
249
|
+
return { ...prepared.result, status: 'failed', error: {
|
|
250
|
+
code: 'REVIEW_PROVENANCE_INVALID', message: `cannot record review provenance: ${String(error)}`
|
|
251
|
+
} };
|
|
360
252
|
}
|
|
253
|
+
}
|
|
361
254
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
255
|
+
function finalizeReviewSummaryUnlocked(
|
|
256
|
+
request: ReviewFinalizationRequest,
|
|
257
|
+
options: ReviewFinalizationOptions = {}
|
|
258
|
+
): ReviewFinalizationResult {
|
|
259
|
+
const spec = STAGES[request.stage as ReviewStage];
|
|
260
|
+
if (!spec || !request.taskRef || !request.artifact) return prepareReviewSummaryCandidate(request, '', options).result;
|
|
261
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
262
|
+
if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
|
|
263
|
+
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
264
|
+
if (!validated.ok) {
|
|
265
|
+
return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
266
|
+
}
|
|
267
|
+
const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
268
|
+
let content: string;
|
|
269
|
+
try { content = fileSystem.readFileSync(validated.artifact.path); }
|
|
270
|
+
catch (error) { return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
|
|
271
|
+
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
272
|
+
if (request.dryRun) return prepared.result;
|
|
273
|
+
if (prepared.result.status !== 'applied') return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
|
|
274
|
+
const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
|
|
367
275
|
try {
|
|
368
|
-
|
|
369
|
-
fileSystem.writeFileSync(tempPath, transformed.content, mode);
|
|
276
|
+
fileSystem.writeFileSync(tempPath, prepared.content, fileSystem.statModeSync(validated.artifact.path));
|
|
370
277
|
} catch (error) {
|
|
371
278
|
cleanupTemp(fileSystem, tempPath);
|
|
372
|
-
return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
|
|
279
|
+
return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
|
|
373
280
|
}
|
|
374
281
|
try {
|
|
375
|
-
if (fileSystem.readFileSync(validated.artifact.path) !==
|
|
282
|
+
if (fileSystem.readFileSync(validated.artifact.path) !== content) {
|
|
376
283
|
cleanupTemp(fileSystem, tempPath);
|
|
377
|
-
return failed(
|
|
378
|
-
request,
|
|
379
|
-
'REVIEW_ARTIFACT_CONFLICT',
|
|
380
|
-
`${request.artifact} changed during finalization`,
|
|
381
|
-
resolved.taskId,
|
|
382
|
-
stageStatus
|
|
383
|
-
);
|
|
284
|
+
return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, prepared.result.stageStatus);
|
|
384
285
|
}
|
|
385
286
|
fileSystem.renameSync(tempPath, validated.artifact.path);
|
|
386
287
|
} catch (error) {
|
|
387
288
|
cleanupTemp(fileSystem, tempPath);
|
|
388
|
-
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
|
|
389
|
-
}
|
|
390
|
-
if (repairIntent?.state === 'awaiting-repair') {
|
|
391
|
-
try {
|
|
392
|
-
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
393
|
-
...repairIntent,
|
|
394
|
-
state: 'passed',
|
|
395
|
-
artifactSha256: sha256Content(transformed.content),
|
|
396
|
-
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
397
|
-
});
|
|
398
|
-
} catch (error) {
|
|
399
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
|
|
400
|
-
artifactSha256: sha256Content(transformed.content),
|
|
401
|
-
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
402
|
-
});
|
|
403
|
-
}
|
|
289
|
+
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
|
|
404
290
|
}
|
|
405
|
-
return
|
|
406
|
-
status: 'applied',
|
|
407
|
-
changed: true,
|
|
408
|
-
intent: 'finalize-summary',
|
|
409
|
-
requestRef: request.taskRef,
|
|
410
|
-
taskId: resolved.taskId,
|
|
411
|
-
stage,
|
|
412
|
-
artifact: request.artifact,
|
|
413
|
-
stageStatus,
|
|
414
|
-
artifactSha256: sha256Content(transformed.content),
|
|
415
|
-
semanticDigest: canonicalSemanticDigest(transformed.content),
|
|
416
|
-
repairable: false,
|
|
417
|
-
operation: null,
|
|
418
|
-
operations,
|
|
419
|
-
error: null
|
|
420
|
-
};
|
|
291
|
+
return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
|
|
421
292
|
}
|
|
422
293
|
|
|
423
294
|
function finalizeReviewSummary(
|
|
@@ -445,11 +316,12 @@ function finalizeReviewSummary(
|
|
|
445
316
|
}
|
|
446
317
|
}
|
|
447
318
|
|
|
448
|
-
export { finalizeReviewSummary };
|
|
319
|
+
export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
|
|
449
320
|
export type {
|
|
450
321
|
ReviewFinalizationError,
|
|
451
322
|
ReviewFinalizationErrorCode,
|
|
452
323
|
ReviewFinalizationOptions,
|
|
453
324
|
ReviewFinalizationRequest,
|
|
454
|
-
ReviewFinalizationResult
|
|
325
|
+
ReviewFinalizationResult,
|
|
326
|
+
ReviewSummaryCandidatePreparation
|
|
455
327
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { artifactName,
|
|
4
|
+
import { artifactName, maxArtifactRound } from "./artifact-name.ts";
|
|
5
5
|
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.ts";
|
|
6
6
|
|
|
7
7
|
type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
|
|
@@ -45,7 +45,7 @@ export function resolvePostReviewGlobs(
|
|
|
45
45
|
|
|
46
46
|
export function findAuthoritativeReviewCodeArtifact(taskDir: string) {
|
|
47
47
|
const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
|
|
48
|
-
const round =
|
|
48
|
+
const round = maxArtifactRound(entries, "review-code");
|
|
49
49
|
if (round === 0) {
|
|
50
50
|
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
51
51
|
}
|