@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.
@@ -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 command = buildValidationCommand(packageManager, scriptName, packageName);
118
- discoveredValidationCommands.push({
119
- command,
120
- source,
121
- classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
122
- reason: allowedValidationScriptNames.has(scriptName)
123
- ? "Discovered from a bounded repository script; execution would still require approval."
124
- : "Command is not in the bounded allowlist for workflow validation."
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 ? "blocked" : baseReadinessStatus;
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: "trusted-publishing-reviewed-separately",
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 securityEvidenceNormalizationAgent = {
2593
+ const pipelineIntakeAgent = {
1909
2594
  manifest: agentManifestSchema.parse({
1910
2595
  version: 1,
1911
- name: "security-evidence-normalizer",
1912
- displayName: "Security Evidence Normalizer",
1913
- category: "security",
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/**", "**/*.json", "**/*.md", "**/package.json"],
2607
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
1923
2608
  writePaths: []
1924
2609
  },
1925
- inputs: ["workflowInputs", "repo", "agentResults"],
2610
+ inputs: ["workflowInputs", "repo"],
1926
2611
  outputs: ["summary", "metadata"],
1927
2612
  contextPolicy: {
1928
- sections: ["workflowInputs", "repo", "agentResults"],
2613
+ sections: ["workflowInputs", "repo", "context"],
1929
2614
  minimalContext: true
1930
2615
  },
1931
2616
  catalog: {
1932
- domain: "security",
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 securityRequest = getWorkflowInput(stateSlice, "securityRequest");
1946
- if (!securityRequest) {
1947
- throw new Error("security-review requires validated security request inputs before evidence normalization.");
1948
- }
1949
- const repoRoot = stateSlice.repo?.root;
1950
- const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1951
- const targetType = typeof intakeMetadata.targetType === "string" && intakeMetadata.targetType === "artifact-bundle"
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
- const normalizedFocusAreas = securityRequest.focusAreas.length > 0 ? [...new Set(securityRequest.focusAreas)] : ["general-review"];
1965
- const affectedPackages = targetType === "artifact-bundle"
1966
- ? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
1967
- : [];
1968
- const securitySignals = [
1969
- ...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
1970
- ...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
1971
- ...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
1972
- "Security evidence collection remains local, read-only, and bounded to validated references."
1973
- ];
1974
- const provenanceRefs = [
1975
- securityRequest.targetRef,
1976
- ...securityRequest.evidenceSources,
1977
- ...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
1978
- ];
1979
- const normalization = securityEvidenceNormalizationSchema.parse({
1980
- targetRef: securityRequest.targetRef,
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), and ${normalization.githubActions.evidence.length} GitHub Actions evidence export(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} GitHub Actions evidence still shows failing checks: ${normalizedGithubActions.failingChecks.join(", ")}.`
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],