@fitlab-ai/agent-infra 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +43 -9
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +83 -21
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-operations.js +1 -1
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +6 -6
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +78 -14
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +203 -10
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +46 -13
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +102 -21
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-operations.ts +1 -1
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +6 -6
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +85 -11
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +203 -8
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/workflow-command.ts +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +9 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +10 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +7 -6
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +10 -8
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +9 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -10,8 +10,9 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
10
10
|
import { allowsManualOverride } from './guard-override.js';
|
|
11
11
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
12
12
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
13
|
+
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
|
|
13
14
|
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
14
|
-
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryCommit, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
15
|
+
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
15
16
|
const STAGES = {
|
|
16
17
|
analysis: { family: 'review-analysis' },
|
|
17
18
|
plan: { family: 'review-plan' },
|
|
@@ -37,6 +38,162 @@ function failed(request, code, message, taskId = null, stageStatus = null, extra
|
|
|
37
38
|
...extra
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
function preflightFailed(request, code, message, taskId = null, extra = {}) {
|
|
42
|
+
const { status: _status, intent: _intent, stageStatus: _stageStatus, ...finalizationExtra } = extra;
|
|
43
|
+
return {
|
|
44
|
+
...failed(request, code, message, taskId, null, finalizationExtra),
|
|
45
|
+
status: 'failed',
|
|
46
|
+
intent: 'preflight',
|
|
47
|
+
stageStatus: null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Validate and seal a review draft before any finding-ledger write. */
|
|
51
|
+
function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
52
|
+
const stage = request.stage;
|
|
53
|
+
const spec = STAGES[stage];
|
|
54
|
+
if (!spec)
|
|
55
|
+
return preflightFailed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
|
|
56
|
+
if (!request.taskRef || !request.artifact)
|
|
57
|
+
return preflightFailed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
|
|
58
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
59
|
+
if (!resolved.ok)
|
|
60
|
+
return preflightFailed(request, resolved.code, resolved.message, resolved.taskId ?? null);
|
|
61
|
+
if (resolved.state !== 'active')
|
|
62
|
+
return preflightFailed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
63
|
+
const parsed = parseArtifactName(request.artifact);
|
|
64
|
+
if (!parsed || parsed.family !== spec.family)
|
|
65
|
+
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`, resolved.taskId);
|
|
66
|
+
const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
|
|
67
|
+
if (publicationError)
|
|
68
|
+
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
|
|
69
|
+
let taskContent;
|
|
70
|
+
let content;
|
|
71
|
+
let recovery;
|
|
72
|
+
let existingIntent;
|
|
73
|
+
try {
|
|
74
|
+
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
75
|
+
if (request.recoveryId) {
|
|
76
|
+
const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
|
|
77
|
+
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
78
|
+
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal', resolved.taskId);
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
101
|
+
}
|
|
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
|
+
const execution = validateLifecycleExecution(request.taskRef, {
|
|
105
|
+
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
106
|
+
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
107
|
+
dryRun: request.dryRun
|
|
108
|
+
}, { repoRoot: options.repoRoot });
|
|
109
|
+
if (!execution.ok)
|
|
110
|
+
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
|
|
111
|
+
const artifactSha256 = sha256Content(content);
|
|
112
|
+
const semanticDigest = canonicalSemanticDigest(content);
|
|
113
|
+
const detail = inspectDecisionDetailDuplicates(content);
|
|
114
|
+
const structure = inspectArtifactContract(content, getArtifactSchema(spec.family));
|
|
115
|
+
const expected = expectedQualificationRelations(taskContent, spec.family);
|
|
116
|
+
if (!expected.ok)
|
|
117
|
+
return preflightFailed(request, 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', `${expected.code}: ${expected.message}`, resolved.taskId, { artifactSha256, semanticDigest });
|
|
118
|
+
const qualification = validateQualificationAudit(taskContent, content, {
|
|
119
|
+
family: spec.family,
|
|
120
|
+
artifact: request.artifact,
|
|
121
|
+
expectedUpstreamRelations: expected.relations
|
|
122
|
+
});
|
|
123
|
+
const qualificationError = qualification.ok ? null : qualification;
|
|
124
|
+
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
|
+
const diagnostics = [
|
|
137
|
+
...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
|
|
138
|
+
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
|
|
139
|
+
...(qualificationError ? [`${qualificationError.code}: ${qualificationError.message}`] : [])
|
|
140
|
+
].join('; ');
|
|
141
|
+
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
|
|
142
|
+
artifactSha256, semanticDigest,
|
|
143
|
+
...(recovery ? { recovery: recoveryInfo(recovery) } : {})
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!recovery && existingIntent?.state === 'passed') {
|
|
147
|
+
return {
|
|
148
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
149
|
+
status: 'passed', changed: false, error: null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (request.dryRun || recovery && readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact)?.state === 'preflight-ready') {
|
|
153
|
+
return {
|
|
154
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, ...(recovery ? { recovery: recoveryInfo(recovery) } : {}) }),
|
|
155
|
+
status: 'passed', changed: false, error: null
|
|
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
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function preflightReviewSummary(request, options = {}) {
|
|
175
|
+
if (request.dryRun || options.lockAlreadyHeld)
|
|
176
|
+
return preflightReviewSummaryUnlocked(request, options);
|
|
177
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
178
|
+
if (!resolved.ok)
|
|
179
|
+
return preflightReviewSummaryUnlocked(request, options);
|
|
180
|
+
try {
|
|
181
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.preflight', () => preflightReviewSummaryUnlocked(request, { ...options, lockAlreadyHeld: true }));
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
185
|
+
throw error;
|
|
186
|
+
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function recoveryInfo(recovery) {
|
|
190
|
+
return {
|
|
191
|
+
recoveryId: recovery.recoveryId,
|
|
192
|
+
candidatePath: recovery.stagingPath,
|
|
193
|
+
baselineSha256: recovery.baselineSha256,
|
|
194
|
+
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
195
|
+
};
|
|
196
|
+
}
|
|
40
197
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
41
198
|
function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
42
199
|
let taskId = null;
|
|
@@ -76,7 +233,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
76
233
|
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
77
234
|
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
78
235
|
return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
79
|
-
if (!['awaiting-recovery', '
|
|
236
|
+
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
80
237
|
return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
81
238
|
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
82
239
|
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
@@ -97,11 +254,15 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
97
254
|
catch (error) {
|
|
98
255
|
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
99
256
|
}
|
|
100
|
-
if (repairIntent?.state === '
|
|
257
|
+
if (repairIntent?.state === 'consumed') {
|
|
101
258
|
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
102
259
|
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
103
260
|
}
|
|
104
261
|
}
|
|
262
|
+
if (repairIntent?.state === 'passed'
|
|
263
|
+
&& (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
|
|
264
|
+
repairIntent = undefined;
|
|
265
|
+
}
|
|
105
266
|
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
106
267
|
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
107
268
|
}
|
|
@@ -159,6 +320,12 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
159
320
|
if (!transformed.ok)
|
|
160
321
|
return reject(transformed.code, transformed.message);
|
|
161
322
|
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
|
+
}
|
|
162
329
|
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
163
330
|
if (transformed.changed)
|
|
164
331
|
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
@@ -197,7 +364,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
197
364
|
lockAlreadyHeld: options.lockAlreadyHeld
|
|
198
365
|
};
|
|
199
366
|
}
|
|
200
|
-
function commitReviewSummaryProvenance(prepared, repoRoot) {
|
|
367
|
+
function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
|
|
201
368
|
if (prepared.result.status === 'failed' || !prepared.recovery)
|
|
202
369
|
return prepared.result;
|
|
203
370
|
try {
|
|
@@ -205,16 +372,40 @@ function commitReviewSummaryProvenance(prepared, repoRoot) {
|
|
|
205
372
|
if (!intent)
|
|
206
373
|
throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
207
374
|
let committed;
|
|
208
|
-
if (intent.state === '
|
|
209
|
-
|
|
375
|
+
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
376
|
+
return {
|
|
377
|
+
...prepared.result,
|
|
378
|
+
status: 'no-op',
|
|
379
|
+
changed: false,
|
|
380
|
+
artifactSha256: intent.finalArtifactSha256,
|
|
381
|
+
semanticDigest: intent.finalSemanticDigest,
|
|
382
|
+
error: null
|
|
383
|
+
};
|
|
210
384
|
}
|
|
211
|
-
else {
|
|
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') {
|
|
212
393
|
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
213
394
|
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
214
395
|
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
215
396
|
}
|
|
216
|
-
if (
|
|
217
|
-
|
|
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 });
|
|
403
|
+
}
|
|
404
|
+
else if (intent.state === 'full-finalizer-ready') {
|
|
405
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
406
|
+
}
|
|
407
|
+
if (!committed || committed.state !== 'passed')
|
|
408
|
+
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
218
409
|
return {
|
|
219
410
|
...prepared.result,
|
|
220
411
|
artifactSha256: committed.finalArtifactSha256,
|
|
@@ -223,6 +414,8 @@ function commitReviewSummaryProvenance(prepared, repoRoot) {
|
|
|
223
414
|
};
|
|
224
415
|
}
|
|
225
416
|
catch (error) {
|
|
417
|
+
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
418
|
+
throw error;
|
|
226
419
|
return { ...prepared.result, status: 'failed', error: {
|
|
227
420
|
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
228
421
|
} };
|
|
@@ -268,5 +461,5 @@ function finalizeReviewSummary(request, options = {}) {
|
|
|
268
461
|
return failed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
269
462
|
}
|
|
270
463
|
}
|
|
271
|
-
export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
|
|
464
|
+
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
|
|
272
465
|
//# sourceMappingURL=review-finalization.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
4
|
const NO_FOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
5
5
|
const DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
|
|
6
6
|
export class SecureFileError extends Error {
|
|
@@ -15,11 +15,8 @@ function conflict(message) {
|
|
|
15
15
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', message);
|
|
16
16
|
}
|
|
17
17
|
function sameStat(left, right) {
|
|
18
|
-
return left.dev === right.dev
|
|
19
|
-
&& left.
|
|
20
|
-
&& left.size === right.size
|
|
21
|
-
&& left.mtimeNs === right.mtimeNs
|
|
22
|
-
&& left.ctimeNs === right.ctimeNs;
|
|
18
|
+
return left.dev === right.dev && left.ino === right.ino && left.size === right.size
|
|
19
|
+
&& left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
23
20
|
}
|
|
24
21
|
function assertNoSymlinkAncestors(candidate) {
|
|
25
22
|
let current = path.dirname(candidate);
|
|
@@ -36,15 +33,12 @@ function assertNoSymlinkAncestors(candidate) {
|
|
|
36
33
|
export async function readStableFile(candidate, options) {
|
|
37
34
|
return readStableFileSync(candidate, options);
|
|
38
35
|
}
|
|
39
|
-
/** Shared bounded reader for synchronous domain commands and async workflow callers. */
|
|
40
36
|
export function readStableFileSync(candidate, options) {
|
|
41
|
-
if (!path.isAbsolute(candidate))
|
|
37
|
+
if (!path.isAbsolute(candidate))
|
|
42
38
|
conflict('candidate path must be absolute and terminal');
|
|
43
|
-
}
|
|
44
39
|
assertNoSymlinkAncestors(candidate);
|
|
45
40
|
let descriptor;
|
|
46
41
|
try {
|
|
47
|
-
// O_NONBLOCK lets fstat reject a FIFO even when it has no writer.
|
|
48
42
|
descriptor = fs.openSync(candidate, fs.constants.O_RDONLY | NO_FOLLOW | (fs.constants.O_NONBLOCK ?? 0));
|
|
49
43
|
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
50
44
|
if (!before.isFile())
|
|
@@ -63,18 +57,16 @@ export function readStableFileSync(candidate, options) {
|
|
|
63
57
|
if (!sameStat(before, after))
|
|
64
58
|
conflict('candidate identity or metadata changed during read');
|
|
65
59
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
66
|
-
if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256)
|
|
60
|
+
if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256)
|
|
67
61
|
conflict('candidate digest does not match the expected digest');
|
|
68
|
-
}
|
|
69
62
|
return { bytes, sha256, stat: after };
|
|
70
63
|
}
|
|
71
64
|
catch (error) {
|
|
72
65
|
if (error instanceof SecureFileError)
|
|
73
66
|
throw error;
|
|
74
67
|
const code = error.code;
|
|
75
|
-
if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL')
|
|
68
|
+
if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL')
|
|
76
69
|
conflict('candidate terminal is not a stable non-symlink file');
|
|
77
|
-
}
|
|
78
70
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', error instanceof Error ? error.message : String(error));
|
|
79
71
|
}
|
|
80
72
|
finally {
|
|
@@ -93,9 +85,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
93
85
|
catch {
|
|
94
86
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is missing');
|
|
95
87
|
}
|
|
96
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
88
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
97
89
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is not a real directory');
|
|
98
|
-
}
|
|
99
90
|
const next = path.dirname(current);
|
|
100
91
|
if (next === current)
|
|
101
92
|
break;
|
|
@@ -103,9 +94,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
103
94
|
}
|
|
104
95
|
try {
|
|
105
96
|
const existing = await fs.promises.lstat(target);
|
|
106
|
-
if (existing.isSymbolicLink() || !existing.isFile())
|
|
97
|
+
if (existing.isSymbolicLink() || !existing.isFile())
|
|
107
98
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative target is not a regular file');
|
|
108
|
-
}
|
|
109
99
|
}
|
|
110
100
|
catch (error) {
|
|
111
101
|
if (error.code !== 'ENOENT')
|
|
@@ -120,7 +110,6 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
120
110
|
await handle.chmod(mode);
|
|
121
111
|
await handle.close();
|
|
122
112
|
handle = undefined;
|
|
123
|
-
// Check after all asynchronous preparation; keep the final check/rename adjacent.
|
|
124
113
|
if (expectedSha256 !== undefined)
|
|
125
114
|
readStableFileSync(target, { maxBytes: 1024 * 1024, expectedSha256 });
|
|
126
115
|
fs.renameSync(temporary, target);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export const TASK_WORKFLOW_COMMANDS = Object.freeze({
|
|
2
2
|
'artifact-inspect': ['task-artifact', 'inspect'],
|
|
3
3
|
'artifact-init': ['task-artifact', 'init'],
|
|
4
|
+
'artifact-preflight': ['task-artifact', 'preflight'],
|
|
4
5
|
'artifact-finalize-local': ['task-artifact', 'finalize-local'],
|
|
6
|
+
'review-preflight': ['task-review', 'preflight'],
|
|
5
7
|
'review-finalize-summary': ['task-review', 'finalize-summary'],
|
|
6
8
|
event: ['task-event', null],
|
|
7
9
|
'ledger-finding-response': ['task-ledger', 'finding-respond'],
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
2
|
+
import { applyLedgerIntent } from './ledger-intents.js';
|
|
3
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
4
|
+
import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
|
|
5
|
+
function failed(code, message) {
|
|
6
|
+
return { status: 'failed', changed: false, error: { code, message } };
|
|
7
|
+
}
|
|
8
|
+
function isFailure(value) {
|
|
9
|
+
return Boolean(value) && typeof value === 'object' && value.status === 'failed';
|
|
10
|
+
}
|
|
11
|
+
function options(args, allowed) {
|
|
12
|
+
const values = {};
|
|
13
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
14
|
+
const flag = args[index];
|
|
15
|
+
if (flag === '--dry-run') {
|
|
16
|
+
if (!Object.hasOwn(allowed, flag) || values[flag] !== undefined)
|
|
17
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
18
|
+
values[flag] = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const key = allowed[flag];
|
|
22
|
+
if (!key || values[flag] !== undefined)
|
|
23
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
24
|
+
const value = args[++index];
|
|
25
|
+
if (value === undefined || value.startsWith('--'))
|
|
26
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${flag}' requires a value`);
|
|
27
|
+
if (flag === '--needs-implementation' && value !== 'true' && value !== 'false') {
|
|
28
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', "option '--needs-implementation' must be true or false");
|
|
29
|
+
}
|
|
30
|
+
values[flag] = value;
|
|
31
|
+
}
|
|
32
|
+
return values;
|
|
33
|
+
}
|
|
34
|
+
function value(values, flag) {
|
|
35
|
+
const candidate = values[flag];
|
|
36
|
+
return typeof candidate === 'string' ? candidate : undefined;
|
|
37
|
+
}
|
|
38
|
+
function required(values, flags) {
|
|
39
|
+
const missing = flags.find((flag) => value(values, flag) === undefined);
|
|
40
|
+
return missing ? failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${missing}' is required`) : null;
|
|
41
|
+
}
|
|
42
|
+
function resultForLock(error) {
|
|
43
|
+
if (error instanceof TaskExecutionLockError)
|
|
44
|
+
return failed(error.code, error.message);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
function ledgerIntent(operation, args) {
|
|
48
|
+
const [taskRef, kind] = args;
|
|
49
|
+
const mappings = {
|
|
50
|
+
'ledger-finding-response': {
|
|
51
|
+
kind: 'finding-respond', required: ['--id', '--round', '--status', '--evidence'],
|
|
52
|
+
flags: { '--id': 'id', '--round': 'round', '--status': 'status', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
53
|
+
},
|
|
54
|
+
'ledger-finding-review': {
|
|
55
|
+
kind: 'finding-review', required: ['--id', '--status', '--evidence'],
|
|
56
|
+
flags: { '--id': 'id', '--status': 'status', '--evidence': 'evidence', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
57
|
+
},
|
|
58
|
+
'ledger-finding-upsert': {
|
|
59
|
+
kind: 'finding-upsert', required: ['--stage', '--review-artifact', '--ordinal', '--severity', '--evidence'],
|
|
60
|
+
flags: { '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal', '--severity': 'severity', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
61
|
+
},
|
|
62
|
+
'decision-next-id': { kind: 'decision-next-id', required: [], flags: {} },
|
|
63
|
+
'decision-upsert': {
|
|
64
|
+
kind: 'decision-upsert', required: ['--id', '--stage', '--artifact'],
|
|
65
|
+
flags: { '--id': 'id', '--stage': 'stage', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const mapping = mappings[operation];
|
|
69
|
+
if (!mapping || !taskRef || kind === undefined)
|
|
70
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
|
|
71
|
+
const values = options(args, mapping.flags);
|
|
72
|
+
if (isFailure(values))
|
|
73
|
+
return values;
|
|
74
|
+
const missing = required(values, mapping.required);
|
|
75
|
+
if (missing)
|
|
76
|
+
return missing;
|
|
77
|
+
const dryRun = values['--dry-run'] === true;
|
|
78
|
+
if (mapping.kind === 'decision-next-id')
|
|
79
|
+
return { kind: mapping.kind, taskRef };
|
|
80
|
+
if (mapping.kind === 'finding-respond')
|
|
81
|
+
return {
|
|
82
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), round: Number(value(values, '--round')),
|
|
83
|
+
status: value(values, '--status'), evidence: value(values, '--evidence'), ...(dryRun ? { dryRun } : {})
|
|
84
|
+
};
|
|
85
|
+
if (mapping.kind === 'finding-review')
|
|
86
|
+
return {
|
|
87
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), status: value(values, '--status'),
|
|
88
|
+
evidence: value(values, '--evidence'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
89
|
+
...(dryRun ? { dryRun } : {})
|
|
90
|
+
};
|
|
91
|
+
if (mapping.kind === 'finding-upsert')
|
|
92
|
+
return {
|
|
93
|
+
kind: mapping.kind, taskRef, stage: value(values, '--stage'), reviewArtifact: value(values, '--review-artifact'),
|
|
94
|
+
ordinal: Number(value(values, '--ordinal')), severity: value(values, '--severity'), evidence: value(values, '--evidence'),
|
|
95
|
+
...(dryRun ? { dryRun } : {})
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), stage: value(values, '--stage'),
|
|
99
|
+
artifact: value(values, '--artifact'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
100
|
+
...(dryRun ? { dryRun } : {})
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function warningIntent(args) {
|
|
104
|
+
const [taskRef, kind] = args;
|
|
105
|
+
if (!taskRef || kind !== 'add')
|
|
106
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
|
|
107
|
+
const values = options(args, {
|
|
108
|
+
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
109
|
+
'--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
|
|
110
|
+
});
|
|
111
|
+
if (isFailure(values))
|
|
112
|
+
return values;
|
|
113
|
+
const missing = required(values, ['--step', '--severity', '--code', '--target', '--message', '--action']);
|
|
114
|
+
if (missing)
|
|
115
|
+
return missing;
|
|
116
|
+
return {
|
|
117
|
+
kind: 'add', taskRef, step: value(values, '--step'), severity: value(values, '--severity'),
|
|
118
|
+
code: value(values, '--code'), target: value(values, '--target'), message: value(values, '--message'),
|
|
119
|
+
action: value(values, '--action'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The caller has already selected and authorized the execution boundary.
|
|
124
|
+
* This function deliberately invokes task domains in that process: starting a
|
|
125
|
+
* second CLI process would discard the broker binding and could misclassify a
|
|
126
|
+
* sandbox process with cleared environment variables as direct-host.
|
|
127
|
+
*/
|
|
128
|
+
export function dispatchWorkflowCommand(repoRoot, operation, args) {
|
|
129
|
+
const taskRef = args[0];
|
|
130
|
+
if (!taskRef)
|
|
131
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task ref is required');
|
|
132
|
+
try {
|
|
133
|
+
if (operation === 'invalidation-reconcile') {
|
|
134
|
+
const values = options(args, { '--max-targets': 'maxTargets', '--dry-run': 'dryRun' });
|
|
135
|
+
if (isFailure(values))
|
|
136
|
+
return values;
|
|
137
|
+
const maxTargets = value(values, '--max-targets');
|
|
138
|
+
if (maxTargets !== undefined && (!Number.isSafeInteger(Number(maxTargets)) || Number(maxTargets) < 1)) {
|
|
139
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', '--max-targets must be a positive integer');
|
|
140
|
+
}
|
|
141
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-invalidation.reconcile', () => reconcileTaskInvalidation(taskRef, {
|
|
142
|
+
repoRoot, ...(maxTargets === undefined ? {} : { maxTargets: Number(maxTargets) }),
|
|
143
|
+
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
if (operation === 'warning-add') {
|
|
147
|
+
const intent = warningIntent(args);
|
|
148
|
+
if (isFailure(intent))
|
|
149
|
+
return intent;
|
|
150
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-warning.add', () => applyWorkflowWarningIntent(intent, { repoRoot }));
|
|
151
|
+
}
|
|
152
|
+
const intent = ledgerIntent(operation, args);
|
|
153
|
+
if (isFailure(intent))
|
|
154
|
+
return intent;
|
|
155
|
+
return withTaskExecutionLock(repoRoot, taskRef, `task-ledger.${intent.kind}`, () => applyLedgerIntent(intent, { repoRoot }));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return resultForLock(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=workflow-dispatch.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.0",
|
|
4
|
+
"sourceInputHash": "c67e66a46040d5246d67c751af605b796cd61913b98add01ff327829c01ce3db",
|
|
5
|
+
"internalExecutableBuildHash": "f6b4be06df12111b3a7614d335c9e802049c3cea194c6e7c040f3251188c694f"
|
|
6
6
|
}
|
|
@@ -36,7 +36,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
36
36
|
'task-create': ['input'],
|
|
37
37
|
'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
|
|
38
38
|
'sandbox-control': ['serve', 'execute', 'recover', 'client'],
|
|
39
|
-
'host-control': ['serve', 'status', 'install', 'uninstall'],
|
|
40
39
|
'agent-client': ['next-steps', 'model-selection'],
|
|
41
40
|
'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
|
|
42
41
|
'codex-sandbox-controller': ['verify-context', 'run'],
|
|
@@ -47,7 +46,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
47
46
|
'platform-security': ['read', 'dismiss'],
|
|
48
47
|
'platform-metadata': ['init-labels', 'init-milestones'],
|
|
49
48
|
'platform-context': ['resolve'],
|
|
50
|
-
'platform-comment': ['list', 'owner', 'backfill', 'sync'],
|
|
49
|
+
'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
|
|
51
50
|
'platform-issue': ['inspect', 'create', 'bind', 'sync'],
|
|
52
51
|
'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
|
|
53
52
|
'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
|
|
@@ -57,9 +56,9 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
57
56
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
58
57
|
'task-warning': ['list', 'add', 'set-status'],
|
|
59
58
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
60
|
-
'task-artifact': ['inspect', 'init', 'finalize-local'],
|
|
59
|
+
'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
|
|
61
60
|
'task-orchestration': ['status', 'progress'],
|
|
62
|
-
'task-review': ['finalize-summary'],
|
|
61
|
+
'task-review': ['preflight', 'finalize-summary'],
|
|
63
62
|
'task-event': ['event'],
|
|
64
63
|
'task-invalidation': ['reconcile'],
|
|
65
64
|
'task-override': ['diagnose', 'issue', 'consume'],
|