@h9-foundry/agentforge-cli 0.9.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/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +539 -19
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +1794 -72
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentManifestSchema, agentOutputSchema, designArtifactSchema, githubActionsEvidenceSchema, implementationArtifactSchema, implementationInventorySchema, incidentArtifactSchema, incidentEvidenceNormalizationSchema, incidentRequestSchema, maintenanceArtifactSchema, maintenanceEvidenceNormalizationSchema, maintenanceRequestSchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, releaseApprovalRecommendationSchema, releaseArtifactSchema, releaseEvidenceNormalizationSchema, releaseRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
3
|
+
import { agentManifestSchema, agentOutputSchema, attestationVerificationEvidenceSchema, buildkiteCiEvidenceExportSchema, ciEvidenceSchema, deploymentGateArtifactSchema, deploymentGateEvidenceNormalizationSchema, deploymentRequestSchema, dependencyIntegrityEvidenceSchema, genericCiEvidenceExportSchema, designArtifactSchema, githubActionsEvidenceSchema, gitlabCiEvidenceExportSchema, jenkinsCiEvidenceExportSchema, implementationArtifactSchema, implementationInventorySchema, incidentArtifactSchema, incidentEvidenceNormalizationSchema, incidentRequestSchema, maintenanceArtifactSchema, maintenanceEvidenceNormalizationSchema, maintenanceRequestSchema, pipelineArtifactSchema, pipelineEvidenceNormalizationSchema, pipelineRequestSchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, promotionApprovalArtifactSchema, promotionApprovalEvidenceNormalizationSchema, promotionRequestSchema, releaseApprovalRecommendationSchema, releaseArtifactSchema, releaseCiEvidenceSummarySchema, releaseEvidenceNormalizationSchema, releaseRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
4
4
|
const contextCollectorAgent = {
|
|
5
5
|
manifest: agentManifestSchema.parse({
|
|
6
6
|
version: 1,
|
|
@@ -99,7 +99,14 @@ function resolveWorkspacePackage(root, packageName) {
|
|
|
99
99
|
}
|
|
100
100
|
return {};
|
|
101
101
|
}
|
|
102
|
+
const dependencyManifestSections = [
|
|
103
|
+
"dependencies",
|
|
104
|
+
"devDependencies",
|
|
105
|
+
"peerDependencies",
|
|
106
|
+
"optionalDependencies"
|
|
107
|
+
];
|
|
102
108
|
const allowedValidationScriptNames = new Set(["test", "lint", "typecheck", "build", "build:packages", "release:verify"]);
|
|
109
|
+
const fallbackValidationPackageManagers = ["pnpm", "npm", "yarn"];
|
|
103
110
|
function normalizeRequestedCommand(command) {
|
|
104
111
|
return command.trim().replace(/\s+/g, " ");
|
|
105
112
|
}
|
|
@@ -109,20 +116,29 @@ function buildValidationCommand(packageManager, scriptName, packageName) {
|
|
|
109
116
|
}
|
|
110
117
|
return `${packageManager} ${scriptName}`;
|
|
111
118
|
}
|
|
119
|
+
function resolveValidationCommandManagers(packageManager, packageName) {
|
|
120
|
+
if (packageManager !== "unknown") {
|
|
121
|
+
return [packageManager];
|
|
122
|
+
}
|
|
123
|
+
// Generic repos without lockfiles still need deterministic command matching for bounded root scripts.
|
|
124
|
+
return packageName ? ["pnpm"] : fallbackValidationPackageManagers;
|
|
125
|
+
}
|
|
112
126
|
function collectValidationCommands(repoRoot, packageManager, packageScopes) {
|
|
113
127
|
const discoveredValidationCommands = [];
|
|
114
128
|
const registerScripts = (packageJsonPath, source, packageName) => {
|
|
115
129
|
const scripts = parsePackageScripts(packageJsonPath);
|
|
116
130
|
for (const scriptName of Object.keys(scripts)) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
for (const commandPackageManager of resolveValidationCommandManagers(packageManager, packageName)) {
|
|
132
|
+
const command = buildValidationCommand(commandPackageManager, scriptName, packageName);
|
|
133
|
+
discoveredValidationCommands.push({
|
|
134
|
+
command,
|
|
135
|
+
source,
|
|
136
|
+
classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
|
|
137
|
+
reason: allowedValidationScriptNames.has(scriptName)
|
|
138
|
+
? "Discovered from a bounded repository script; execution would still require approval."
|
|
139
|
+
: "Command is not in the bounded allowlist for workflow validation."
|
|
140
|
+
});
|
|
141
|
+
}
|
|
126
142
|
}
|
|
127
143
|
};
|
|
128
144
|
if (!repoRoot) {
|
|
@@ -140,6 +156,183 @@ function collectValidationCommands(repoRoot, packageManager, packageScopes) {
|
|
|
140
156
|
}
|
|
141
157
|
return discoveredValidationCommands;
|
|
142
158
|
}
|
|
159
|
+
function findDependencyLockfile(repoRoot, packageManager) {
|
|
160
|
+
if (!repoRoot) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const orderedCandidates = packageManager === "pnpm"
|
|
164
|
+
? ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"]
|
|
165
|
+
: packageManager === "npm"
|
|
166
|
+
? ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]
|
|
167
|
+
: packageManager === "yarn"
|
|
168
|
+
? ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"]
|
|
169
|
+
: ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
170
|
+
return orderedCandidates.find((lockfilePath) => existsSync(join(repoRoot, lockfilePath)));
|
|
171
|
+
}
|
|
172
|
+
function readPackageManifestDependencies(repoRoot, manifestPath) {
|
|
173
|
+
if (!repoRoot) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const absolutePath = join(repoRoot, manifestPath);
|
|
177
|
+
if (!existsSync(absolutePath)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const parsed = JSON.parse(readFileSync(absolutePath, "utf8"));
|
|
181
|
+
if (!isRecord(parsed)) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const packageName = typeof parsed.name === "string" && parsed.name.length > 0 ? parsed.name : manifestPath.replace(/\/package\.json$/, "");
|
|
185
|
+
const dependencyEntries = [];
|
|
186
|
+
for (const section of dependencyManifestSections) {
|
|
187
|
+
const sectionValue = parsed[section];
|
|
188
|
+
if (!isRecord(sectionValue)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
for (const [dependencyName, requestedVersion] of Object.entries(sectionValue)) {
|
|
192
|
+
if (typeof requestedVersion !== "string") {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
dependencyEntries.push({
|
|
196
|
+
manifestPath,
|
|
197
|
+
packageName,
|
|
198
|
+
dependencyName,
|
|
199
|
+
dependencyType: section,
|
|
200
|
+
requestedVersion
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
packageName,
|
|
206
|
+
dependencyEntries
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function resolveDependencyManifestPaths(repoRoot, candidatePaths) {
|
|
210
|
+
if (!repoRoot) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const normalizedCandidates = candidatePaths.length > 0
|
|
214
|
+
? candidatePaths
|
|
215
|
+
: existsSync(join(repoRoot, "package.json"))
|
|
216
|
+
? ["package.json"]
|
|
217
|
+
: [];
|
|
218
|
+
const manifestPaths = normalizedCandidates.flatMap((candidatePath) => {
|
|
219
|
+
const normalizedCandidate = candidatePath.replace(/^\.\//, "");
|
|
220
|
+
const manifestPath = normalizedCandidate.endsWith("package.json")
|
|
221
|
+
? normalizedCandidate
|
|
222
|
+
: `${normalizedCandidate.replace(/\/$/, "")}/package.json`;
|
|
223
|
+
return existsSync(join(repoRoot, manifestPath)) ? [manifestPath] : [];
|
|
224
|
+
});
|
|
225
|
+
return [...new Set(manifestPaths)];
|
|
226
|
+
}
|
|
227
|
+
function collectDependencyIntegrityEvidence(repoRoot, packageManager, manifestPaths) {
|
|
228
|
+
if (!repoRoot || manifestPaths.length === 0) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const manifests = manifestPaths
|
|
232
|
+
.map((manifestPath) => readPackageManifestDependencies(repoRoot, manifestPath))
|
|
233
|
+
.filter((manifest) => Boolean(manifest));
|
|
234
|
+
if (manifests.length === 0) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
const inventoryEntries = manifests.flatMap((manifest) => manifest.dependencyEntries);
|
|
238
|
+
const packageNames = [...new Set(manifests.map((manifest) => manifest.packageName))];
|
|
239
|
+
const lockfilePath = findDependencyLockfile(repoRoot, packageManager);
|
|
240
|
+
const integrityStatus = lockfilePath
|
|
241
|
+
? "verified-lockfile"
|
|
242
|
+
: inventoryEntries.length === 0
|
|
243
|
+
? "manifest-only"
|
|
244
|
+
: "missing-lockfile";
|
|
245
|
+
return [
|
|
246
|
+
dependencyIntegrityEvidenceSchema.parse({
|
|
247
|
+
inventoryFormat: "workspace-inventory",
|
|
248
|
+
packageManager,
|
|
249
|
+
integrityStatus,
|
|
250
|
+
lockfilePath,
|
|
251
|
+
manifestPaths,
|
|
252
|
+
packageNames,
|
|
253
|
+
packageCount: packageNames.length,
|
|
254
|
+
dependencyEntryCount: inventoryEntries.length,
|
|
255
|
+
inventoryEntries,
|
|
256
|
+
provenanceSource: "workspace-scan",
|
|
257
|
+
provenanceRefs: [...new Set([...manifestPaths, ...(lockfilePath ? [lockfilePath] : [])])]
|
|
258
|
+
})
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
function buildDependencyIntegritySignals(evidenceEntries) {
|
|
262
|
+
return evidenceEntries.flatMap((evidence) => {
|
|
263
|
+
const signals = [
|
|
264
|
+
`Dependency inventory covers ${evidence.packageCount} manifest(s) with ${evidence.dependencyEntryCount} declared dependency entr${evidence.dependencyEntryCount === 1 ? "y" : "ies"}.`
|
|
265
|
+
];
|
|
266
|
+
if (evidence.integrityStatus === "verified-lockfile" && evidence.lockfilePath) {
|
|
267
|
+
signals.push(`Workspace dependency integrity is verified against ${evidence.lockfilePath}.`);
|
|
268
|
+
}
|
|
269
|
+
else if (evidence.integrityStatus === "missing-lockfile") {
|
|
270
|
+
signals.push("Workspace dependency manifests were found without a recognized lockfile.");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
signals.push("Workspace dependency inventory is manifest-only and does not include lockfile verification.");
|
|
274
|
+
}
|
|
275
|
+
return signals;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function loadAttestationVerificationEvidence(bundlePath) {
|
|
279
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
283
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
284
|
+
const result = attestationVerificationEvidenceSchema.safeParse(candidate);
|
|
285
|
+
return result.success ? result.data : undefined;
|
|
286
|
+
}
|
|
287
|
+
function normalizeAttestationVerificationEvidence(repoRoot, evidenceSources) {
|
|
288
|
+
const seen = new Set();
|
|
289
|
+
const normalized = [];
|
|
290
|
+
for (const pathValue of evidenceSources) {
|
|
291
|
+
if (!repoRoot) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const evidence = loadAttestationVerificationEvidence(join(repoRoot, pathValue));
|
|
295
|
+
if (!evidence) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const key = `${evidence.verifier}:${evidence.subject}:${evidence.status}:${evidence.sourcePath ?? pathValue}`;
|
|
299
|
+
if (seen.has(key)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
seen.add(key);
|
|
303
|
+
normalized.push(evidence);
|
|
304
|
+
}
|
|
305
|
+
return normalized;
|
|
306
|
+
}
|
|
307
|
+
function buildReleaseTrustSummary(evidenceEntries) {
|
|
308
|
+
if (evidenceEntries.length === 0) {
|
|
309
|
+
return ["Trusted publishing remains reviewed separately; no attestation verification evidence was supplied."];
|
|
310
|
+
}
|
|
311
|
+
const failedCount = evidenceEntries.filter((entry) => entry.status === "failed").length;
|
|
312
|
+
const verifiedCount = evidenceEntries.filter((entry) => entry.status === "verified").length;
|
|
313
|
+
const skippedCount = evidenceEntries.filter((entry) => entry.status === "skipped").length;
|
|
314
|
+
const summary = [];
|
|
315
|
+
if (verifiedCount > 0) {
|
|
316
|
+
summary.push(`Verified ${verifiedCount} attestation or provenance evidence export${verifiedCount === 1 ? "" : "s"}.`);
|
|
317
|
+
}
|
|
318
|
+
if (failedCount > 0) {
|
|
319
|
+
summary.push(`Detected ${failedCount} attestation verification failure${failedCount === 1 ? "" : "s"} that require release follow-up.`);
|
|
320
|
+
}
|
|
321
|
+
if (skippedCount > 0) {
|
|
322
|
+
summary.push(`Skipped ${skippedCount} attestation verification evidence export${skippedCount === 1 ? "" : "s"} based on the supplied local evidence.`);
|
|
323
|
+
}
|
|
324
|
+
summary.push("Trusted publishing remains reviewed separately from bounded attestation verification.");
|
|
325
|
+
return summary;
|
|
326
|
+
}
|
|
327
|
+
function resolveReleaseTrustStatus(evidenceEntries) {
|
|
328
|
+
if (evidenceEntries.some((entry) => entry.status === "failed")) {
|
|
329
|
+
return "attestation-verification-failed";
|
|
330
|
+
}
|
|
331
|
+
if (evidenceEntries.some((entry) => entry.status === "verified")) {
|
|
332
|
+
return "attestation-verified-trusted-publishing-reviewed-separately";
|
|
333
|
+
}
|
|
334
|
+
return "trusted-publishing-reviewed-separately";
|
|
335
|
+
}
|
|
143
336
|
function loadBundleArtifactKinds(bundlePath) {
|
|
144
337
|
if (!existsSync(bundlePath)) {
|
|
145
338
|
return [];
|
|
@@ -152,6 +345,121 @@ function loadBundleArtifactKinds(bundlePath) {
|
|
|
152
345
|
.map((artifact) => (isRecord(artifact) && typeof artifact.artifactKind === "string" ? artifact.artifactKind : undefined))
|
|
153
346
|
.filter((artifactKind) => Boolean(artifactKind));
|
|
154
347
|
}
|
|
348
|
+
function loadBundleLifecycleArtifact(bundlePath, artifactKind) {
|
|
349
|
+
if (!existsSync(bundlePath)) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
353
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
return parsed.lifecycleArtifacts.find((artifact) => isRecord(artifact) && artifact.artifactKind === artifactKind);
|
|
357
|
+
}
|
|
358
|
+
function evaluateReleaseReportReadiness(bundlePath) {
|
|
359
|
+
const artifact = loadBundleLifecycleArtifact(bundlePath, "release-report");
|
|
360
|
+
if (!artifact) {
|
|
361
|
+
return {
|
|
362
|
+
ready: false,
|
|
363
|
+
detail: `Release report bundle is missing a release-report artifact: ${bundlePath}`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const parsed = releaseArtifactSchema.safeParse(artifact);
|
|
367
|
+
if (!parsed.success) {
|
|
368
|
+
return {
|
|
369
|
+
ready: false,
|
|
370
|
+
detail: `Release report bundle could not be parsed as a bounded release-report artifact: ${bundlePath}`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const releaseArtifact = parsed.data;
|
|
374
|
+
if (releaseArtifact.status !== "complete") {
|
|
375
|
+
return {
|
|
376
|
+
ready: false,
|
|
377
|
+
detail: `Release report ${bundlePath} is ${releaseArtifact.status} and cannot satisfy a deployment gate yet.`
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (releaseArtifact.payload.readinessStatus !== "ready") {
|
|
381
|
+
return {
|
|
382
|
+
ready: false,
|
|
383
|
+
detail: `Release report ${bundlePath} is ${releaseArtifact.payload.readinessStatus} and cannot satisfy a deployment gate yet.`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
ready: true,
|
|
388
|
+
detail: `Using ${bundlePath} as a ready release-report reference.`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function evaluatePipelineReportReadiness(bundlePath) {
|
|
392
|
+
const artifact = loadBundleLifecycleArtifact(bundlePath, "pipeline-report");
|
|
393
|
+
if (!artifact) {
|
|
394
|
+
return {
|
|
395
|
+
ready: false,
|
|
396
|
+
detail: `Pipeline report bundle is missing a pipeline-report artifact: ${bundlePath}`
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const parsed = pipelineArtifactSchema.safeParse(artifact);
|
|
400
|
+
if (!parsed.success) {
|
|
401
|
+
return {
|
|
402
|
+
ready: false,
|
|
403
|
+
detail: `Pipeline report bundle could not be parsed as a bounded pipeline-report artifact: ${bundlePath}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const pipelineArtifact = parsed.data;
|
|
407
|
+
if (pipelineArtifact.status !== "complete") {
|
|
408
|
+
return {
|
|
409
|
+
ready: false,
|
|
410
|
+
detail: `Pipeline report ${bundlePath} is ${pipelineArtifact.status} and cannot satisfy a deployment gate yet.`
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (pipelineArtifact.payload.reviewStatus !== "ready") {
|
|
414
|
+
return {
|
|
415
|
+
ready: false,
|
|
416
|
+
detail: `Pipeline report ${bundlePath} is ${pipelineArtifact.payload.reviewStatus} and cannot satisfy a deployment gate yet.`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
ready: true,
|
|
421
|
+
detail: `Using ${bundlePath} as a ready pipeline-report reference.`
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function evaluateDeploymentGateApprovalReadiness(bundlePath, targetEnvironment) {
|
|
425
|
+
const artifact = loadBundleLifecycleArtifact(bundlePath, "deployment-gate-report");
|
|
426
|
+
if (!artifact) {
|
|
427
|
+
return {
|
|
428
|
+
ready: false,
|
|
429
|
+
detail: `Deployment gate bundle is missing a deployment-gate-report artifact: ${bundlePath}`
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const parsed = deploymentGateArtifactSchema.safeParse(artifact);
|
|
433
|
+
if (!parsed.success) {
|
|
434
|
+
return {
|
|
435
|
+
ready: false,
|
|
436
|
+
detail: `Deployment gate bundle could not be parsed as a bounded deployment-gate-report artifact: ${bundlePath}`
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const deploymentGateArtifact = parsed.data;
|
|
440
|
+
if (deploymentGateArtifact.status !== "complete") {
|
|
441
|
+
return {
|
|
442
|
+
ready: false,
|
|
443
|
+
detail: `Deployment gate report ${bundlePath} is ${deploymentGateArtifact.status} and cannot satisfy promotion approval yet.`
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (deploymentGateArtifact.payload.gateStatus !== "ready_for_approval") {
|
|
447
|
+
return {
|
|
448
|
+
ready: false,
|
|
449
|
+
detail: `Deployment gate report ${bundlePath} is ${deploymentGateArtifact.payload.gateStatus} and cannot satisfy promotion approval yet.`
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (targetEnvironment && deploymentGateArtifact.payload.targetEnvironment !== targetEnvironment) {
|
|
453
|
+
return {
|
|
454
|
+
ready: false,
|
|
455
|
+
detail: `Deployment gate report ${bundlePath} targets ${deploymentGateArtifact.payload.targetEnvironment}, not ${targetEnvironment}.`
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
ready: true,
|
|
460
|
+
detail: `Using ${bundlePath} as a ready deployment-gate-report reference for ${targetEnvironment ?? deploymentGateArtifact.payload.targetEnvironment}.`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
155
463
|
function loadBundleArtifactPayloadPaths(bundlePath) {
|
|
156
464
|
if (!existsSync(bundlePath)) {
|
|
157
465
|
return [];
|
|
@@ -247,6 +555,292 @@ function normalizeGitHubActionsEvidence(repoRoot, evidenceSources) {
|
|
|
247
555
|
provenanceRefs
|
|
248
556
|
};
|
|
249
557
|
}
|
|
558
|
+
function mapGitHubActionsEvidenceToCiEvidence(evidence) {
|
|
559
|
+
return ciEvidenceSchema.parse({
|
|
560
|
+
platform: "github-actions",
|
|
561
|
+
providerName: "GitHub Actions",
|
|
562
|
+
host: "github.com",
|
|
563
|
+
repository: evidence.repository,
|
|
564
|
+
pipelineName: evidence.workflowName,
|
|
565
|
+
pipelineRunId: `${evidence.workflowRunId}`,
|
|
566
|
+
runAttempt: evidence.runAttempt,
|
|
567
|
+
event: evidence.event,
|
|
568
|
+
branch: evidence.headBranch,
|
|
569
|
+
commitSha: evidence.headSha,
|
|
570
|
+
status: evidence.status,
|
|
571
|
+
conclusion: evidence.conclusion,
|
|
572
|
+
htmlUrl: evidence.htmlUrl,
|
|
573
|
+
jobs: evidence.jobs,
|
|
574
|
+
artifacts: [],
|
|
575
|
+
provenanceSource: "local-export"
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
function loadGitLabCiEvidenceExport(bundlePath) {
|
|
579
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
583
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
584
|
+
const result = gitlabCiEvidenceExportSchema.safeParse(candidate);
|
|
585
|
+
return result.success ? result.data : undefined;
|
|
586
|
+
}
|
|
587
|
+
function loadBuildkiteCiEvidenceExport(bundlePath) {
|
|
588
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
592
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
593
|
+
const result = buildkiteCiEvidenceExportSchema.safeParse(candidate);
|
|
594
|
+
return result.success ? result.data : undefined;
|
|
595
|
+
}
|
|
596
|
+
function loadGenericCiEvidenceExport(bundlePath) {
|
|
597
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
601
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
602
|
+
const result = genericCiEvidenceExportSchema.safeParse(candidate);
|
|
603
|
+
return result.success ? result.data : undefined;
|
|
604
|
+
}
|
|
605
|
+
function loadJenkinsCiEvidenceExport(bundlePath) {
|
|
606
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
610
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
611
|
+
const result = jenkinsCiEvidenceExportSchema.safeParse(candidate);
|
|
612
|
+
return result.success ? result.data : undefined;
|
|
613
|
+
}
|
|
614
|
+
function mapGitLabCiStatus(status) {
|
|
615
|
+
switch (status) {
|
|
616
|
+
case "pending":
|
|
617
|
+
return { status: "queued" };
|
|
618
|
+
case "running":
|
|
619
|
+
return { status: "in_progress" };
|
|
620
|
+
case "success":
|
|
621
|
+
return { status: "completed", conclusion: "success" };
|
|
622
|
+
case "failed":
|
|
623
|
+
return { status: "completed", conclusion: "failure" };
|
|
624
|
+
case "canceled":
|
|
625
|
+
return { status: "completed", conclusion: "cancelled" };
|
|
626
|
+
case "skipped":
|
|
627
|
+
return { status: "completed", conclusion: "skipped" };
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function normalizeGitLabCiEvidence(repoRoot, evidenceSources) {
|
|
631
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
632
|
+
if (!repoRoot) {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
const normalized = loadGitLabCiEvidenceExport(join(repoRoot, pathValue));
|
|
636
|
+
if (!normalized) {
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
639
|
+
const pipelineStatus = mapGitLabCiStatus(normalized.status);
|
|
640
|
+
const jobs = normalized.jobs.map((job) => {
|
|
641
|
+
const jobStatus = mapGitLabCiStatus(job.status);
|
|
642
|
+
return {
|
|
643
|
+
name: job.name,
|
|
644
|
+
status: jobStatus.status,
|
|
645
|
+
conclusion: jobStatus.conclusion,
|
|
646
|
+
htmlUrl: job.webUrl,
|
|
647
|
+
startedAt: job.startedAt,
|
|
648
|
+
completedAt: job.completedAt
|
|
649
|
+
};
|
|
650
|
+
});
|
|
651
|
+
const evidence = ciEvidenceSchema.parse({
|
|
652
|
+
platform: "gitlab-ci",
|
|
653
|
+
host: normalized.host,
|
|
654
|
+
repository: normalized.projectPath,
|
|
655
|
+
pipelineName: normalized.pipelineName,
|
|
656
|
+
pipelineRunId: `${normalized.pipelineId}`,
|
|
657
|
+
runAttempt: normalized.runAttempt,
|
|
658
|
+
event: normalized.event,
|
|
659
|
+
branch: normalized.branch,
|
|
660
|
+
commitSha: normalized.commitSha,
|
|
661
|
+
status: pipelineStatus.status,
|
|
662
|
+
conclusion: pipelineStatus.conclusion,
|
|
663
|
+
htmlUrl: normalized.webUrl,
|
|
664
|
+
jobs,
|
|
665
|
+
artifacts: [],
|
|
666
|
+
provenanceSource: "local-export"
|
|
667
|
+
});
|
|
668
|
+
return [evidence];
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
function normalizeGenericCiEvidence(repoRoot, evidenceSources) {
|
|
672
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
673
|
+
if (!repoRoot) {
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
const normalized = loadGenericCiEvidenceExport(join(repoRoot, pathValue));
|
|
677
|
+
if (!normalized) {
|
|
678
|
+
return [];
|
|
679
|
+
}
|
|
680
|
+
const evidence = ciEvidenceSchema.parse({
|
|
681
|
+
platform: "generic-ci",
|
|
682
|
+
providerName: normalized.providerName,
|
|
683
|
+
host: normalized.host,
|
|
684
|
+
repository: normalized.repository,
|
|
685
|
+
pipelineName: normalized.pipelineName,
|
|
686
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
687
|
+
runAttempt: normalized.runAttempt,
|
|
688
|
+
event: normalized.event,
|
|
689
|
+
branch: normalized.branch,
|
|
690
|
+
commitSha: normalized.commitSha,
|
|
691
|
+
status: normalized.status,
|
|
692
|
+
conclusion: normalized.conclusion,
|
|
693
|
+
htmlUrl: normalized.htmlUrl,
|
|
694
|
+
jobs: normalized.jobs,
|
|
695
|
+
artifacts: normalized.artifacts,
|
|
696
|
+
provenanceSource: "local-export"
|
|
697
|
+
});
|
|
698
|
+
return [evidence];
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function normalizeBuildkiteCiEvidence(repoRoot, evidenceSources) {
|
|
702
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
703
|
+
if (!repoRoot) {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
const normalized = loadBuildkiteCiEvidenceExport(join(repoRoot, pathValue));
|
|
707
|
+
if (!normalized) {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
const evidence = ciEvidenceSchema.parse({
|
|
711
|
+
platform: "buildkite",
|
|
712
|
+
providerName: "Buildkite",
|
|
713
|
+
host: normalized.host,
|
|
714
|
+
repository: normalized.repository,
|
|
715
|
+
pipelineName: normalized.pipelineName,
|
|
716
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
717
|
+
runAttempt: normalized.runAttempt,
|
|
718
|
+
event: normalized.event,
|
|
719
|
+
branch: normalized.branch,
|
|
720
|
+
commitSha: normalized.commitSha,
|
|
721
|
+
status: normalized.status,
|
|
722
|
+
conclusion: normalized.conclusion,
|
|
723
|
+
htmlUrl: normalized.htmlUrl,
|
|
724
|
+
jobs: normalized.jobs,
|
|
725
|
+
artifacts: normalized.artifacts,
|
|
726
|
+
provenanceSource: "local-export"
|
|
727
|
+
});
|
|
728
|
+
return [evidence];
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function normalizeJenkinsCiEvidence(repoRoot, evidenceSources) {
|
|
732
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
733
|
+
if (!repoRoot) {
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
const normalized = loadJenkinsCiEvidenceExport(join(repoRoot, pathValue));
|
|
737
|
+
if (!normalized) {
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
const evidence = ciEvidenceSchema.parse({
|
|
741
|
+
platform: "jenkins-ci",
|
|
742
|
+
providerName: "Jenkins",
|
|
743
|
+
host: normalized.host,
|
|
744
|
+
repository: normalized.repository,
|
|
745
|
+
pipelineName: normalized.pipelineName,
|
|
746
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
747
|
+
runAttempt: normalized.runAttempt,
|
|
748
|
+
event: normalized.event,
|
|
749
|
+
branch: normalized.branch,
|
|
750
|
+
commitSha: normalized.commitSha,
|
|
751
|
+
status: normalized.status,
|
|
752
|
+
conclusion: normalized.conclusion,
|
|
753
|
+
htmlUrl: normalized.htmlUrl,
|
|
754
|
+
jobs: normalized.jobs,
|
|
755
|
+
artifacts: normalized.artifacts,
|
|
756
|
+
provenanceSource: "local-export"
|
|
757
|
+
});
|
|
758
|
+
return [evidence];
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
function normalizeImportedCiEvidence(repoRoot, evidenceSources) {
|
|
762
|
+
const seen = new Set();
|
|
763
|
+
const normalized = [];
|
|
764
|
+
for (const pathValue of evidenceSources) {
|
|
765
|
+
if (!repoRoot) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const absolutePath = join(repoRoot, pathValue);
|
|
769
|
+
if (!existsSync(absolutePath) || !absolutePath.endsWith(".json")) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const evidence = (() => {
|
|
773
|
+
const githubActions = loadGitHubActionsEvidence(absolutePath);
|
|
774
|
+
if (githubActions) {
|
|
775
|
+
return mapGitHubActionsEvidenceToCiEvidence(githubActions);
|
|
776
|
+
}
|
|
777
|
+
const gitlab = loadGitLabCiEvidenceExport(absolutePath);
|
|
778
|
+
if (gitlab) {
|
|
779
|
+
return normalizeGitLabCiEvidence(repoRoot, [pathValue])[0];
|
|
780
|
+
}
|
|
781
|
+
const buildkite = loadBuildkiteCiEvidenceExport(absolutePath);
|
|
782
|
+
if (buildkite) {
|
|
783
|
+
return normalizeBuildkiteCiEvidence(repoRoot, [pathValue])[0];
|
|
784
|
+
}
|
|
785
|
+
const jenkins = loadJenkinsCiEvidenceExport(absolutePath);
|
|
786
|
+
if (jenkins) {
|
|
787
|
+
return normalizeJenkinsCiEvidence(repoRoot, [pathValue])[0];
|
|
788
|
+
}
|
|
789
|
+
const generic = loadGenericCiEvidenceExport(absolutePath);
|
|
790
|
+
if (generic) {
|
|
791
|
+
return normalizeGenericCiEvidence(repoRoot, [pathValue])[0];
|
|
792
|
+
}
|
|
793
|
+
return undefined;
|
|
794
|
+
})();
|
|
795
|
+
if (!evidence) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const key = `${evidence.platform}:${evidence.host}:${evidence.pipelineRunId}:${evidence.pipelineName}:${evidence.repository}`;
|
|
799
|
+
if (seen.has(key)) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
seen.add(key);
|
|
803
|
+
normalized.push(evidence);
|
|
804
|
+
}
|
|
805
|
+
return normalized;
|
|
806
|
+
}
|
|
807
|
+
function summarizeCiEvidenceFailures(evidence) {
|
|
808
|
+
const failedJobs = evidence.jobs
|
|
809
|
+
.filter((job) => job.status === "completed" && isFailingGitHubActionsConclusion(job.conclusion))
|
|
810
|
+
.map((job) => `${evidence.pipelineName} / ${job.name}`);
|
|
811
|
+
const runLevelFailure = failedJobs.length === 0 &&
|
|
812
|
+
evidence.status === "completed" &&
|
|
813
|
+
isFailingGitHubActionsConclusion(evidence.conclusion)
|
|
814
|
+
? [`${evidence.pipelineName} / pipeline-run`]
|
|
815
|
+
: [];
|
|
816
|
+
return [...failedJobs, ...runLevelFailure];
|
|
817
|
+
}
|
|
818
|
+
function formatCiEvidenceStatus(evidence) {
|
|
819
|
+
if (evidence.status === "completed" && evidence.conclusion) {
|
|
820
|
+
return evidence.conclusion;
|
|
821
|
+
}
|
|
822
|
+
return evidence.status;
|
|
823
|
+
}
|
|
824
|
+
function summarizeCiEvidenceForRelease(evidence) {
|
|
825
|
+
const provider = evidence.providerName ?? evidence.pipelineName;
|
|
826
|
+
const displayLabel = `${provider} (${evidence.platform}) pipeline \`${evidence.pipelineName}\` run \`${evidence.pipelineRunId}\``;
|
|
827
|
+
return releaseCiEvidenceSummarySchema.parse({
|
|
828
|
+
provider,
|
|
829
|
+
platform: evidence.platform,
|
|
830
|
+
host: evidence.host,
|
|
831
|
+
repository: evidence.repository,
|
|
832
|
+
pipelineName: evidence.pipelineName,
|
|
833
|
+
pipelineRunId: evidence.pipelineRunId,
|
|
834
|
+
status: evidence.status,
|
|
835
|
+
conclusion: evidence.conclusion,
|
|
836
|
+
branch: evidence.branch,
|
|
837
|
+
commitSha: evidence.commitSha,
|
|
838
|
+
failingChecks: summarizeCiEvidenceFailures(evidence),
|
|
839
|
+
provenanceSource: evidence.provenanceSource,
|
|
840
|
+
displayLabel,
|
|
841
|
+
statusSummary: `${displayLabel} completed from ${evidence.provenanceSource} evidence with ${formatCiEvidenceStatus(evidence)}.`
|
|
842
|
+
});
|
|
843
|
+
}
|
|
250
844
|
function derivePackageScope(pathValue) {
|
|
251
845
|
const segments = pathValue.split("/").filter(Boolean);
|
|
252
846
|
if (segments.length < 2) {
|
|
@@ -268,7 +862,7 @@ function getWorkflowInput(stateSlice, key) {
|
|
|
268
862
|
}
|
|
269
863
|
return stateSlice.workflowInputs[key];
|
|
270
864
|
}
|
|
271
|
-
function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], githubRefs = []) {
|
|
865
|
+
function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], scmRefs = [], githubRefs = []) {
|
|
272
866
|
return {
|
|
273
867
|
schemaVersion: state.version,
|
|
274
868
|
workflow: {
|
|
@@ -280,6 +874,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
|
|
|
280
874
|
runId: state.runId,
|
|
281
875
|
inputRefs: [...inputRefs],
|
|
282
876
|
issueRefs: [...issueRefs],
|
|
877
|
+
scmRefs: [...scmRefs],
|
|
283
878
|
githubRefs: [...githubRefs]
|
|
284
879
|
},
|
|
285
880
|
status: "complete",
|
|
@@ -308,7 +903,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
|
|
|
308
903
|
summary
|
|
309
904
|
};
|
|
310
905
|
}
|
|
311
|
-
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubRefs = []) {
|
|
906
|
+
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, scmRefs = [], githubRefs = []) {
|
|
312
907
|
return {
|
|
313
908
|
schemaVersion: state.version,
|
|
314
909
|
workflow: {
|
|
@@ -319,6 +914,7 @@ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubR
|
|
|
319
914
|
runId: state.runId,
|
|
320
915
|
inputRefs: [...inputRefs],
|
|
321
916
|
issueRefs: [...issueRefs],
|
|
917
|
+
scmRefs: [...scmRefs],
|
|
322
918
|
githubRefs: [...githubRefs]
|
|
323
919
|
},
|
|
324
920
|
status: "complete",
|
|
@@ -445,6 +1041,7 @@ const planningAnalystAgent = {
|
|
|
445
1041
|
outputSchema: agentOutputSchema,
|
|
446
1042
|
async execute({ state, stateSlice }) {
|
|
447
1043
|
const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
|
|
1044
|
+
const planningScmRefs = getWorkflowInput(stateSlice, "planningScmRefs") ?? [];
|
|
448
1045
|
const planningGithubRefs = getWorkflowInput(stateSlice, "planningGithubRefs") ?? [];
|
|
449
1046
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
450
1047
|
if (!planningRequest) {
|
|
@@ -469,7 +1066,7 @@ const planningAnalystAgent = {
|
|
|
469
1066
|
];
|
|
470
1067
|
const summary = `Planning brief scoped ${objectives.length} objective(s) for ${state.repo.name}.`;
|
|
471
1068
|
const planningBrief = planningArtifactSchema.parse({
|
|
472
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningGithubRefs),
|
|
1069
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningScmRefs, planningGithubRefs),
|
|
473
1070
|
artifactKind: "planning-brief",
|
|
474
1071
|
lifecycleDomain: "plan",
|
|
475
1072
|
workflow: {
|
|
@@ -1064,6 +1661,7 @@ const maintenanceIntakeAgent = {
|
|
|
1064
1661
|
const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
|
|
1065
1662
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1066
1663
|
const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
|
|
1664
|
+
const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
|
|
1067
1665
|
const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
|
|
1068
1666
|
if (!maintenanceRequest) {
|
|
1069
1667
|
throw new Error("maintenance-triage requires a validated maintenance request before runtime execution.");
|
|
@@ -1084,6 +1682,7 @@ const maintenanceIntakeAgent = {
|
|
|
1084
1682
|
issueRefs: [...new Set(maintenanceRequest.issueRefs)]
|
|
1085
1683
|
}),
|
|
1086
1684
|
maintenanceIssueRefs,
|
|
1685
|
+
maintenanceScmRefs,
|
|
1087
1686
|
maintenanceGithubRefs,
|
|
1088
1687
|
evidenceSourceCount: maintenanceRequest.dependencyAlertRefs.length +
|
|
1089
1688
|
maintenanceRequest.docsTaskRefs.length +
|
|
@@ -1256,6 +1855,7 @@ const maintenanceAnalystAgent = {
|
|
|
1256
1855
|
async execute({ state, stateSlice }) {
|
|
1257
1856
|
const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
|
|
1258
1857
|
const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
|
|
1858
|
+
const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
|
|
1259
1859
|
const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
|
|
1260
1860
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1261
1861
|
if (!maintenanceRequest) {
|
|
@@ -1320,7 +1920,7 @@ const maintenanceAnalystAgent = {
|
|
|
1320
1920
|
];
|
|
1321
1921
|
const summary = `Maintenance report prepared for ${maintenanceRequest.maintenanceGoal}.`;
|
|
1322
1922
|
const maintenanceReport = maintenanceArtifactSchema.parse({
|
|
1323
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceGithubRefs),
|
|
1923
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceScmRefs, maintenanceGithubRefs),
|
|
1324
1924
|
artifactKind: "maintenance-report",
|
|
1325
1925
|
lifecycleDomain: "maintain",
|
|
1326
1926
|
payload: {
|
|
@@ -1415,6 +2015,7 @@ const incidentAnalystAgent = {
|
|
|
1415
2015
|
const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
|
|
1416
2016
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1417
2017
|
const incidentIssueRefs = getWorkflowInput(stateSlice, "incidentIssueRefs") ?? [];
|
|
2018
|
+
const incidentScmRefs = getWorkflowInput(stateSlice, "incidentScmRefs") ?? [];
|
|
1418
2019
|
const incidentGithubRefs = getWorkflowInput(stateSlice, "incidentGithubRefs") ?? [];
|
|
1419
2020
|
if (!incidentRequest) {
|
|
1420
2021
|
throw new Error("incident-handoff requires validated incident inputs before incident analysis.");
|
|
@@ -1457,7 +2058,7 @@ const incidentAnalystAgent = {
|
|
|
1457
2058
|
];
|
|
1458
2059
|
const summary = `Incident brief prepared for ${incidentRequest.incidentSummary}.`;
|
|
1459
2060
|
const incidentBrief = incidentArtifactSchema.parse({
|
|
1460
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentGithubRefs),
|
|
2061
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentScmRefs, incidentGithubRefs),
|
|
1461
2062
|
artifactKind: "incident-brief",
|
|
1462
2063
|
lifecycleDomain: "operate",
|
|
1463
2064
|
redaction: {
|
|
@@ -1551,6 +2152,7 @@ const releaseIntakeAgent = {
|
|
|
1551
2152
|
async execute({ stateSlice }) {
|
|
1552
2153
|
const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
|
|
1553
2154
|
const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
|
|
2155
|
+
const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
|
|
1554
2156
|
const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
|
|
1555
2157
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1556
2158
|
if (!releaseRequest) {
|
|
@@ -1566,6 +2168,7 @@ const releaseIntakeAgent = {
|
|
|
1566
2168
|
metadata: {
|
|
1567
2169
|
...releaseRequestSchema.parse(releaseRequest),
|
|
1568
2170
|
releaseIssueRefs,
|
|
2171
|
+
releaseScmRefs,
|
|
1569
2172
|
releaseGithubRefs,
|
|
1570
2173
|
evidenceSourceCount: releaseRequest.qaReportRefs.length + releaseRequest.securityReportRefs.length + releaseRequest.evidenceSources.length
|
|
1571
2174
|
}
|
|
@@ -1625,6 +2228,10 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1625
2228
|
? asStringArray(intakeMetadata.evidenceSources)
|
|
1626
2229
|
: releaseRequest.evidenceSources;
|
|
1627
2230
|
const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
|
|
2231
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2232
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2233
|
+
const attestationVerificationEvidence = normalizeAttestationVerificationEvidence(repoRoot, normalizedEvidenceSources);
|
|
2234
|
+
const trustSummary = buildReleaseTrustSummary(attestationVerificationEvidence);
|
|
1628
2235
|
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
1629
2236
|
if (missingEvidenceSources.length > 0) {
|
|
1630
2237
|
throw new Error(`Release evidence source not found: ${missingEvidenceSources[0]}`);
|
|
@@ -1643,6 +2250,17 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1643
2250
|
manifestPath: resolved.manifestPath
|
|
1644
2251
|
};
|
|
1645
2252
|
});
|
|
2253
|
+
const dependencyManifestPaths = resolveDependencyManifestPaths(repoRoot, [
|
|
2254
|
+
"package.json",
|
|
2255
|
+
...versionResolutions
|
|
2256
|
+
.map((entry) => entry.manifestPath)
|
|
2257
|
+
.filter((value) => Boolean(value))
|
|
2258
|
+
.map((manifestPath) => manifestPath.replace(`${repoRoot ?? ""}/`, ""))
|
|
2259
|
+
]);
|
|
2260
|
+
const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", dependencyManifestPaths);
|
|
2261
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2262
|
+
const hasMissingDependencyIntegrity = dependencyIntegrityEvidence.some((entry) => entry.integrityStatus === "missing-lockfile");
|
|
2263
|
+
const hasFailedAttestationVerification = attestationVerificationEvidence.some((entry) => entry.status === "failed");
|
|
1646
2264
|
const missingPackages = versionResolutions.filter((entry) => entry.status === "package-missing").map((entry) => entry.name);
|
|
1647
2265
|
const versionCheckStatus = missingPackages.length === 0 ? "passed" : "failed";
|
|
1648
2266
|
const baseReadinessStatus = qaReportRefs.length > 0 && securityReportRefs.length > 0
|
|
@@ -1650,7 +2268,13 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1650
2268
|
: qaReportRefs.length > 0 || securityReportRefs.length > 0
|
|
1651
2269
|
? "partial"
|
|
1652
2270
|
: "blocked";
|
|
1653
|
-
const readinessStatus = missingPackages.length > 0
|
|
2271
|
+
const readinessStatus = missingPackages.length > 0
|
|
2272
|
+
? "blocked"
|
|
2273
|
+
: hasFailedAttestationVerification
|
|
2274
|
+
? "blocked"
|
|
2275
|
+
: hasMissingDependencyIntegrity && baseReadinessStatus === "ready"
|
|
2276
|
+
? "partial"
|
|
2277
|
+
: baseReadinessStatus;
|
|
1654
2278
|
const localReadinessChecks = [
|
|
1655
2279
|
{
|
|
1656
2280
|
name: "qa-report-refs",
|
|
@@ -1673,6 +2297,32 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1673
2297
|
? `Using ${evidenceSources.length} bounded local release evidence source(s).`
|
|
1674
2298
|
: "No additional local release evidence sources were supplied."
|
|
1675
2299
|
},
|
|
2300
|
+
{
|
|
2301
|
+
name: "imported-ci-evidence",
|
|
2302
|
+
status: ciEvidence.length > 0 ? "passed" : "skipped",
|
|
2303
|
+
detail: ciEvidence.length > 0
|
|
2304
|
+
? `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
2305
|
+
: "No imported CI evidence exports were supplied."
|
|
2306
|
+
},
|
|
2307
|
+
{
|
|
2308
|
+
name: "dependency-integrity",
|
|
2309
|
+
status: dependencyIntegrityEvidence.length === 0
|
|
2310
|
+
? "skipped"
|
|
2311
|
+
: hasMissingDependencyIntegrity
|
|
2312
|
+
? "failed"
|
|
2313
|
+
: "passed",
|
|
2314
|
+
detail: dependencyIntegritySignals[0] ??
|
|
2315
|
+
"No dependency manifests were available for bounded integrity verification."
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
name: "attestation-verification",
|
|
2319
|
+
status: attestationVerificationEvidence.length === 0
|
|
2320
|
+
? "skipped"
|
|
2321
|
+
: hasFailedAttestationVerification
|
|
2322
|
+
? "failed"
|
|
2323
|
+
: "passed",
|
|
2324
|
+
detail: trustSummary[0]
|
|
2325
|
+
},
|
|
1676
2326
|
{
|
|
1677
2327
|
name: "workspace-version-targets",
|
|
1678
2328
|
status: versionCheckStatus,
|
|
@@ -1706,6 +2356,8 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1706
2356
|
];
|
|
1707
2357
|
const provenanceRefs = [
|
|
1708
2358
|
...normalizedEvidenceSources,
|
|
2359
|
+
...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
2360
|
+
...attestationVerificationEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
1709
2361
|
...versionResolutions
|
|
1710
2362
|
.map((entry) => entry.manifestPath)
|
|
1711
2363
|
.filter((value) => Boolean(value))
|
|
@@ -1715,6 +2367,10 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1715
2367
|
securityReportRefs,
|
|
1716
2368
|
normalizedEvidenceSources,
|
|
1717
2369
|
missingEvidenceSources: [],
|
|
2370
|
+
ciEvidence,
|
|
2371
|
+
ciEvidenceSummary,
|
|
2372
|
+
dependencyIntegrityEvidence,
|
|
2373
|
+
attestationVerificationEvidence,
|
|
1718
2374
|
versionResolutions: versionResolutions.map((entry) => ({
|
|
1719
2375
|
name: entry.name,
|
|
1720
2376
|
targetVersion: entry.targetVersion,
|
|
@@ -1724,6 +2380,7 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1724
2380
|
localReadinessChecks,
|
|
1725
2381
|
readinessStatus,
|
|
1726
2382
|
approvalRecommendations,
|
|
2383
|
+
trustSummary,
|
|
1727
2384
|
provenanceRefs: [...new Set(provenanceRefs)]
|
|
1728
2385
|
});
|
|
1729
2386
|
return agentOutputSchema.parse({
|
|
@@ -1776,6 +2433,7 @@ const releaseAnalystAgent = {
|
|
|
1776
2433
|
async execute({ state, stateSlice }) {
|
|
1777
2434
|
const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
|
|
1778
2435
|
const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
|
|
2436
|
+
const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
|
|
1779
2437
|
const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
|
|
1780
2438
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1781
2439
|
if (!releaseRequest) {
|
|
@@ -1801,6 +2459,13 @@ const releaseAnalystAgent = {
|
|
|
1801
2459
|
: releaseRequest.evidenceSources;
|
|
1802
2460
|
const constraints = asStringArray(intakeMetadata.constraints);
|
|
1803
2461
|
const allEvidenceRefs = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
|
|
2462
|
+
const importedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
|
|
2463
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? importedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2464
|
+
const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
|
|
2465
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2466
|
+
const attestationVerificationEvidence = normalizedEvidence?.attestationVerificationEvidence ?? [];
|
|
2467
|
+
const trustSummary = normalizedEvidence?.trustSummary ?? buildReleaseTrustSummary(attestationVerificationEvidence);
|
|
2468
|
+
const importedCiFailures = [...new Set(ciEvidenceSummary.flatMap((entry) => entry.failingChecks))];
|
|
1804
2469
|
const versionResolutions = normalizedEvidence?.versionResolutions ?? [];
|
|
1805
2470
|
const verificationChecks = normalizedEvidence?.localReadinessChecks ?? [
|
|
1806
2471
|
{
|
|
@@ -1823,6 +2488,13 @@ const releaseAnalystAgent = {
|
|
|
1823
2488
|
detail: evidenceSources.length > 0
|
|
1824
2489
|
? `Using ${evidenceSources.length} bounded local release evidence source(s).`
|
|
1825
2490
|
: "No additional local release evidence sources were supplied."
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
name: "imported-ci-evidence",
|
|
2494
|
+
status: importedCiEvidence.length > 0 ? "passed" : "skipped",
|
|
2495
|
+
detail: importedCiEvidence.length > 0
|
|
2496
|
+
? `Using ${importedCiEvidence.length} imported CI evidence export(s) across ${ciEvidenceSummary.map((entry) => entry.provider).join(", ")}.`
|
|
2497
|
+
: "No imported CI evidence exports were supplied."
|
|
1826
2498
|
}
|
|
1827
2499
|
];
|
|
1828
2500
|
const readinessStatus = normalizedEvidence?.readinessStatus ??
|
|
@@ -1845,7 +2517,10 @@ const releaseAnalystAgent = {
|
|
|
1845
2517
|
...(versionResolutions.length > 0
|
|
1846
2518
|
? [`Resolved ${versionResolutions.length} workspace version target(s) before any publish or promotion step.`]
|
|
1847
2519
|
: []),
|
|
2520
|
+
...dependencyIntegritySignals.map((signal) => `${signal} Review dependency integrity before any publish or promotion step.`),
|
|
2521
|
+
...trustSummary.map((line) => `${line} Keep publish execution separate from verification.`),
|
|
1848
2522
|
"Review the bounded QA and security evidence before invoking any publish or promotion step.",
|
|
2523
|
+
...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before any publish or promotion step.`),
|
|
1849
2524
|
"Run `agentforge release check --json` and `agentforge release verify --json` before any release cut.",
|
|
1850
2525
|
...approvalRecommendations.map((recommendation) => `${recommendation.action}: ${recommendation.classification.replaceAll("_", " ")} (${recommendation.reason})`),
|
|
1851
2526
|
"Keep trusted publishing and tag or publish actions outside this default read-only workflow path."
|
|
@@ -1856,10 +2531,11 @@ const releaseAnalystAgent = {
|
|
|
1856
2531
|
];
|
|
1857
2532
|
const externalDependencies = [
|
|
1858
2533
|
...(qaReportRefs.length > 0 ? ["Validated QA report inputs remain available for reviewer inspection."] : []),
|
|
1859
|
-
...(securityReportRefs.length > 0 ? ["Validated security report inputs remain available for reviewer inspection."] : [])
|
|
2534
|
+
...(securityReportRefs.length > 0 ? ["Validated security report inputs remain available for reviewer inspection."] : []),
|
|
2535
|
+
...ciEvidenceSummary.map((entry) => `${entry.displayLabel} remains available for reviewer inspection.`)
|
|
1860
2536
|
];
|
|
1861
2537
|
const releaseReport = releaseArtifactSchema.parse({
|
|
1862
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseGithubRefs),
|
|
2538
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseScmRefs, releaseGithubRefs),
|
|
1863
2539
|
artifactKind: "release-report",
|
|
1864
2540
|
lifecycleDomain: "release",
|
|
1865
2541
|
payload: {
|
|
@@ -1868,9 +2544,12 @@ const releaseAnalystAgent = {
|
|
|
1868
2544
|
readinessStatus,
|
|
1869
2545
|
verificationChecks: verificationChecks.map((check) => ({ ...check })),
|
|
1870
2546
|
versionResolutions,
|
|
2547
|
+
ciEvidenceSummary,
|
|
2548
|
+
dependencyIntegritySignals,
|
|
2549
|
+
trustSummary,
|
|
1871
2550
|
approvalRecommendations: approvalRecommendations.map((recommendation) => releaseApprovalRecommendationSchema.parse(recommendation)),
|
|
1872
2551
|
publishingPlan,
|
|
1873
|
-
trustStatus:
|
|
2552
|
+
trustStatus: resolveReleaseTrustStatus(attestationVerificationEvidence),
|
|
1874
2553
|
publishedPackages: [],
|
|
1875
2554
|
tagRefs: [],
|
|
1876
2555
|
provenanceRefs: allEvidenceRefs,
|
|
@@ -1892,6 +2571,10 @@ const releaseAnalystAgent = {
|
|
|
1892
2571
|
qaReportRefs,
|
|
1893
2572
|
securityReportRefs,
|
|
1894
2573
|
evidenceSources,
|
|
2574
|
+
ciEvidence: importedCiEvidence,
|
|
2575
|
+
ciEvidenceSummary,
|
|
2576
|
+
dependencyIntegrityEvidence,
|
|
2577
|
+
attestationVerificationEvidence,
|
|
1895
2578
|
constraints,
|
|
1896
2579
|
normalizedEvidence: normalizedEvidence ?? null
|
|
1897
2580
|
},
|
|
@@ -1899,18 +2582,20 @@ const releaseAnalystAgent = {
|
|
|
1899
2582
|
readinessStatus,
|
|
1900
2583
|
approvalRecommendations,
|
|
1901
2584
|
publishingPlan,
|
|
1902
|
-
rollbackNotes
|
|
2585
|
+
rollbackNotes,
|
|
2586
|
+
trustSummary,
|
|
2587
|
+
importedCiFailures
|
|
1903
2588
|
}
|
|
1904
2589
|
}
|
|
1905
2590
|
});
|
|
1906
2591
|
}
|
|
1907
2592
|
};
|
|
1908
|
-
const
|
|
2593
|
+
const pipelineIntakeAgent = {
|
|
1909
2594
|
manifest: agentManifestSchema.parse({
|
|
1910
2595
|
version: 1,
|
|
1911
|
-
name: "
|
|
1912
|
-
displayName: "
|
|
1913
|
-
category: "
|
|
2596
|
+
name: "pipeline-intake",
|
|
2597
|
+
displayName: "Pipeline Intake",
|
|
2598
|
+
category: "release",
|
|
1914
2599
|
runtime: {
|
|
1915
2600
|
minVersion: "0.1.0",
|
|
1916
2601
|
kind: "deterministic"
|
|
@@ -1919,17 +2604,17 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1919
2604
|
model: false,
|
|
1920
2605
|
network: false,
|
|
1921
2606
|
tools: [],
|
|
1922
|
-
readPaths: [".agentops/requests/**", ".agentops/runs/**"
|
|
2607
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
1923
2608
|
writePaths: []
|
|
1924
2609
|
},
|
|
1925
|
-
inputs: ["workflowInputs", "repo"
|
|
2610
|
+
inputs: ["workflowInputs", "repo"],
|
|
1926
2611
|
outputs: ["summary", "metadata"],
|
|
1927
2612
|
contextPolicy: {
|
|
1928
|
-
sections: ["workflowInputs", "repo", "
|
|
2613
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
1929
2614
|
minimalContext: true
|
|
1930
2615
|
},
|
|
1931
2616
|
catalog: {
|
|
1932
|
-
domain: "
|
|
2617
|
+
domain: "release",
|
|
1933
2618
|
supportLevel: "internal",
|
|
1934
2619
|
maturity: "mvp",
|
|
1935
2620
|
trustScope: "official-core-only"
|
|
@@ -1942,48 +2627,1052 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1942
2627
|
}),
|
|
1943
2628
|
outputSchema: agentOutputSchema,
|
|
1944
2629
|
async execute({ stateSlice }) {
|
|
1945
|
-
const
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
? "artifact-bundle"
|
|
1953
|
-
: "local-reference";
|
|
1954
|
-
const targetPath = repoRoot ? join(repoRoot, securityRequest.targetRef) : securityRequest.targetRef;
|
|
1955
|
-
if (repoRoot && !existsSync(targetPath)) {
|
|
1956
|
-
throw new Error(`Security target reference not found: ${securityRequest.targetRef}`);
|
|
1957
|
-
}
|
|
1958
|
-
const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
|
|
1959
|
-
const normalizedEvidenceSources = [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
|
|
1960
|
-
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
1961
|
-
if (missingEvidenceSources.length > 0) {
|
|
1962
|
-
throw new Error(`Security evidence source not found: ${missingEvidenceSources[0]}`);
|
|
2630
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2631
|
+
const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
|
|
2632
|
+
const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
|
|
2633
|
+
const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
|
|
2634
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2635
|
+
if (!pipelineRequest) {
|
|
2636
|
+
throw new Error("pipeline-evidence-review requires a validated pipeline request before runtime execution.");
|
|
1963
2637
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
: []
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2638
|
+
return agentOutputSchema.parse({
|
|
2639
|
+
summary: `Loaded pipeline request from ${requestFile ?? ".agentops/requests/pipeline.yaml"} for ${pipelineRequest.pipelineScope}.`,
|
|
2640
|
+
findings: [],
|
|
2641
|
+
proposedActions: [],
|
|
2642
|
+
lifecycleArtifacts: [],
|
|
2643
|
+
requestedTools: [],
|
|
2644
|
+
blockedActionFlags: [],
|
|
2645
|
+
metadata: {
|
|
2646
|
+
...pipelineRequestSchema.parse(pipelineRequest),
|
|
2647
|
+
pipelineIssueRefs,
|
|
2648
|
+
pipelineScmRefs,
|
|
2649
|
+
pipelineGithubRefs,
|
|
2650
|
+
evidenceSourceCount: pipelineRequest.evidenceSources.length +
|
|
2651
|
+
pipelineRequest.qaReportRefs.length +
|
|
2652
|
+
pipelineRequest.securityReportRefs.length +
|
|
2653
|
+
pipelineRequest.releaseReportRefs.length
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
const pipelineEvidenceNormalizationAgent = {
|
|
2659
|
+
manifest: agentManifestSchema.parse({
|
|
2660
|
+
version: 1,
|
|
2661
|
+
name: "pipeline-evidence-normalizer",
|
|
2662
|
+
displayName: "Pipeline Evidence Normalizer",
|
|
2663
|
+
category: "release",
|
|
2664
|
+
runtime: {
|
|
2665
|
+
minVersion: "0.1.0",
|
|
2666
|
+
kind: "deterministic"
|
|
2667
|
+
},
|
|
2668
|
+
permissions: {
|
|
2669
|
+
model: false,
|
|
2670
|
+
network: false,
|
|
2671
|
+
tools: [],
|
|
2672
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
|
|
2673
|
+
writePaths: []
|
|
2674
|
+
},
|
|
2675
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
2676
|
+
outputs: ["summary", "metadata"],
|
|
2677
|
+
contextPolicy: {
|
|
2678
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
2679
|
+
minimalContext: true
|
|
2680
|
+
},
|
|
2681
|
+
catalog: {
|
|
2682
|
+
domain: "release",
|
|
2683
|
+
supportLevel: "internal",
|
|
2684
|
+
maturity: "mvp",
|
|
2685
|
+
trustScope: "official-core-only"
|
|
2686
|
+
},
|
|
2687
|
+
trust: {
|
|
2688
|
+
tier: "core",
|
|
2689
|
+
source: "official",
|
|
2690
|
+
reviewed: true
|
|
2691
|
+
}
|
|
2692
|
+
}),
|
|
2693
|
+
outputSchema: agentOutputSchema,
|
|
2694
|
+
async execute({ stateSlice }) {
|
|
2695
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2696
|
+
if (!pipelineRequest) {
|
|
2697
|
+
throw new Error("pipeline-evidence-review requires validated pipeline request inputs before evidence normalization.");
|
|
2698
|
+
}
|
|
2699
|
+
const repoRoot = stateSlice.repo?.root;
|
|
2700
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
2701
|
+
const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
|
|
2702
|
+
? asStringArray(intakeMetadata.qaReportRefs)
|
|
2703
|
+
: pipelineRequest.qaReportRefs;
|
|
2704
|
+
const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
|
|
2705
|
+
? asStringArray(intakeMetadata.securityReportRefs)
|
|
2706
|
+
: pipelineRequest.securityReportRefs;
|
|
2707
|
+
const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
|
|
2708
|
+
? asStringArray(intakeMetadata.releaseReportRefs)
|
|
2709
|
+
: pipelineRequest.releaseReportRefs;
|
|
2710
|
+
const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
2711
|
+
? asStringArray(intakeMetadata.evidenceSources)
|
|
2712
|
+
: pipelineRequest.evidenceSources;
|
|
2713
|
+
const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...evidenceSources])];
|
|
2714
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
2715
|
+
if (missingEvidenceSources.length > 0) {
|
|
2716
|
+
throw new Error(`Pipeline evidence source not found: ${missingEvidenceSources[0]}`);
|
|
2717
|
+
}
|
|
2718
|
+
const referencedArtifactKinds = [
|
|
2719
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
|
|
2720
|
+
];
|
|
2721
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2722
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2723
|
+
const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
|
|
2724
|
+
const verificationChecks = [
|
|
2725
|
+
{
|
|
2726
|
+
name: "referenced-artifacts",
|
|
2727
|
+
status: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0 ? "passed" : "skipped",
|
|
2728
|
+
detail: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0
|
|
2729
|
+
? `Validated ${qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length} referenced artifact bundle(s).`
|
|
2730
|
+
: "No upstream lifecycle artifact references were supplied."
|
|
2731
|
+
},
|
|
2732
|
+
{
|
|
2733
|
+
name: "imported-ci-evidence",
|
|
2734
|
+
status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
|
|
2735
|
+
detail: ciEvidence.length === 0
|
|
2736
|
+
? "No imported CI evidence exports were supplied."
|
|
2737
|
+
: failingChecks.length > 0
|
|
2738
|
+
? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
|
|
2739
|
+
: `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
2740
|
+
}
|
|
2741
|
+
];
|
|
2742
|
+
const reviewStatus = ciEvidence.length === 0 || failingChecks.length > 0
|
|
2743
|
+
? "blocked"
|
|
2744
|
+
: "ready";
|
|
2745
|
+
const normalization = pipelineEvidenceNormalizationSchema.parse({
|
|
2746
|
+
qaReportRefs,
|
|
2747
|
+
securityReportRefs,
|
|
2748
|
+
releaseReportRefs,
|
|
2749
|
+
normalizedEvidenceSources,
|
|
2750
|
+
missingEvidenceSources: [],
|
|
2751
|
+
ciEvidence,
|
|
2752
|
+
ciEvidenceSummary,
|
|
2753
|
+
referencedArtifactKinds,
|
|
2754
|
+
verificationChecks,
|
|
2755
|
+
reviewStatus,
|
|
2756
|
+
provenanceRefs: normalizedEvidenceSources
|
|
2757
|
+
});
|
|
2758
|
+
return agentOutputSchema.parse({
|
|
2759
|
+
summary: `Normalized pipeline evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
|
|
2760
|
+
findings: [],
|
|
2761
|
+
proposedActions: [],
|
|
2762
|
+
lifecycleArtifacts: [],
|
|
2763
|
+
requestedTools: [],
|
|
2764
|
+
blockedActionFlags: [],
|
|
2765
|
+
metadata: normalization
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
};
|
|
2769
|
+
const pipelineAnalystAgent = {
|
|
2770
|
+
manifest: agentManifestSchema.parse({
|
|
2771
|
+
version: 1,
|
|
2772
|
+
name: "pipeline-analyst",
|
|
2773
|
+
displayName: "Pipeline Analyst",
|
|
2774
|
+
category: "release",
|
|
2775
|
+
runtime: {
|
|
2776
|
+
minVersion: "0.1.0",
|
|
2777
|
+
kind: "reasoning"
|
|
2778
|
+
},
|
|
2779
|
+
permissions: {
|
|
2780
|
+
model: true,
|
|
2781
|
+
network: false,
|
|
2782
|
+
tools: [],
|
|
2783
|
+
readPaths: ["**/*"],
|
|
2784
|
+
writePaths: []
|
|
2785
|
+
},
|
|
2786
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
2787
|
+
outputs: ["lifecycleArtifacts"],
|
|
2788
|
+
contextPolicy: {
|
|
2789
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
2790
|
+
minimalContext: true
|
|
2791
|
+
},
|
|
2792
|
+
catalog: {
|
|
2793
|
+
domain: "release",
|
|
2794
|
+
supportLevel: "internal",
|
|
2795
|
+
maturity: "mvp",
|
|
2796
|
+
trustScope: "official-core-only"
|
|
2797
|
+
},
|
|
2798
|
+
trust: {
|
|
2799
|
+
tier: "core",
|
|
2800
|
+
source: "official",
|
|
2801
|
+
reviewed: true
|
|
2802
|
+
}
|
|
2803
|
+
}),
|
|
2804
|
+
outputSchema: agentOutputSchema,
|
|
2805
|
+
async execute({ state, stateSlice }) {
|
|
2806
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2807
|
+
const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
|
|
2808
|
+
const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
|
|
2809
|
+
const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
|
|
2810
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2811
|
+
if (!pipelineRequest) {
|
|
2812
|
+
throw new Error("pipeline-evidence-review requires validated pipeline inputs before analysis.");
|
|
2813
|
+
}
|
|
2814
|
+
const evidenceMetadata = pipelineEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
|
|
2815
|
+
const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
|
|
2816
|
+
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
2817
|
+
? normalizedEvidence.normalizedEvidenceSources
|
|
2818
|
+
: pipelineRequest.evidenceSources;
|
|
2819
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
|
|
2820
|
+
const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
|
|
2821
|
+
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
|
|
2822
|
+
const blockers = [
|
|
2823
|
+
...verificationChecks
|
|
2824
|
+
.filter((check) => check.status === "failed")
|
|
2825
|
+
.map((check) => check.detail ?? `${check.name} failed during deterministic pipeline review.`)
|
|
2826
|
+
];
|
|
2827
|
+
const riskSummary = [
|
|
2828
|
+
...(pipelineRequest.focusAreas.length > 0
|
|
2829
|
+
? [`Focused review areas still require human interpretation: ${pipelineRequest.focusAreas.join(", ")}.`]
|
|
2830
|
+
: []),
|
|
2831
|
+
...(referencedArtifactKinds.length > 0
|
|
2832
|
+
? [`Referenced lifecycle artifacts remain in scope: ${referencedArtifactKinds.join(", ")}.`]
|
|
2833
|
+
: []),
|
|
2834
|
+
...(ciEvidenceSummary.length > 0
|
|
2835
|
+
? [`Imported CI provenance spans ${[...new Set(ciEvidenceSummary.map((entry) => entry.provider))].join(", ")}.`]
|
|
2836
|
+
: [])
|
|
2837
|
+
];
|
|
2838
|
+
const recommendedNextSteps = [
|
|
2839
|
+
...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before moving to deployment or promotion review.`),
|
|
2840
|
+
...(pipelineRequest.qaReportRefs.length + pipelineRequest.securityReportRefs.length + pipelineRequest.releaseReportRefs.length > 0
|
|
2841
|
+
? ["Carry the validated QA, security, and release artifacts forward into deployment-gate-review."]
|
|
2842
|
+
: ["Attach downstream QA, security, or release artifacts before using this pipeline report as a deployment gate input."]),
|
|
2843
|
+
...(pipelineRequest.constraints.length > 0 ? [`Keep follow-up bounded by: ${pipelineRequest.constraints.join("; ")}.`] : [])
|
|
2844
|
+
];
|
|
2845
|
+
const reviewStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.reviewStatus ?? "ready";
|
|
2846
|
+
const summary = `Pipeline report prepared for ${pipelineRequest.pipelineScope}.`;
|
|
2847
|
+
const pipelineReport = pipelineArtifactSchema.parse({
|
|
2848
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Pipeline Evidence Review", summary, [requestFile ?? ".agentops/requests/pipeline.yaml", ...new Set(evidenceSources)], pipelineIssueRefs, pipelineScmRefs, pipelineGithubRefs),
|
|
2849
|
+
artifactKind: "pipeline-report",
|
|
2850
|
+
lifecycleDomain: "release",
|
|
2851
|
+
payload: {
|
|
2852
|
+
pipelineScope: pipelineRequest.pipelineScope,
|
|
2853
|
+
evidenceSources,
|
|
2854
|
+
verificationChecks,
|
|
2855
|
+
ciEvidenceSummary,
|
|
2856
|
+
reviewStatus,
|
|
2857
|
+
blockers,
|
|
2858
|
+
riskSummary,
|
|
2859
|
+
recommendedNextSteps: recommendedNextSteps.length > 0 ? recommendedNextSteps : ["Capture additional bounded CI evidence before follow-on review."],
|
|
2860
|
+
referencedArtifactKinds,
|
|
2861
|
+
provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
|
|
2862
|
+
}
|
|
2863
|
+
});
|
|
2864
|
+
return agentOutputSchema.parse({
|
|
2865
|
+
summary,
|
|
2866
|
+
findings: [],
|
|
2867
|
+
proposedActions: [],
|
|
2868
|
+
lifecycleArtifacts: [pipelineReport],
|
|
2869
|
+
requestedTools: [],
|
|
2870
|
+
blockedActionFlags: [],
|
|
2871
|
+
confidence: 0.76,
|
|
2872
|
+
metadata: {
|
|
2873
|
+
deterministicInputs: {
|
|
2874
|
+
evidenceSources,
|
|
2875
|
+
ciEvidenceSummary,
|
|
2876
|
+
verificationChecks,
|
|
2877
|
+
referencedArtifactKinds,
|
|
2878
|
+
focusAreas: pipelineRequest.focusAreas,
|
|
2879
|
+
constraints: pipelineRequest.constraints
|
|
2880
|
+
},
|
|
2881
|
+
synthesizedAssessment: {
|
|
2882
|
+
reviewStatus,
|
|
2883
|
+
blockers,
|
|
2884
|
+
riskSummary,
|
|
2885
|
+
recommendedNextSteps: pipelineReport.payload.recommendedNextSteps
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
const deploymentGateIntakeAgent = {
|
|
2892
|
+
manifest: agentManifestSchema.parse({
|
|
2893
|
+
version: 1,
|
|
2894
|
+
name: "deployment-gate-intake",
|
|
2895
|
+
displayName: "Deployment Gate Intake",
|
|
2896
|
+
category: "release",
|
|
2897
|
+
runtime: {
|
|
2898
|
+
minVersion: "0.1.0",
|
|
2899
|
+
kind: "deterministic"
|
|
2900
|
+
},
|
|
2901
|
+
permissions: {
|
|
2902
|
+
model: false,
|
|
2903
|
+
network: false,
|
|
2904
|
+
tools: [],
|
|
2905
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
2906
|
+
writePaths: []
|
|
2907
|
+
},
|
|
2908
|
+
inputs: ["workflowInputs", "repo"],
|
|
2909
|
+
outputs: ["summary", "metadata"],
|
|
2910
|
+
contextPolicy: {
|
|
2911
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
2912
|
+
minimalContext: true
|
|
2913
|
+
},
|
|
2914
|
+
catalog: {
|
|
2915
|
+
domain: "release",
|
|
2916
|
+
supportLevel: "internal",
|
|
2917
|
+
maturity: "mvp",
|
|
2918
|
+
trustScope: "official-core-only"
|
|
2919
|
+
},
|
|
2920
|
+
trust: {
|
|
2921
|
+
tier: "core",
|
|
2922
|
+
source: "official",
|
|
2923
|
+
reviewed: true
|
|
2924
|
+
}
|
|
2925
|
+
}),
|
|
2926
|
+
outputSchema: agentOutputSchema,
|
|
2927
|
+
async execute({ stateSlice }) {
|
|
2928
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
2929
|
+
const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
|
|
2930
|
+
const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
|
|
2931
|
+
const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
|
|
2932
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2933
|
+
if (!deploymentRequest) {
|
|
2934
|
+
throw new Error("deployment-gate-review requires a validated deployment request before runtime execution.");
|
|
2935
|
+
}
|
|
2936
|
+
return agentOutputSchema.parse({
|
|
2937
|
+
summary: `Loaded deployment request from ${requestFile ?? ".agentops/requests/deployment.yaml"} for ${deploymentRequest.targetEnvironment}.`,
|
|
2938
|
+
findings: [],
|
|
2939
|
+
proposedActions: [],
|
|
2940
|
+
lifecycleArtifacts: [],
|
|
2941
|
+
requestedTools: [],
|
|
2942
|
+
blockedActionFlags: [],
|
|
2943
|
+
metadata: {
|
|
2944
|
+
...deploymentRequestSchema.parse(deploymentRequest),
|
|
2945
|
+
deploymentIssueRefs,
|
|
2946
|
+
deploymentScmRefs,
|
|
2947
|
+
deploymentGithubRefs,
|
|
2948
|
+
evidenceSourceCount: deploymentRequest.evidenceSources.length +
|
|
2949
|
+
deploymentRequest.qaReportRefs.length +
|
|
2950
|
+
deploymentRequest.securityReportRefs.length +
|
|
2951
|
+
deploymentRequest.releaseReportRefs.length +
|
|
2952
|
+
deploymentRequest.pipelineReportRefs.length
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
const deploymentGateEvidenceNormalizationAgent = {
|
|
2958
|
+
manifest: agentManifestSchema.parse({
|
|
2959
|
+
version: 1,
|
|
2960
|
+
name: "deployment-gate-evidence-normalizer",
|
|
2961
|
+
displayName: "Deployment Gate Evidence Normalizer",
|
|
2962
|
+
category: "release",
|
|
2963
|
+
runtime: {
|
|
2964
|
+
minVersion: "0.1.0",
|
|
2965
|
+
kind: "deterministic"
|
|
2966
|
+
},
|
|
2967
|
+
permissions: {
|
|
2968
|
+
model: false,
|
|
2969
|
+
network: false,
|
|
2970
|
+
tools: [],
|
|
2971
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
|
|
2972
|
+
writePaths: []
|
|
2973
|
+
},
|
|
2974
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
2975
|
+
outputs: ["summary", "metadata"],
|
|
2976
|
+
contextPolicy: {
|
|
2977
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
2978
|
+
minimalContext: true
|
|
2979
|
+
},
|
|
2980
|
+
catalog: {
|
|
2981
|
+
domain: "release",
|
|
2982
|
+
supportLevel: "internal",
|
|
2983
|
+
maturity: "mvp",
|
|
2984
|
+
trustScope: "official-core-only"
|
|
2985
|
+
},
|
|
2986
|
+
trust: {
|
|
2987
|
+
tier: "core",
|
|
2988
|
+
source: "official",
|
|
2989
|
+
reviewed: true
|
|
2990
|
+
}
|
|
2991
|
+
}),
|
|
2992
|
+
outputSchema: agentOutputSchema,
|
|
2993
|
+
async execute({ stateSlice }) {
|
|
2994
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
2995
|
+
if (!deploymentRequest) {
|
|
2996
|
+
throw new Error("deployment-gate-review requires validated deployment request inputs before evidence normalization.");
|
|
2997
|
+
}
|
|
2998
|
+
const repoRoot = stateSlice.repo?.root;
|
|
2999
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
3000
|
+
const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
|
|
3001
|
+
? asStringArray(intakeMetadata.qaReportRefs)
|
|
3002
|
+
: deploymentRequest.qaReportRefs;
|
|
3003
|
+
const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
|
|
3004
|
+
? asStringArray(intakeMetadata.securityReportRefs)
|
|
3005
|
+
: deploymentRequest.securityReportRefs;
|
|
3006
|
+
const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
|
|
3007
|
+
? asStringArray(intakeMetadata.releaseReportRefs)
|
|
3008
|
+
: deploymentRequest.releaseReportRefs;
|
|
3009
|
+
const pipelineReportRefs = asStringArray(intakeMetadata.pipelineReportRefs).length > 0
|
|
3010
|
+
? asStringArray(intakeMetadata.pipelineReportRefs)
|
|
3011
|
+
: deploymentRequest.pipelineReportRefs;
|
|
3012
|
+
const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
3013
|
+
? asStringArray(intakeMetadata.evidenceSources)
|
|
3014
|
+
: deploymentRequest.evidenceSources;
|
|
3015
|
+
const normalizedEvidenceSources = [
|
|
3016
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs, ...evidenceSources])
|
|
3017
|
+
];
|
|
3018
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
3019
|
+
if (missingEvidenceSources.length > 0) {
|
|
3020
|
+
throw new Error(`Deployment evidence source not found: ${missingEvidenceSources[0]}`);
|
|
3021
|
+
}
|
|
3022
|
+
const referencedArtifactKinds = [
|
|
3023
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
|
|
3024
|
+
];
|
|
3025
|
+
const releaseReportReadiness = releaseReportRefs.map((bundleRef) => repoRoot
|
|
3026
|
+
? evaluateReleaseReportReadiness(join(repoRoot, bundleRef))
|
|
3027
|
+
: { ready: false, detail: `Release report reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
3028
|
+
const pipelineReportReadiness = pipelineReportRefs.map((bundleRef) => repoRoot
|
|
3029
|
+
? evaluatePipelineReportReadiness(join(repoRoot, bundleRef))
|
|
3030
|
+
: { ready: false, detail: `Pipeline report reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
3031
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
3032
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
3033
|
+
const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
|
|
3034
|
+
const verificationChecks = [
|
|
3035
|
+
{
|
|
3036
|
+
name: "qa-report-refs",
|
|
3037
|
+
status: qaReportRefs.length > 0 ? "passed" : "skipped",
|
|
3038
|
+
detail: qaReportRefs.length > 0 ? `Using ${qaReportRefs.length} validated QA report reference(s).` : "No QA report references were supplied."
|
|
3039
|
+
},
|
|
3040
|
+
{
|
|
3041
|
+
name: "security-report-refs",
|
|
3042
|
+
status: securityReportRefs.length > 0 ? "passed" : "skipped",
|
|
3043
|
+
detail: securityReportRefs.length > 0
|
|
3044
|
+
? `Using ${securityReportRefs.length} validated security report reference(s).`
|
|
3045
|
+
: "No security report references were supplied."
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
name: "release-report-refs",
|
|
3049
|
+
status: releaseReportRefs.length === 0
|
|
3050
|
+
? "skipped"
|
|
3051
|
+
: releaseReportReadiness.some((entry) => !entry.ready)
|
|
3052
|
+
? "failed"
|
|
3053
|
+
: "passed",
|
|
3054
|
+
detail: releaseReportRefs.length > 0
|
|
3055
|
+
? releaseReportReadiness.some((entry) => !entry.ready)
|
|
3056
|
+
? releaseReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3057
|
+
: `Using ${releaseReportRefs.length} ready release report reference(s).`
|
|
3058
|
+
: "No release report references were supplied."
|
|
3059
|
+
},
|
|
3060
|
+
{
|
|
3061
|
+
name: "pipeline-report-refs",
|
|
3062
|
+
status: pipelineReportRefs.length === 0
|
|
3063
|
+
? "skipped"
|
|
3064
|
+
: pipelineReportReadiness.some((entry) => !entry.ready)
|
|
3065
|
+
? "failed"
|
|
3066
|
+
: "passed",
|
|
3067
|
+
detail: pipelineReportRefs.length > 0
|
|
3068
|
+
? pipelineReportReadiness.some((entry) => !entry.ready)
|
|
3069
|
+
? pipelineReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3070
|
+
: `Using ${pipelineReportRefs.length} ready pipeline report reference(s).`
|
|
3071
|
+
: "No pipeline report references were supplied."
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
name: "imported-ci-evidence",
|
|
3075
|
+
status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
|
|
3076
|
+
detail: ciEvidence.length === 0
|
|
3077
|
+
? "No imported CI evidence exports were supplied."
|
|
3078
|
+
: failingChecks.length > 0
|
|
3079
|
+
? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
|
|
3080
|
+
: `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
3081
|
+
}
|
|
3082
|
+
];
|
|
3083
|
+
const gateStatus = ciEvidence.length === 0 || failingChecks.length > 0
|
|
3084
|
+
? "blocked"
|
|
3085
|
+
: qaReportRefs.length > 0 &&
|
|
3086
|
+
securityReportRefs.length > 0 &&
|
|
3087
|
+
releaseReportRefs.length > 0 &&
|
|
3088
|
+
pipelineReportRefs.length > 0
|
|
3089
|
+
? "ready_for_approval"
|
|
3090
|
+
: "conditionally_ready";
|
|
3091
|
+
const normalization = deploymentGateEvidenceNormalizationSchema.parse({
|
|
3092
|
+
qaReportRefs,
|
|
3093
|
+
securityReportRefs,
|
|
3094
|
+
releaseReportRefs,
|
|
3095
|
+
pipelineReportRefs,
|
|
3096
|
+
normalizedEvidenceSources,
|
|
3097
|
+
missingEvidenceSources: [],
|
|
3098
|
+
ciEvidence,
|
|
3099
|
+
ciEvidenceSummary,
|
|
3100
|
+
referencedArtifactKinds,
|
|
3101
|
+
verificationChecks,
|
|
3102
|
+
gateStatus,
|
|
3103
|
+
provenanceRefs: normalizedEvidenceSources
|
|
3104
|
+
});
|
|
3105
|
+
return agentOutputSchema.parse({
|
|
3106
|
+
summary: `Normalized deployment-gate evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
|
|
3107
|
+
findings: [],
|
|
3108
|
+
proposedActions: [],
|
|
3109
|
+
lifecycleArtifacts: [],
|
|
3110
|
+
requestedTools: [],
|
|
3111
|
+
blockedActionFlags: [],
|
|
3112
|
+
metadata: normalization
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
const deploymentGateAnalystAgent = {
|
|
3117
|
+
manifest: agentManifestSchema.parse({
|
|
3118
|
+
version: 1,
|
|
3119
|
+
name: "deployment-gate-analyst",
|
|
3120
|
+
displayName: "Deployment Gate Analyst",
|
|
3121
|
+
category: "release",
|
|
3122
|
+
runtime: {
|
|
3123
|
+
minVersion: "0.1.0",
|
|
3124
|
+
kind: "reasoning"
|
|
3125
|
+
},
|
|
3126
|
+
permissions: {
|
|
3127
|
+
model: true,
|
|
3128
|
+
network: false,
|
|
3129
|
+
tools: [],
|
|
3130
|
+
readPaths: ["**/*"],
|
|
3131
|
+
writePaths: []
|
|
3132
|
+
},
|
|
3133
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3134
|
+
outputs: ["lifecycleArtifacts"],
|
|
3135
|
+
contextPolicy: {
|
|
3136
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3137
|
+
minimalContext: true
|
|
3138
|
+
},
|
|
3139
|
+
catalog: {
|
|
3140
|
+
domain: "release",
|
|
3141
|
+
supportLevel: "internal",
|
|
3142
|
+
maturity: "mvp",
|
|
3143
|
+
trustScope: "official-core-only"
|
|
3144
|
+
},
|
|
3145
|
+
trust: {
|
|
3146
|
+
tier: "core",
|
|
3147
|
+
source: "official",
|
|
3148
|
+
reviewed: true
|
|
3149
|
+
}
|
|
3150
|
+
}),
|
|
3151
|
+
outputSchema: agentOutputSchema,
|
|
3152
|
+
async execute({ state, stateSlice }) {
|
|
3153
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
3154
|
+
const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
|
|
3155
|
+
const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
|
|
3156
|
+
const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
|
|
3157
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
3158
|
+
if (!deploymentRequest) {
|
|
3159
|
+
throw new Error("deployment-gate-review requires validated deployment inputs before analysis.");
|
|
3160
|
+
}
|
|
3161
|
+
const evidenceMetadata = deploymentGateEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
|
|
3162
|
+
const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
|
|
3163
|
+
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
3164
|
+
? normalizedEvidence.normalizedEvidenceSources
|
|
3165
|
+
: deploymentRequest.evidenceSources;
|
|
3166
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
|
|
3167
|
+
const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
|
|
3168
|
+
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
|
|
3169
|
+
const blockers = [
|
|
3170
|
+
...verificationChecks
|
|
3171
|
+
.filter((check) => check.status === "failed")
|
|
3172
|
+
.map((check) => check.detail ?? `${check.name} failed during deterministic deployment-gate review.`)
|
|
3173
|
+
];
|
|
3174
|
+
const requiredFollowUpChecks = [
|
|
3175
|
+
...verificationChecks
|
|
3176
|
+
.filter((check) => check.status === "skipped")
|
|
3177
|
+
.map((check) => check.detail ?? `${check.name} still needs explicit follow-up.`),
|
|
3178
|
+
...ciEvidenceSummary.map((entry) => `Confirm ${entry.displayLabel} remains current for the ${deploymentRequest.targetEnvironment} candidate.`),
|
|
3179
|
+
...(blockers.length === 0 ? ["Obtain explicit maintainer approval before any deploy, publish, or promotion action."] : [])
|
|
3180
|
+
];
|
|
3181
|
+
const gateStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.gateStatus ?? "conditionally_ready";
|
|
3182
|
+
const summary = `Deployment gate report prepared for ${deploymentRequest.targetEnvironment}.`;
|
|
3183
|
+
const deploymentGateReport = deploymentGateArtifactSchema.parse({
|
|
3184
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Deployment Gate Review", summary, [requestFile ?? ".agentops/requests/deployment.yaml", ...new Set(evidenceSources)], deploymentIssueRefs, deploymentScmRefs, deploymentGithubRefs),
|
|
3185
|
+
artifactKind: "deployment-gate-report",
|
|
3186
|
+
lifecycleDomain: "release",
|
|
3187
|
+
payload: {
|
|
3188
|
+
deploymentScope: deploymentRequest.deploymentScope,
|
|
3189
|
+
targetEnvironment: deploymentRequest.targetEnvironment,
|
|
3190
|
+
evidenceSources,
|
|
3191
|
+
verificationChecks,
|
|
3192
|
+
ciEvidenceSummary,
|
|
3193
|
+
gateStatus,
|
|
3194
|
+
blockers,
|
|
3195
|
+
requiredFollowUpChecks,
|
|
3196
|
+
referencedArtifactKinds,
|
|
3197
|
+
provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
|
|
3198
|
+
}
|
|
3199
|
+
});
|
|
3200
|
+
return agentOutputSchema.parse({
|
|
3201
|
+
summary,
|
|
3202
|
+
findings: [],
|
|
3203
|
+
proposedActions: [],
|
|
3204
|
+
lifecycleArtifacts: [deploymentGateReport],
|
|
3205
|
+
requestedTools: [],
|
|
3206
|
+
blockedActionFlags: [],
|
|
3207
|
+
confidence: 0.77,
|
|
3208
|
+
metadata: {
|
|
3209
|
+
deterministicInputs: {
|
|
3210
|
+
targetEnvironment: deploymentRequest.targetEnvironment,
|
|
3211
|
+
evidenceSources,
|
|
3212
|
+
ciEvidenceSummary,
|
|
3213
|
+
verificationChecks,
|
|
3214
|
+
referencedArtifactKinds,
|
|
3215
|
+
constraints: deploymentRequest.constraints
|
|
3216
|
+
},
|
|
3217
|
+
synthesizedAssessment: {
|
|
3218
|
+
gateStatus,
|
|
3219
|
+
blockers,
|
|
3220
|
+
requiredFollowUpChecks: deploymentGateReport.payload.requiredFollowUpChecks
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
const promotionApprovalIntakeAgent = {
|
|
3227
|
+
manifest: agentManifestSchema.parse({
|
|
3228
|
+
version: 1,
|
|
3229
|
+
name: "promotion-approval-intake",
|
|
3230
|
+
displayName: "Promotion Approval Intake",
|
|
3231
|
+
category: "release",
|
|
3232
|
+
runtime: {
|
|
3233
|
+
minVersion: "0.1.0",
|
|
3234
|
+
kind: "deterministic"
|
|
3235
|
+
},
|
|
3236
|
+
permissions: {
|
|
3237
|
+
model: false,
|
|
3238
|
+
network: false,
|
|
3239
|
+
tools: [],
|
|
3240
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
3241
|
+
writePaths: []
|
|
3242
|
+
},
|
|
3243
|
+
inputs: ["workflowInputs", "repo"],
|
|
3244
|
+
outputs: ["summary", "metadata"],
|
|
3245
|
+
contextPolicy: {
|
|
3246
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
3247
|
+
minimalContext: true
|
|
3248
|
+
},
|
|
3249
|
+
catalog: {
|
|
3250
|
+
domain: "release",
|
|
3251
|
+
supportLevel: "internal",
|
|
3252
|
+
maturity: "mvp",
|
|
3253
|
+
trustScope: "official-core-only"
|
|
3254
|
+
},
|
|
3255
|
+
trust: {
|
|
3256
|
+
tier: "core",
|
|
3257
|
+
source: "official",
|
|
3258
|
+
reviewed: true
|
|
3259
|
+
}
|
|
3260
|
+
}),
|
|
3261
|
+
outputSchema: agentOutputSchema,
|
|
3262
|
+
async execute({ stateSlice }) {
|
|
3263
|
+
const promotionRequest = getWorkflowInput(stateSlice, "promotionRequest");
|
|
3264
|
+
const promotionIssueRefs = getWorkflowInput(stateSlice, "promotionIssueRefs") ?? [];
|
|
3265
|
+
const promotionScmRefs = getWorkflowInput(stateSlice, "promotionScmRefs") ?? [];
|
|
3266
|
+
const promotionGithubRefs = getWorkflowInput(stateSlice, "promotionGithubRefs") ?? [];
|
|
3267
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
3268
|
+
if (!promotionRequest) {
|
|
3269
|
+
throw new Error("promotion-approval requires a validated promotion request before runtime execution.");
|
|
3270
|
+
}
|
|
3271
|
+
return agentOutputSchema.parse({
|
|
3272
|
+
summary: `Loaded promotion approval request from ${requestFile ?? ".agentops/requests/promotion.yaml"} for ${promotionRequest.targetEnvironment}.`,
|
|
3273
|
+
findings: [],
|
|
3274
|
+
proposedActions: [],
|
|
3275
|
+
lifecycleArtifacts: [],
|
|
3276
|
+
requestedTools: [],
|
|
3277
|
+
blockedActionFlags: [],
|
|
3278
|
+
metadata: {
|
|
3279
|
+
...promotionRequestSchema.parse(promotionRequest),
|
|
3280
|
+
promotionIssueRefs,
|
|
3281
|
+
promotionScmRefs,
|
|
3282
|
+
promotionGithubRefs,
|
|
3283
|
+
evidenceSourceCount: promotionRequest.evidenceSources.length +
|
|
3284
|
+
promotionRequest.qaReportRefs.length +
|
|
3285
|
+
promotionRequest.securityReportRefs.length +
|
|
3286
|
+
promotionRequest.releaseReportRefs.length +
|
|
3287
|
+
promotionRequest.deploymentGateReportRefs.length
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
const promotionApprovalEvidenceNormalizationAgent = {
|
|
3293
|
+
manifest: agentManifestSchema.parse({
|
|
3294
|
+
version: 1,
|
|
3295
|
+
name: "promotion-approval-evidence-normalizer",
|
|
3296
|
+
displayName: "Promotion Approval Evidence Normalizer",
|
|
3297
|
+
category: "release",
|
|
3298
|
+
runtime: {
|
|
3299
|
+
minVersion: "0.1.0",
|
|
3300
|
+
kind: "deterministic"
|
|
3301
|
+
},
|
|
3302
|
+
permissions: {
|
|
3303
|
+
model: false,
|
|
3304
|
+
network: false,
|
|
3305
|
+
tools: [],
|
|
3306
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
|
|
3307
|
+
writePaths: []
|
|
3308
|
+
},
|
|
3309
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3310
|
+
outputs: ["summary", "metadata"],
|
|
3311
|
+
contextPolicy: {
|
|
3312
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3313
|
+
minimalContext: true
|
|
3314
|
+
},
|
|
3315
|
+
catalog: {
|
|
3316
|
+
domain: "release",
|
|
3317
|
+
supportLevel: "internal",
|
|
3318
|
+
maturity: "mvp",
|
|
3319
|
+
trustScope: "official-core-only"
|
|
3320
|
+
},
|
|
3321
|
+
trust: {
|
|
3322
|
+
tier: "core",
|
|
3323
|
+
source: "official",
|
|
3324
|
+
reviewed: true
|
|
3325
|
+
}
|
|
3326
|
+
}),
|
|
3327
|
+
outputSchema: agentOutputSchema,
|
|
3328
|
+
async execute({ stateSlice }) {
|
|
3329
|
+
const promotionRequest = getWorkflowInput(stateSlice, "promotionRequest");
|
|
3330
|
+
if (!promotionRequest) {
|
|
3331
|
+
throw new Error("promotion-approval requires validated promotion request inputs before evidence normalization.");
|
|
3332
|
+
}
|
|
3333
|
+
const repoRoot = stateSlice.repo?.root;
|
|
3334
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
3335
|
+
const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
|
|
3336
|
+
? asStringArray(intakeMetadata.qaReportRefs)
|
|
3337
|
+
: promotionRequest.qaReportRefs;
|
|
3338
|
+
const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
|
|
3339
|
+
? asStringArray(intakeMetadata.securityReportRefs)
|
|
3340
|
+
: promotionRequest.securityReportRefs;
|
|
3341
|
+
const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
|
|
3342
|
+
? asStringArray(intakeMetadata.releaseReportRefs)
|
|
3343
|
+
: promotionRequest.releaseReportRefs;
|
|
3344
|
+
const deploymentGateReportRefs = asStringArray(intakeMetadata.deploymentGateReportRefs).length > 0
|
|
3345
|
+
? asStringArray(intakeMetadata.deploymentGateReportRefs)
|
|
3346
|
+
: promotionRequest.deploymentGateReportRefs;
|
|
3347
|
+
const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
3348
|
+
? asStringArray(intakeMetadata.evidenceSources)
|
|
3349
|
+
: promotionRequest.evidenceSources;
|
|
3350
|
+
const normalizedEvidenceSources = [
|
|
3351
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...deploymentGateReportRefs, ...evidenceSources])
|
|
3352
|
+
];
|
|
3353
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
3354
|
+
if (missingEvidenceSources.length > 0) {
|
|
3355
|
+
throw new Error(`Promotion evidence source not found: ${missingEvidenceSources[0]}`);
|
|
3356
|
+
}
|
|
3357
|
+
const referencedArtifactKinds = [
|
|
3358
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...deploymentGateReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
|
|
3359
|
+
];
|
|
3360
|
+
const releaseReportReadiness = releaseReportRefs.map((bundleRef) => repoRoot
|
|
3361
|
+
? evaluateReleaseReportReadiness(join(repoRoot, bundleRef))
|
|
3362
|
+
: { ready: false, detail: `Release report reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
3363
|
+
const deploymentGateReadiness = deploymentGateReportRefs.map((bundleRef) => repoRoot
|
|
3364
|
+
? evaluateDeploymentGateApprovalReadiness(join(repoRoot, bundleRef), promotionRequest.targetEnvironment)
|
|
3365
|
+
: { ready: false, detail: `Deployment gate reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
3366
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
3367
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
3368
|
+
const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
|
|
3369
|
+
const verificationChecks = [
|
|
3370
|
+
{
|
|
3371
|
+
name: "qa-report-refs",
|
|
3372
|
+
status: qaReportRefs.length > 0 ? "passed" : "skipped",
|
|
3373
|
+
detail: qaReportRefs.length > 0 ? `Using ${qaReportRefs.length} validated QA report reference(s).` : "No QA report references were supplied."
|
|
3374
|
+
},
|
|
3375
|
+
{
|
|
3376
|
+
name: "security-report-refs",
|
|
3377
|
+
status: securityReportRefs.length > 0 ? "passed" : "skipped",
|
|
3378
|
+
detail: securityReportRefs.length > 0
|
|
3379
|
+
? `Using ${securityReportRefs.length} validated security report reference(s).`
|
|
3380
|
+
: "No security report references were supplied."
|
|
3381
|
+
},
|
|
3382
|
+
{
|
|
3383
|
+
name: "release-report-refs",
|
|
3384
|
+
status: releaseReportRefs.length === 0
|
|
3385
|
+
? "failed"
|
|
3386
|
+
: releaseReportReadiness.some((entry) => !entry.ready)
|
|
3387
|
+
? "failed"
|
|
3388
|
+
: "passed",
|
|
3389
|
+
detail: releaseReportRefs.length > 0
|
|
3390
|
+
? releaseReportReadiness.some((entry) => !entry.ready)
|
|
3391
|
+
? releaseReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3392
|
+
: `Using ${releaseReportRefs.length} ready release report reference(s).`
|
|
3393
|
+
: "At least one ready release report reference is required."
|
|
3394
|
+
},
|
|
3395
|
+
{
|
|
3396
|
+
name: "deployment-gate-report-refs",
|
|
3397
|
+
status: deploymentGateReportRefs.length === 0
|
|
3398
|
+
? "failed"
|
|
3399
|
+
: deploymentGateReadiness.some((entry) => !entry.ready)
|
|
3400
|
+
? "failed"
|
|
3401
|
+
: "passed",
|
|
3402
|
+
detail: deploymentGateReportRefs.length > 0
|
|
3403
|
+
? deploymentGateReadiness.some((entry) => !entry.ready)
|
|
3404
|
+
? deploymentGateReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3405
|
+
: `Using ${deploymentGateReportRefs.length} ready deployment gate report reference(s) for ${promotionRequest.targetEnvironment}.`
|
|
3406
|
+
: "At least one ready deployment gate report reference is required."
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
name: "imported-ci-evidence",
|
|
3410
|
+
status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
|
|
3411
|
+
detail: ciEvidence.length === 0
|
|
3412
|
+
? "No imported CI evidence exports were supplied."
|
|
3413
|
+
: failingChecks.length > 0
|
|
3414
|
+
? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
|
|
3415
|
+
: `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
3416
|
+
}
|
|
3417
|
+
];
|
|
3418
|
+
const approvalStatus = verificationChecks.some((check) => check.status === "failed")
|
|
3419
|
+
? "blocked"
|
|
3420
|
+
: qaReportRefs.length > 0 && securityReportRefs.length > 0
|
|
3421
|
+
? "approval_recommended"
|
|
3422
|
+
: "needs_follow_up";
|
|
3423
|
+
const approvalRecommendations = [
|
|
3424
|
+
{
|
|
3425
|
+
action: "promote-release",
|
|
3426
|
+
classification: approvalStatus === "approval_recommended" ? "approval_required" : "deny",
|
|
3427
|
+
reason: approvalStatus === "approval_recommended"
|
|
3428
|
+
? "Promotion remains a release-significant side effect and requires explicit maintainer approval."
|
|
3429
|
+
: "Keep release promotion blocked until bounded release and deployment gate evidence is complete."
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
action: "publish-packages",
|
|
3433
|
+
classification: approvalStatus === "approval_recommended" ? "approval_required" : "deny",
|
|
3434
|
+
reason: approvalStatus === "approval_recommended"
|
|
3435
|
+
? "Package publication remains outside the default read-only workflow path."
|
|
3436
|
+
: "Do not publish packages while promotion approval remains blocked or incomplete."
|
|
3437
|
+
}
|
|
3438
|
+
];
|
|
3439
|
+
const normalization = promotionApprovalEvidenceNormalizationSchema.parse({
|
|
3440
|
+
qaReportRefs,
|
|
3441
|
+
securityReportRefs,
|
|
3442
|
+
releaseReportRefs,
|
|
3443
|
+
deploymentGateReportRefs,
|
|
3444
|
+
normalizedEvidenceSources,
|
|
3445
|
+
missingEvidenceSources: [],
|
|
3446
|
+
ciEvidence,
|
|
3447
|
+
ciEvidenceSummary,
|
|
3448
|
+
referencedArtifactKinds,
|
|
3449
|
+
verificationChecks,
|
|
3450
|
+
approvalRecommendations,
|
|
3451
|
+
approvalStatus,
|
|
3452
|
+
provenanceRefs: normalizedEvidenceSources
|
|
3453
|
+
});
|
|
3454
|
+
return agentOutputSchema.parse({
|
|
3455
|
+
summary: `Normalized promotion approval evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
|
|
3456
|
+
findings: [],
|
|
3457
|
+
proposedActions: [],
|
|
3458
|
+
lifecycleArtifacts: [],
|
|
3459
|
+
requestedTools: [],
|
|
3460
|
+
blockedActionFlags: [],
|
|
3461
|
+
metadata: normalization
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
const promotionApprovalAnalystAgent = {
|
|
3466
|
+
manifest: agentManifestSchema.parse({
|
|
3467
|
+
version: 1,
|
|
3468
|
+
name: "promotion-approval-analyst",
|
|
3469
|
+
displayName: "Promotion Approval Analyst",
|
|
3470
|
+
category: "release",
|
|
3471
|
+
runtime: {
|
|
3472
|
+
minVersion: "0.1.0",
|
|
3473
|
+
kind: "reasoning"
|
|
3474
|
+
},
|
|
3475
|
+
permissions: {
|
|
3476
|
+
model: true,
|
|
3477
|
+
network: false,
|
|
3478
|
+
tools: [],
|
|
3479
|
+
readPaths: ["**/*"],
|
|
3480
|
+
writePaths: []
|
|
3481
|
+
},
|
|
3482
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3483
|
+
outputs: ["lifecycleArtifacts"],
|
|
3484
|
+
contextPolicy: {
|
|
3485
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3486
|
+
minimalContext: true
|
|
3487
|
+
},
|
|
3488
|
+
catalog: {
|
|
3489
|
+
domain: "release",
|
|
3490
|
+
supportLevel: "internal",
|
|
3491
|
+
maturity: "mvp",
|
|
3492
|
+
trustScope: "official-core-only"
|
|
3493
|
+
},
|
|
3494
|
+
trust: {
|
|
3495
|
+
tier: "core",
|
|
3496
|
+
source: "official",
|
|
3497
|
+
reviewed: true
|
|
3498
|
+
}
|
|
3499
|
+
}),
|
|
3500
|
+
outputSchema: agentOutputSchema,
|
|
3501
|
+
async execute({ state, stateSlice }) {
|
|
3502
|
+
const promotionRequest = getWorkflowInput(stateSlice, "promotionRequest");
|
|
3503
|
+
const promotionIssueRefs = getWorkflowInput(stateSlice, "promotionIssueRefs") ?? [];
|
|
3504
|
+
const promotionScmRefs = getWorkflowInput(stateSlice, "promotionScmRefs") ?? [];
|
|
3505
|
+
const promotionGithubRefs = getWorkflowInput(stateSlice, "promotionGithubRefs") ?? [];
|
|
3506
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
3507
|
+
if (!promotionRequest) {
|
|
3508
|
+
throw new Error("promotion-approval requires validated promotion inputs before analysis.");
|
|
3509
|
+
}
|
|
3510
|
+
const evidenceMetadata = promotionApprovalEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
|
|
3511
|
+
const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
|
|
3512
|
+
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
3513
|
+
? normalizedEvidence.normalizedEvidenceSources
|
|
3514
|
+
: promotionRequest.evidenceSources;
|
|
3515
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
|
|
3516
|
+
const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
|
|
3517
|
+
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
|
|
3518
|
+
const blockers = [
|
|
3519
|
+
...verificationChecks
|
|
3520
|
+
.filter((check) => check.status === "failed")
|
|
3521
|
+
.map((check) => check.detail ?? `${check.name} failed during deterministic promotion approval review.`)
|
|
3522
|
+
];
|
|
3523
|
+
const approvalStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.approvalStatus ?? "needs_follow_up";
|
|
3524
|
+
const requiredApprovals = [
|
|
3525
|
+
"Obtain explicit maintainer approval before any promotion or publish action.",
|
|
3526
|
+
`Confirm the ${promotionRequest.targetEnvironment} deployment owner accepts the current promotion window.`
|
|
3527
|
+
];
|
|
3528
|
+
const recommendedNextSteps = [
|
|
3529
|
+
...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before approving promotion.`),
|
|
3530
|
+
...(promotionRequest.qaReportRefs.length > 0 || promotionRequest.securityReportRefs.length > 0
|
|
3531
|
+
? ["Carry the validated QA and security artifacts into the promotion approval packet."]
|
|
3532
|
+
: ["Attach QA and security artifacts if additional assurance is required before promotion."]),
|
|
3533
|
+
"Keep deployment, publication, and tag creation outside this review-only workflow.",
|
|
3534
|
+
...(promotionRequest.constraints.length > 0 ? [`Keep follow-up bounded by: ${promotionRequest.constraints.join("; ")}.`] : [])
|
|
3535
|
+
];
|
|
3536
|
+
const approvalRecommendations = normalizedEvidence?.approvalRecommendations ?? [
|
|
3537
|
+
{
|
|
3538
|
+
action: "promote-release",
|
|
3539
|
+
classification: approvalStatus === "approval_recommended" ? "approval_required" : "deny",
|
|
3540
|
+
reason: approvalStatus === "approval_recommended"
|
|
3541
|
+
? "Promotion remains a release-significant side effect and requires explicit maintainer approval."
|
|
3542
|
+
: "Keep release promotion blocked until bounded release and deployment gate evidence is complete."
|
|
3543
|
+
}
|
|
3544
|
+
];
|
|
3545
|
+
const summary = `Promotion approval report prepared for ${promotionRequest.targetEnvironment}.`;
|
|
3546
|
+
const promotionApprovalReport = promotionApprovalArtifactSchema.parse({
|
|
3547
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Promotion Approval Review", summary, [requestFile ?? ".agentops/requests/promotion.yaml", ...new Set(evidenceSources)], promotionIssueRefs, promotionScmRefs, promotionGithubRefs),
|
|
3548
|
+
artifactKind: "promotion-approval-report",
|
|
3549
|
+
lifecycleDomain: "release",
|
|
3550
|
+
payload: {
|
|
3551
|
+
promotionScope: promotionRequest.promotionScope,
|
|
3552
|
+
targetEnvironment: promotionRequest.targetEnvironment,
|
|
3553
|
+
evidenceSources,
|
|
3554
|
+
verificationChecks,
|
|
3555
|
+
ciEvidenceSummary,
|
|
3556
|
+
approvalStatus,
|
|
3557
|
+
blockers,
|
|
3558
|
+
requiredApprovals,
|
|
3559
|
+
recommendedNextSteps,
|
|
3560
|
+
approvalRecommendations: approvalRecommendations.map((recommendation) => releaseApprovalRecommendationSchema.parse(recommendation)),
|
|
3561
|
+
referencedArtifactKinds,
|
|
3562
|
+
provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
|
|
3563
|
+
}
|
|
3564
|
+
});
|
|
3565
|
+
return agentOutputSchema.parse({
|
|
3566
|
+
summary,
|
|
3567
|
+
findings: [],
|
|
3568
|
+
proposedActions: [],
|
|
3569
|
+
lifecycleArtifacts: [promotionApprovalReport],
|
|
3570
|
+
requestedTools: [],
|
|
3571
|
+
blockedActionFlags: [],
|
|
3572
|
+
confidence: 0.78,
|
|
3573
|
+
metadata: {
|
|
3574
|
+
deterministicInputs: {
|
|
3575
|
+
targetEnvironment: promotionRequest.targetEnvironment,
|
|
3576
|
+
evidenceSources,
|
|
3577
|
+
ciEvidenceSummary,
|
|
3578
|
+
verificationChecks,
|
|
3579
|
+
referencedArtifactKinds,
|
|
3580
|
+
constraints: promotionRequest.constraints
|
|
3581
|
+
},
|
|
3582
|
+
synthesizedAssessment: {
|
|
3583
|
+
approvalStatus,
|
|
3584
|
+
blockers,
|
|
3585
|
+
requiredApprovals,
|
|
3586
|
+
recommendedNextSteps
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
};
|
|
3592
|
+
const securityEvidenceNormalizationAgent = {
|
|
3593
|
+
manifest: agentManifestSchema.parse({
|
|
3594
|
+
version: 1,
|
|
3595
|
+
name: "security-evidence-normalizer",
|
|
3596
|
+
displayName: "Security Evidence Normalizer",
|
|
3597
|
+
category: "security",
|
|
3598
|
+
runtime: {
|
|
3599
|
+
minVersion: "0.1.0",
|
|
3600
|
+
kind: "deterministic"
|
|
3601
|
+
},
|
|
3602
|
+
permissions: {
|
|
3603
|
+
model: false,
|
|
3604
|
+
network: false,
|
|
3605
|
+
tools: [],
|
|
3606
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
|
|
3607
|
+
writePaths: []
|
|
3608
|
+
},
|
|
3609
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3610
|
+
outputs: ["summary", "metadata"],
|
|
3611
|
+
contextPolicy: {
|
|
3612
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3613
|
+
minimalContext: true
|
|
3614
|
+
},
|
|
3615
|
+
catalog: {
|
|
3616
|
+
domain: "security",
|
|
3617
|
+
supportLevel: "internal",
|
|
3618
|
+
maturity: "mvp",
|
|
3619
|
+
trustScope: "official-core-only"
|
|
3620
|
+
},
|
|
3621
|
+
trust: {
|
|
3622
|
+
tier: "core",
|
|
3623
|
+
source: "official",
|
|
3624
|
+
reviewed: true
|
|
3625
|
+
}
|
|
3626
|
+
}),
|
|
3627
|
+
outputSchema: agentOutputSchema,
|
|
3628
|
+
async execute({ stateSlice }) {
|
|
3629
|
+
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
3630
|
+
if (!securityRequest) {
|
|
3631
|
+
throw new Error("security-review requires validated security request inputs before evidence normalization.");
|
|
3632
|
+
}
|
|
3633
|
+
const repoRoot = stateSlice.repo?.root;
|
|
3634
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
3635
|
+
const targetType = typeof intakeMetadata.targetType === "string" && intakeMetadata.targetType === "artifact-bundle"
|
|
3636
|
+
? "artifact-bundle"
|
|
3637
|
+
: "local-reference";
|
|
3638
|
+
const targetPath = repoRoot ? join(repoRoot, securityRequest.targetRef) : securityRequest.targetRef;
|
|
3639
|
+
if (repoRoot && !existsSync(targetPath)) {
|
|
3640
|
+
throw new Error(`Security target reference not found: ${securityRequest.targetRef}`);
|
|
3641
|
+
}
|
|
3642
|
+
const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
|
|
3643
|
+
const normalizedEvidenceSources = [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
|
|
3644
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
3645
|
+
if (missingEvidenceSources.length > 0) {
|
|
3646
|
+
throw new Error(`Security evidence source not found: ${missingEvidenceSources[0]}`);
|
|
3647
|
+
}
|
|
3648
|
+
const normalizedFocusAreas = securityRequest.focusAreas.length > 0 ? [...new Set(securityRequest.focusAreas)] : ["general-review"];
|
|
3649
|
+
const affectedPackages = targetType === "artifact-bundle"
|
|
3650
|
+
? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
|
|
3651
|
+
: [];
|
|
3652
|
+
const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", resolveDependencyManifestPaths(repoRoot, ["package.json", ...affectedPackages]));
|
|
3653
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
3654
|
+
const securitySignals = [
|
|
3655
|
+
...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
|
|
3656
|
+
...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
|
|
3657
|
+
...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
|
|
3658
|
+
...dependencyIntegritySignals,
|
|
3659
|
+
"Security evidence collection remains local, read-only, and bounded to validated references."
|
|
3660
|
+
];
|
|
3661
|
+
const provenanceRefs = [
|
|
3662
|
+
securityRequest.targetRef,
|
|
3663
|
+
...securityRequest.evidenceSources,
|
|
3664
|
+
...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
3665
|
+
...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
|
|
3666
|
+
];
|
|
3667
|
+
const normalization = securityEvidenceNormalizationSchema.parse({
|
|
3668
|
+
targetRef: securityRequest.targetRef,
|
|
1981
3669
|
targetType,
|
|
1982
3670
|
referencedArtifactKinds,
|
|
1983
3671
|
normalizedEvidenceSources,
|
|
1984
3672
|
missingEvidenceSources: [],
|
|
1985
3673
|
normalizedFocusAreas,
|
|
1986
3674
|
securitySignals,
|
|
3675
|
+
dependencyIntegrityEvidence,
|
|
1987
3676
|
provenanceRefs: [...new Set(provenanceRefs)],
|
|
1988
3677
|
affectedPackages
|
|
1989
3678
|
});
|
|
@@ -2037,6 +3726,7 @@ const securityAnalystAgent = {
|
|
|
2037
3726
|
async execute({ state, stateSlice }) {
|
|
2038
3727
|
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
2039
3728
|
const securityIssueRefs = getWorkflowInput(stateSlice, "securityIssueRefs") ?? [];
|
|
3729
|
+
const securityScmRefs = getWorkflowInput(stateSlice, "securityScmRefs") ?? [];
|
|
2040
3730
|
const securityGithubRefs = getWorkflowInput(stateSlice, "securityGithubRefs") ?? [];
|
|
2041
3731
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2042
3732
|
if (!securityRequest) {
|
|
@@ -2048,6 +3738,8 @@ const securityAnalystAgent = {
|
|
|
2048
3738
|
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? asStringArray(intakeMetadata.referencedArtifactKinds);
|
|
2049
3739
|
const normalizedFocusAreas = normalizedEvidence?.normalizedFocusAreas ?? asStringArray(intakeMetadata.focusAreas);
|
|
2050
3740
|
const normalizedConstraints = asStringArray(intakeMetadata.constraints);
|
|
3741
|
+
const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
|
|
3742
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2051
3743
|
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
2052
3744
|
? normalizedEvidence.normalizedEvidenceSources
|
|
2053
3745
|
: asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
@@ -2084,6 +3776,7 @@ const securityAnalystAgent = {
|
|
|
2084
3776
|
];
|
|
2085
3777
|
const followUpWork = [
|
|
2086
3778
|
...(normalizedEvidence?.securitySignals ?? []),
|
|
3779
|
+
...dependencyIntegritySignals,
|
|
2087
3780
|
...(referencedArtifactKinds.length > 0
|
|
2088
3781
|
? [`Confirm the security posture for referenced artifacts: ${referencedArtifactKinds.join(", ")}.`]
|
|
2089
3782
|
: []),
|
|
@@ -2094,7 +3787,7 @@ const securityAnalystAgent = {
|
|
|
2094
3787
|
...buildLifecycleArtifactEnvelopeBase(state, "Security Review", summary, [
|
|
2095
3788
|
requestFile ?? ".agentops/requests/security.yaml",
|
|
2096
3789
|
...(normalizedEvidence?.provenanceRefs ?? [securityRequest.targetRef, ...securityRequest.evidenceSources])
|
|
2097
|
-
], securityIssueRefs, securityGithubRefs),
|
|
3790
|
+
], securityIssueRefs, securityScmRefs, securityGithubRefs),
|
|
2098
3791
|
artifactKind: "security-report",
|
|
2099
3792
|
lifecycleDomain: "security",
|
|
2100
3793
|
redaction: {
|
|
@@ -2115,7 +3808,8 @@ const securityAnalystAgent = {
|
|
|
2115
3808
|
: securityRequest.releaseContext === "candidate"
|
|
2116
3809
|
? "candidate release requires explicit security review before promotion."
|
|
2117
3810
|
: "no release context was supplied; security output remains advisory.",
|
|
2118
|
-
followUpWork
|
|
3811
|
+
followUpWork,
|
|
3812
|
+
dependencyIntegritySignals
|
|
2119
3813
|
}
|
|
2120
3814
|
});
|
|
2121
3815
|
return agentOutputSchema.parse({
|
|
@@ -2133,6 +3827,7 @@ const securityAnalystAgent = {
|
|
|
2133
3827
|
focusAreas,
|
|
2134
3828
|
constraints: normalizedConstraints,
|
|
2135
3829
|
referencedArtifactKinds,
|
|
3830
|
+
dependencyIntegrityEvidence,
|
|
2136
3831
|
normalizedEvidence: normalizedEvidence ?? null
|
|
2137
3832
|
},
|
|
2138
3833
|
synthesizedAssessment: {
|
|
@@ -2217,6 +3912,7 @@ const qaEvidenceNormalizationAgent = {
|
|
|
2217
3912
|
const allowlistedCommands = new Set(allowedValidationCommands.map((entry) => entry.command));
|
|
2218
3913
|
const normalizedExecutedChecks = qaRequest.executedChecks.map(normalizeRequestedCommand);
|
|
2219
3914
|
const unrecognizedExecutedChecks = normalizedExecutedChecks.filter((command) => !allowlistedCommands.has(command));
|
|
3915
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2220
3916
|
const githubActions = normalizeGitHubActionsEvidence(repoRoot, normalizedEvidenceSources);
|
|
2221
3917
|
const normalization = qaEvidenceNormalizationSchema.parse({
|
|
2222
3918
|
targetRef: qaRequest.targetRef,
|
|
@@ -2228,10 +3924,11 @@ const qaEvidenceNormalizationAgent = {
|
|
|
2228
3924
|
unrecognizedExecutedChecks,
|
|
2229
3925
|
affectedPackages,
|
|
2230
3926
|
allowedValidationCommands,
|
|
3927
|
+
ciEvidence,
|
|
2231
3928
|
githubActions
|
|
2232
3929
|
});
|
|
2233
3930
|
return agentOutputSchema.parse({
|
|
2234
|
-
summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s), ${normalization.allowedValidationCommands.length} allowlisted validation command(s),
|
|
3931
|
+
summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s), ${normalization.allowedValidationCommands.length} allowlisted validation command(s), ${normalization.githubActions.evidence.length} GitHub Actions export(s), and ${normalization.ciEvidence.length} generic CI evidence export(s).`,
|
|
2235
3932
|
findings: [],
|
|
2236
3933
|
proposedActions: [],
|
|
2237
3934
|
lifecycleArtifacts: [],
|
|
@@ -2280,6 +3977,7 @@ const qaAnalystAgent = {
|
|
|
2280
3977
|
async execute({ state, stateSlice }) {
|
|
2281
3978
|
const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
|
|
2282
3979
|
const qaIssueRefs = getWorkflowInput(stateSlice, "qaIssueRefs") ?? [];
|
|
3980
|
+
const qaScmRefs = getWorkflowInput(stateSlice, "qaScmRefs") ?? [];
|
|
2283
3981
|
const qaGithubRefs = getWorkflowInput(stateSlice, "qaGithubRefs") ?? [];
|
|
2284
3982
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2285
3983
|
if (!qaRequest) {
|
|
@@ -2302,6 +4000,10 @@ const qaAnalystAgent = {
|
|
|
2302
4000
|
failingChecks: [],
|
|
2303
4001
|
provenanceRefs: []
|
|
2304
4002
|
};
|
|
4003
|
+
const normalizedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
|
|
4004
|
+
const normalizedCiEvidenceSummary = normalizedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
4005
|
+
const normalizedCiWorkflowNames = [...new Set(normalizedCiEvidence.map((entry) => entry.pipelineName))];
|
|
4006
|
+
const normalizedCiFailingChecks = [...new Set(normalizedCiEvidence.flatMap((entry) => summarizeCiEvidenceFailures(entry)))];
|
|
2305
4007
|
const targetType = normalizedEvidence?.targetType
|
|
2306
4008
|
? normalizedEvidence.targetType
|
|
2307
4009
|
: typeof intakeMetadata.targetType === "string"
|
|
@@ -2341,12 +4043,14 @@ const qaAnalystAgent = {
|
|
|
2341
4043
|
...(focusAreas.includes("coverage") ? ["Coverage evidence still needs deterministic normalization before it can be promoted to an official QA signal."] : []),
|
|
2342
4044
|
...(executedChecks.length === 0 ? ["No executed validation checks were recorded in the request."] : []),
|
|
2343
4045
|
...unrecognizedExecutedChecks.map((command) => `Executed check is outside the bounded allowlist and still needs manual interpretation: ${command}`),
|
|
2344
|
-
...normalizedGithubActions.failingChecks.map((checkName) => `GitHub Actions evidence still reports a failing check that needs manual review: ${checkName}`)
|
|
4046
|
+
...normalizedGithubActions.failingChecks.map((checkName) => `GitHub Actions evidence still reports a failing check that needs manual review: ${checkName}`),
|
|
4047
|
+
...normalizedCiFailingChecks.map((checkName) => `Imported CI evidence still reports a failing check that needs manual review: ${checkName}`)
|
|
2345
4048
|
];
|
|
2346
4049
|
const recommendedNextChecks = [
|
|
2347
4050
|
...executedChecks.map((command) => `Review the recorded output for \`${command}\` before promotion.`),
|
|
2348
4051
|
...focusAreas.map((focusArea) => `Confirm whether ${focusArea} needs additional deterministic QA evidence.`),
|
|
2349
4052
|
...normalizedGithubActions.workflowNames.map((workflowName) => `Review the exported GitHub Actions evidence for workflow \`${workflowName}\` before promotion.`),
|
|
4053
|
+
...normalizedCiWorkflowNames.map((workflowName) => `Review the imported CI evidence for pipeline \`${workflowName}\` before promotion.`),
|
|
2350
4054
|
...(normalizedConstraints.length > 0 ? [`Keep QA follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
|
|
2351
4055
|
];
|
|
2352
4056
|
const summary = `QA report prepared for ${qaRequest.targetRef}.`;
|
|
@@ -2356,7 +4060,7 @@ const qaAnalystAgent = {
|
|
|
2356
4060
|
? "candidate release still requires explicit QA review before promotion."
|
|
2357
4061
|
: "no release context was supplied; QA output remains advisory.";
|
|
2358
4062
|
const qaReport = qaArtifactSchema.parse({
|
|
2359
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaGithubRefs),
|
|
4063
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaScmRefs, qaGithubRefs),
|
|
2360
4064
|
artifactKind: "qa-report",
|
|
2361
4065
|
lifecycleDomain: "test",
|
|
2362
4066
|
workflow: {
|
|
@@ -2367,13 +4071,21 @@ const qaAnalystAgent = {
|
|
|
2367
4071
|
targetRef: qaRequest.targetRef,
|
|
2368
4072
|
evidenceSources,
|
|
2369
4073
|
executedChecks,
|
|
4074
|
+
ciEvidenceSummary: normalizedCiEvidenceSummary,
|
|
2370
4075
|
findings,
|
|
2371
4076
|
coverageGaps,
|
|
2372
4077
|
recommendedNextChecks: recommendedNextChecks.length > 0
|
|
2373
4078
|
? recommendedNextChecks
|
|
2374
4079
|
: ["Capture additional bounded QA evidence before promotion."],
|
|
2375
|
-
releaseImpact: normalizedGithubActions.failingChecks.length > 0
|
|
2376
|
-
? `${releaseImpactBase}
|
|
4080
|
+
releaseImpact: normalizedGithubActions.failingChecks.length > 0 || normalizedCiFailingChecks.length > 0
|
|
4081
|
+
? `${releaseImpactBase} ${[
|
|
4082
|
+
normalizedGithubActions.failingChecks.length > 0
|
|
4083
|
+
? `GitHub Actions evidence still shows failing checks: ${normalizedGithubActions.failingChecks.join(", ")}.`
|
|
4084
|
+
: undefined,
|
|
4085
|
+
normalizedCiFailingChecks.length > 0
|
|
4086
|
+
? `Imported CI evidence still shows failing checks: ${normalizedCiFailingChecks.join(", ")}.`
|
|
4087
|
+
: undefined
|
|
4088
|
+
].filter((value) => Boolean(value)).join(" ")}`
|
|
2377
4089
|
: releaseImpactBase
|
|
2378
4090
|
}
|
|
2379
4091
|
});
|
|
@@ -2393,6 +4105,7 @@ const qaAnalystAgent = {
|
|
|
2393
4105
|
executedChecks,
|
|
2394
4106
|
focusAreas,
|
|
2395
4107
|
constraints: normalizedConstraints,
|
|
4108
|
+
ciEvidence: normalizedCiEvidence,
|
|
2396
4109
|
githubActions: normalizedGithubActions
|
|
2397
4110
|
},
|
|
2398
4111
|
synthesizedAssessment: {
|
|
@@ -2602,7 +4315,7 @@ const implementationPlannerAgent = {
|
|
|
2602
4315
|
];
|
|
2603
4316
|
const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
|
|
2604
4317
|
const implementationProposal = implementationArtifactSchema.parse({
|
|
2605
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.githubRefs),
|
|
4318
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.scmRefs, designRecord.source.githubRefs),
|
|
2606
4319
|
artifactKind: "implementation-proposal",
|
|
2607
4320
|
lifecycleDomain: "build",
|
|
2608
4321
|
workflow: {
|
|
@@ -2715,7 +4428,7 @@ const designAnalystAgent = {
|
|
|
2715
4428
|
];
|
|
2716
4429
|
const summary = `Design record prepared for ${designRequest.decisionTarget}.`;
|
|
2717
4430
|
const designRecord = designArtifactSchema.parse({
|
|
2718
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.githubRefs),
|
|
4431
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.scmRefs, planningBrief.source.githubRefs),
|
|
2719
4432
|
artifactKind: "design-record",
|
|
2720
4433
|
lifecycleDomain: "design",
|
|
2721
4434
|
workflow: {
|
|
@@ -3013,6 +4726,15 @@ export function createBuiltinAgentRegistry() {
|
|
|
3013
4726
|
["release-intake", releaseIntakeAgent],
|
|
3014
4727
|
["release-evidence-normalizer", releaseEvidenceNormalizationAgent],
|
|
3015
4728
|
["release-analyst", releaseAnalystAgent],
|
|
4729
|
+
["pipeline-intake", pipelineIntakeAgent],
|
|
4730
|
+
["pipeline-evidence-normalizer", pipelineEvidenceNormalizationAgent],
|
|
4731
|
+
["pipeline-analyst", pipelineAnalystAgent],
|
|
4732
|
+
["deployment-gate-intake", deploymentGateIntakeAgent],
|
|
4733
|
+
["deployment-gate-evidence-normalizer", deploymentGateEvidenceNormalizationAgent],
|
|
4734
|
+
["deployment-gate-analyst", deploymentGateAnalystAgent],
|
|
4735
|
+
["promotion-approval-intake", promotionApprovalIntakeAgent],
|
|
4736
|
+
["promotion-approval-evidence-normalizer", promotionApprovalEvidenceNormalizationAgent],
|
|
4737
|
+
["promotion-approval-analyst", promotionApprovalAnalystAgent],
|
|
3016
4738
|
["security-evidence-normalizer", securityEvidenceNormalizationAgent],
|
|
3017
4739
|
["security-analyst", securityAnalystAgent],
|
|
3018
4740
|
["qa-evidence-normalizer", qaEvidenceNormalizationAgent],
|