@h9-foundry/agentforge-cli 0.8.0 → 0.10.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, 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,82 @@ 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
+ }
155
424
  function loadBundleArtifactPayloadPaths(bundlePath) {
156
425
  if (!existsSync(bundlePath)) {
157
426
  return [];
@@ -247,6 +516,292 @@ function normalizeGitHubActionsEvidence(repoRoot, evidenceSources) {
247
516
  provenanceRefs
248
517
  };
249
518
  }
519
+ function mapGitHubActionsEvidenceToCiEvidence(evidence) {
520
+ return ciEvidenceSchema.parse({
521
+ platform: "github-actions",
522
+ providerName: "GitHub Actions",
523
+ host: "github.com",
524
+ repository: evidence.repository,
525
+ pipelineName: evidence.workflowName,
526
+ pipelineRunId: `${evidence.workflowRunId}`,
527
+ runAttempt: evidence.runAttempt,
528
+ event: evidence.event,
529
+ branch: evidence.headBranch,
530
+ commitSha: evidence.headSha,
531
+ status: evidence.status,
532
+ conclusion: evidence.conclusion,
533
+ htmlUrl: evidence.htmlUrl,
534
+ jobs: evidence.jobs,
535
+ artifacts: [],
536
+ provenanceSource: "local-export"
537
+ });
538
+ }
539
+ function loadGitLabCiEvidenceExport(bundlePath) {
540
+ if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
541
+ return undefined;
542
+ }
543
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
544
+ const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
545
+ const result = gitlabCiEvidenceExportSchema.safeParse(candidate);
546
+ return result.success ? result.data : undefined;
547
+ }
548
+ function loadBuildkiteCiEvidenceExport(bundlePath) {
549
+ if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
550
+ return undefined;
551
+ }
552
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
553
+ const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
554
+ const result = buildkiteCiEvidenceExportSchema.safeParse(candidate);
555
+ return result.success ? result.data : undefined;
556
+ }
557
+ function loadGenericCiEvidenceExport(bundlePath) {
558
+ if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
559
+ return undefined;
560
+ }
561
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
562
+ const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
563
+ const result = genericCiEvidenceExportSchema.safeParse(candidate);
564
+ return result.success ? result.data : undefined;
565
+ }
566
+ function loadJenkinsCiEvidenceExport(bundlePath) {
567
+ if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
568
+ return undefined;
569
+ }
570
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
571
+ const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
572
+ const result = jenkinsCiEvidenceExportSchema.safeParse(candidate);
573
+ return result.success ? result.data : undefined;
574
+ }
575
+ function mapGitLabCiStatus(status) {
576
+ switch (status) {
577
+ case "pending":
578
+ return { status: "queued" };
579
+ case "running":
580
+ return { status: "in_progress" };
581
+ case "success":
582
+ return { status: "completed", conclusion: "success" };
583
+ case "failed":
584
+ return { status: "completed", conclusion: "failure" };
585
+ case "canceled":
586
+ return { status: "completed", conclusion: "cancelled" };
587
+ case "skipped":
588
+ return { status: "completed", conclusion: "skipped" };
589
+ }
590
+ }
591
+ function normalizeGitLabCiEvidence(repoRoot, evidenceSources) {
592
+ return evidenceSources.flatMap((pathValue) => {
593
+ if (!repoRoot) {
594
+ return [];
595
+ }
596
+ const normalized = loadGitLabCiEvidenceExport(join(repoRoot, pathValue));
597
+ if (!normalized) {
598
+ return [];
599
+ }
600
+ const pipelineStatus = mapGitLabCiStatus(normalized.status);
601
+ const jobs = normalized.jobs.map((job) => {
602
+ const jobStatus = mapGitLabCiStatus(job.status);
603
+ return {
604
+ name: job.name,
605
+ status: jobStatus.status,
606
+ conclusion: jobStatus.conclusion,
607
+ htmlUrl: job.webUrl,
608
+ startedAt: job.startedAt,
609
+ completedAt: job.completedAt
610
+ };
611
+ });
612
+ const evidence = ciEvidenceSchema.parse({
613
+ platform: "gitlab-ci",
614
+ host: normalized.host,
615
+ repository: normalized.projectPath,
616
+ pipelineName: normalized.pipelineName,
617
+ pipelineRunId: `${normalized.pipelineId}`,
618
+ runAttempt: normalized.runAttempt,
619
+ event: normalized.event,
620
+ branch: normalized.branch,
621
+ commitSha: normalized.commitSha,
622
+ status: pipelineStatus.status,
623
+ conclusion: pipelineStatus.conclusion,
624
+ htmlUrl: normalized.webUrl,
625
+ jobs,
626
+ artifacts: [],
627
+ provenanceSource: "local-export"
628
+ });
629
+ return [evidence];
630
+ });
631
+ }
632
+ function normalizeGenericCiEvidence(repoRoot, evidenceSources) {
633
+ return evidenceSources.flatMap((pathValue) => {
634
+ if (!repoRoot) {
635
+ return [];
636
+ }
637
+ const normalized = loadGenericCiEvidenceExport(join(repoRoot, pathValue));
638
+ if (!normalized) {
639
+ return [];
640
+ }
641
+ const evidence = ciEvidenceSchema.parse({
642
+ platform: "generic-ci",
643
+ providerName: normalized.providerName,
644
+ host: normalized.host,
645
+ repository: normalized.repository,
646
+ pipelineName: normalized.pipelineName,
647
+ pipelineRunId: normalized.pipelineRunId,
648
+ runAttempt: normalized.runAttempt,
649
+ event: normalized.event,
650
+ branch: normalized.branch,
651
+ commitSha: normalized.commitSha,
652
+ status: normalized.status,
653
+ conclusion: normalized.conclusion,
654
+ htmlUrl: normalized.htmlUrl,
655
+ jobs: normalized.jobs,
656
+ artifacts: normalized.artifacts,
657
+ provenanceSource: "local-export"
658
+ });
659
+ return [evidence];
660
+ });
661
+ }
662
+ function normalizeBuildkiteCiEvidence(repoRoot, evidenceSources) {
663
+ return evidenceSources.flatMap((pathValue) => {
664
+ if (!repoRoot) {
665
+ return [];
666
+ }
667
+ const normalized = loadBuildkiteCiEvidenceExport(join(repoRoot, pathValue));
668
+ if (!normalized) {
669
+ return [];
670
+ }
671
+ const evidence = ciEvidenceSchema.parse({
672
+ platform: "buildkite",
673
+ providerName: "Buildkite",
674
+ host: normalized.host,
675
+ repository: normalized.repository,
676
+ pipelineName: normalized.pipelineName,
677
+ pipelineRunId: normalized.pipelineRunId,
678
+ runAttempt: normalized.runAttempt,
679
+ event: normalized.event,
680
+ branch: normalized.branch,
681
+ commitSha: normalized.commitSha,
682
+ status: normalized.status,
683
+ conclusion: normalized.conclusion,
684
+ htmlUrl: normalized.htmlUrl,
685
+ jobs: normalized.jobs,
686
+ artifacts: normalized.artifacts,
687
+ provenanceSource: "local-export"
688
+ });
689
+ return [evidence];
690
+ });
691
+ }
692
+ function normalizeJenkinsCiEvidence(repoRoot, evidenceSources) {
693
+ return evidenceSources.flatMap((pathValue) => {
694
+ if (!repoRoot) {
695
+ return [];
696
+ }
697
+ const normalized = loadJenkinsCiEvidenceExport(join(repoRoot, pathValue));
698
+ if (!normalized) {
699
+ return [];
700
+ }
701
+ const evidence = ciEvidenceSchema.parse({
702
+ platform: "jenkins-ci",
703
+ providerName: "Jenkins",
704
+ host: normalized.host,
705
+ repository: normalized.repository,
706
+ pipelineName: normalized.pipelineName,
707
+ pipelineRunId: normalized.pipelineRunId,
708
+ runAttempt: normalized.runAttempt,
709
+ event: normalized.event,
710
+ branch: normalized.branch,
711
+ commitSha: normalized.commitSha,
712
+ status: normalized.status,
713
+ conclusion: normalized.conclusion,
714
+ htmlUrl: normalized.htmlUrl,
715
+ jobs: normalized.jobs,
716
+ artifacts: normalized.artifacts,
717
+ provenanceSource: "local-export"
718
+ });
719
+ return [evidence];
720
+ });
721
+ }
722
+ function normalizeImportedCiEvidence(repoRoot, evidenceSources) {
723
+ const seen = new Set();
724
+ const normalized = [];
725
+ for (const pathValue of evidenceSources) {
726
+ if (!repoRoot) {
727
+ continue;
728
+ }
729
+ const absolutePath = join(repoRoot, pathValue);
730
+ if (!existsSync(absolutePath) || !absolutePath.endsWith(".json")) {
731
+ continue;
732
+ }
733
+ const evidence = (() => {
734
+ const githubActions = loadGitHubActionsEvidence(absolutePath);
735
+ if (githubActions) {
736
+ return mapGitHubActionsEvidenceToCiEvidence(githubActions);
737
+ }
738
+ const gitlab = loadGitLabCiEvidenceExport(absolutePath);
739
+ if (gitlab) {
740
+ return normalizeGitLabCiEvidence(repoRoot, [pathValue])[0];
741
+ }
742
+ const buildkite = loadBuildkiteCiEvidenceExport(absolutePath);
743
+ if (buildkite) {
744
+ return normalizeBuildkiteCiEvidence(repoRoot, [pathValue])[0];
745
+ }
746
+ const jenkins = loadJenkinsCiEvidenceExport(absolutePath);
747
+ if (jenkins) {
748
+ return normalizeJenkinsCiEvidence(repoRoot, [pathValue])[0];
749
+ }
750
+ const generic = loadGenericCiEvidenceExport(absolutePath);
751
+ if (generic) {
752
+ return normalizeGenericCiEvidence(repoRoot, [pathValue])[0];
753
+ }
754
+ return undefined;
755
+ })();
756
+ if (!evidence) {
757
+ continue;
758
+ }
759
+ const key = `${evidence.platform}:${evidence.host}:${evidence.pipelineRunId}:${evidence.pipelineName}:${evidence.repository}`;
760
+ if (seen.has(key)) {
761
+ continue;
762
+ }
763
+ seen.add(key);
764
+ normalized.push(evidence);
765
+ }
766
+ return normalized;
767
+ }
768
+ function summarizeCiEvidenceFailures(evidence) {
769
+ const failedJobs = evidence.jobs
770
+ .filter((job) => job.status === "completed" && isFailingGitHubActionsConclusion(job.conclusion))
771
+ .map((job) => `${evidence.pipelineName} / ${job.name}`);
772
+ const runLevelFailure = failedJobs.length === 0 &&
773
+ evidence.status === "completed" &&
774
+ isFailingGitHubActionsConclusion(evidence.conclusion)
775
+ ? [`${evidence.pipelineName} / pipeline-run`]
776
+ : [];
777
+ return [...failedJobs, ...runLevelFailure];
778
+ }
779
+ function formatCiEvidenceStatus(evidence) {
780
+ if (evidence.status === "completed" && evidence.conclusion) {
781
+ return evidence.conclusion;
782
+ }
783
+ return evidence.status;
784
+ }
785
+ function summarizeCiEvidenceForRelease(evidence) {
786
+ const provider = evidence.providerName ?? evidence.pipelineName;
787
+ const displayLabel = `${provider} (${evidence.platform}) pipeline \`${evidence.pipelineName}\` run \`${evidence.pipelineRunId}\``;
788
+ return releaseCiEvidenceSummarySchema.parse({
789
+ provider,
790
+ platform: evidence.platform,
791
+ host: evidence.host,
792
+ repository: evidence.repository,
793
+ pipelineName: evidence.pipelineName,
794
+ pipelineRunId: evidence.pipelineRunId,
795
+ status: evidence.status,
796
+ conclusion: evidence.conclusion,
797
+ branch: evidence.branch,
798
+ commitSha: evidence.commitSha,
799
+ failingChecks: summarizeCiEvidenceFailures(evidence),
800
+ provenanceSource: evidence.provenanceSource,
801
+ displayLabel,
802
+ statusSummary: `${displayLabel} completed from ${evidence.provenanceSource} evidence with ${formatCiEvidenceStatus(evidence)}.`
803
+ });
804
+ }
250
805
  function derivePackageScope(pathValue) {
251
806
  const segments = pathValue.split("/").filter(Boolean);
252
807
  if (segments.length < 2) {
@@ -268,7 +823,7 @@ function getWorkflowInput(stateSlice, key) {
268
823
  }
269
824
  return stateSlice.workflowInputs[key];
270
825
  }
271
- function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], githubRefs = []) {
826
+ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], scmRefs = [], githubRefs = []) {
272
827
  return {
273
828
  schemaVersion: state.version,
274
829
  workflow: {
@@ -280,6 +835,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
280
835
  runId: state.runId,
281
836
  inputRefs: [...inputRefs],
282
837
  issueRefs: [...issueRefs],
838
+ scmRefs: [...scmRefs],
283
839
  githubRefs: [...githubRefs]
284
840
  },
285
841
  status: "complete",
@@ -308,7 +864,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
308
864
  summary
309
865
  };
310
866
  }
311
- function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubRefs = []) {
867
+ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, scmRefs = [], githubRefs = []) {
312
868
  return {
313
869
  schemaVersion: state.version,
314
870
  workflow: {
@@ -319,6 +875,7 @@ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubR
319
875
  runId: state.runId,
320
876
  inputRefs: [...inputRefs],
321
877
  issueRefs: [...issueRefs],
878
+ scmRefs: [...scmRefs],
322
879
  githubRefs: [...githubRefs]
323
880
  },
324
881
  status: "complete",
@@ -445,6 +1002,7 @@ const planningAnalystAgent = {
445
1002
  outputSchema: agentOutputSchema,
446
1003
  async execute({ state, stateSlice }) {
447
1004
  const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
1005
+ const planningScmRefs = getWorkflowInput(stateSlice, "planningScmRefs") ?? [];
448
1006
  const planningGithubRefs = getWorkflowInput(stateSlice, "planningGithubRefs") ?? [];
449
1007
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
450
1008
  if (!planningRequest) {
@@ -469,7 +1027,7 @@ const planningAnalystAgent = {
469
1027
  ];
470
1028
  const summary = `Planning brief scoped ${objectives.length} objective(s) for ${state.repo.name}.`;
471
1029
  const planningBrief = planningArtifactSchema.parse({
472
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningGithubRefs),
1030
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningScmRefs, planningGithubRefs),
473
1031
  artifactKind: "planning-brief",
474
1032
  lifecycleDomain: "plan",
475
1033
  workflow: {
@@ -1064,6 +1622,7 @@ const maintenanceIntakeAgent = {
1064
1622
  const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
1065
1623
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1066
1624
  const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
1625
+ const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
1067
1626
  const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
1068
1627
  if (!maintenanceRequest) {
1069
1628
  throw new Error("maintenance-triage requires a validated maintenance request before runtime execution.");
@@ -1084,6 +1643,7 @@ const maintenanceIntakeAgent = {
1084
1643
  issueRefs: [...new Set(maintenanceRequest.issueRefs)]
1085
1644
  }),
1086
1645
  maintenanceIssueRefs,
1646
+ maintenanceScmRefs,
1087
1647
  maintenanceGithubRefs,
1088
1648
  evidenceSourceCount: maintenanceRequest.dependencyAlertRefs.length +
1089
1649
  maintenanceRequest.docsTaskRefs.length +
@@ -1256,6 +1816,7 @@ const maintenanceAnalystAgent = {
1256
1816
  async execute({ state, stateSlice }) {
1257
1817
  const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
1258
1818
  const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
1819
+ const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
1259
1820
  const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
1260
1821
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1261
1822
  if (!maintenanceRequest) {
@@ -1320,7 +1881,7 @@ const maintenanceAnalystAgent = {
1320
1881
  ];
1321
1882
  const summary = `Maintenance report prepared for ${maintenanceRequest.maintenanceGoal}.`;
1322
1883
  const maintenanceReport = maintenanceArtifactSchema.parse({
1323
- ...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceGithubRefs),
1884
+ ...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceScmRefs, maintenanceGithubRefs),
1324
1885
  artifactKind: "maintenance-report",
1325
1886
  lifecycleDomain: "maintain",
1326
1887
  payload: {
@@ -1415,6 +1976,7 @@ const incidentAnalystAgent = {
1415
1976
  const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
1416
1977
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1417
1978
  const incidentIssueRefs = getWorkflowInput(stateSlice, "incidentIssueRefs") ?? [];
1979
+ const incidentScmRefs = getWorkflowInput(stateSlice, "incidentScmRefs") ?? [];
1418
1980
  const incidentGithubRefs = getWorkflowInput(stateSlice, "incidentGithubRefs") ?? [];
1419
1981
  if (!incidentRequest) {
1420
1982
  throw new Error("incident-handoff requires validated incident inputs before incident analysis.");
@@ -1457,7 +2019,7 @@ const incidentAnalystAgent = {
1457
2019
  ];
1458
2020
  const summary = `Incident brief prepared for ${incidentRequest.incidentSummary}.`;
1459
2021
  const incidentBrief = incidentArtifactSchema.parse({
1460
- ...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentGithubRefs),
2022
+ ...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentScmRefs, incidentGithubRefs),
1461
2023
  artifactKind: "incident-brief",
1462
2024
  lifecycleDomain: "operate",
1463
2025
  redaction: {
@@ -1551,6 +2113,7 @@ const releaseIntakeAgent = {
1551
2113
  async execute({ stateSlice }) {
1552
2114
  const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
1553
2115
  const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
2116
+ const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
1554
2117
  const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
1555
2118
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1556
2119
  if (!releaseRequest) {
@@ -1566,6 +2129,7 @@ const releaseIntakeAgent = {
1566
2129
  metadata: {
1567
2130
  ...releaseRequestSchema.parse(releaseRequest),
1568
2131
  releaseIssueRefs,
2132
+ releaseScmRefs,
1569
2133
  releaseGithubRefs,
1570
2134
  evidenceSourceCount: releaseRequest.qaReportRefs.length + releaseRequest.securityReportRefs.length + releaseRequest.evidenceSources.length
1571
2135
  }
@@ -1625,6 +2189,10 @@ const releaseEvidenceNormalizationAgent = {
1625
2189
  ? asStringArray(intakeMetadata.evidenceSources)
1626
2190
  : releaseRequest.evidenceSources;
1627
2191
  const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
2192
+ const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
2193
+ const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
2194
+ const attestationVerificationEvidence = normalizeAttestationVerificationEvidence(repoRoot, normalizedEvidenceSources);
2195
+ const trustSummary = buildReleaseTrustSummary(attestationVerificationEvidence);
1628
2196
  const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
1629
2197
  if (missingEvidenceSources.length > 0) {
1630
2198
  throw new Error(`Release evidence source not found: ${missingEvidenceSources[0]}`);
@@ -1643,6 +2211,17 @@ const releaseEvidenceNormalizationAgent = {
1643
2211
  manifestPath: resolved.manifestPath
1644
2212
  };
1645
2213
  });
2214
+ const dependencyManifestPaths = resolveDependencyManifestPaths(repoRoot, [
2215
+ "package.json",
2216
+ ...versionResolutions
2217
+ .map((entry) => entry.manifestPath)
2218
+ .filter((value) => Boolean(value))
2219
+ .map((manifestPath) => manifestPath.replace(`${repoRoot ?? ""}/`, ""))
2220
+ ]);
2221
+ const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", dependencyManifestPaths);
2222
+ const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
2223
+ const hasMissingDependencyIntegrity = dependencyIntegrityEvidence.some((entry) => entry.integrityStatus === "missing-lockfile");
2224
+ const hasFailedAttestationVerification = attestationVerificationEvidence.some((entry) => entry.status === "failed");
1646
2225
  const missingPackages = versionResolutions.filter((entry) => entry.status === "package-missing").map((entry) => entry.name);
1647
2226
  const versionCheckStatus = missingPackages.length === 0 ? "passed" : "failed";
1648
2227
  const baseReadinessStatus = qaReportRefs.length > 0 && securityReportRefs.length > 0
@@ -1650,7 +2229,13 @@ const releaseEvidenceNormalizationAgent = {
1650
2229
  : qaReportRefs.length > 0 || securityReportRefs.length > 0
1651
2230
  ? "partial"
1652
2231
  : "blocked";
1653
- const readinessStatus = missingPackages.length > 0 ? "blocked" : baseReadinessStatus;
2232
+ const readinessStatus = missingPackages.length > 0
2233
+ ? "blocked"
2234
+ : hasFailedAttestationVerification
2235
+ ? "blocked"
2236
+ : hasMissingDependencyIntegrity && baseReadinessStatus === "ready"
2237
+ ? "partial"
2238
+ : baseReadinessStatus;
1654
2239
  const localReadinessChecks = [
1655
2240
  {
1656
2241
  name: "qa-report-refs",
@@ -1673,6 +2258,32 @@ const releaseEvidenceNormalizationAgent = {
1673
2258
  ? `Using ${evidenceSources.length} bounded local release evidence source(s).`
1674
2259
  : "No additional local release evidence sources were supplied."
1675
2260
  },
2261
+ {
2262
+ name: "imported-ci-evidence",
2263
+ status: ciEvidence.length > 0 ? "passed" : "skipped",
2264
+ detail: ciEvidence.length > 0
2265
+ ? `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
2266
+ : "No imported CI evidence exports were supplied."
2267
+ },
2268
+ {
2269
+ name: "dependency-integrity",
2270
+ status: dependencyIntegrityEvidence.length === 0
2271
+ ? "skipped"
2272
+ : hasMissingDependencyIntegrity
2273
+ ? "failed"
2274
+ : "passed",
2275
+ detail: dependencyIntegritySignals[0] ??
2276
+ "No dependency manifests were available for bounded integrity verification."
2277
+ },
2278
+ {
2279
+ name: "attestation-verification",
2280
+ status: attestationVerificationEvidence.length === 0
2281
+ ? "skipped"
2282
+ : hasFailedAttestationVerification
2283
+ ? "failed"
2284
+ : "passed",
2285
+ detail: trustSummary[0]
2286
+ },
1676
2287
  {
1677
2288
  name: "workspace-version-targets",
1678
2289
  status: versionCheckStatus,
@@ -1706,6 +2317,8 @@ const releaseEvidenceNormalizationAgent = {
1706
2317
  ];
1707
2318
  const provenanceRefs = [
1708
2319
  ...normalizedEvidenceSources,
2320
+ ...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
2321
+ ...attestationVerificationEvidence.flatMap((entry) => entry.provenanceRefs),
1709
2322
  ...versionResolutions
1710
2323
  .map((entry) => entry.manifestPath)
1711
2324
  .filter((value) => Boolean(value))
@@ -1715,6 +2328,10 @@ const releaseEvidenceNormalizationAgent = {
1715
2328
  securityReportRefs,
1716
2329
  normalizedEvidenceSources,
1717
2330
  missingEvidenceSources: [],
2331
+ ciEvidence,
2332
+ ciEvidenceSummary,
2333
+ dependencyIntegrityEvidence,
2334
+ attestationVerificationEvidence,
1718
2335
  versionResolutions: versionResolutions.map((entry) => ({
1719
2336
  name: entry.name,
1720
2337
  targetVersion: entry.targetVersion,
@@ -1724,6 +2341,7 @@ const releaseEvidenceNormalizationAgent = {
1724
2341
  localReadinessChecks,
1725
2342
  readinessStatus,
1726
2343
  approvalRecommendations,
2344
+ trustSummary,
1727
2345
  provenanceRefs: [...new Set(provenanceRefs)]
1728
2346
  });
1729
2347
  return agentOutputSchema.parse({
@@ -1776,6 +2394,7 @@ const releaseAnalystAgent = {
1776
2394
  async execute({ state, stateSlice }) {
1777
2395
  const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
1778
2396
  const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
2397
+ const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
1779
2398
  const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
1780
2399
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1781
2400
  if (!releaseRequest) {
@@ -1801,6 +2420,13 @@ const releaseAnalystAgent = {
1801
2420
  : releaseRequest.evidenceSources;
1802
2421
  const constraints = asStringArray(intakeMetadata.constraints);
1803
2422
  const allEvidenceRefs = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
2423
+ const importedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
2424
+ const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? importedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
2425
+ const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
2426
+ const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
2427
+ const attestationVerificationEvidence = normalizedEvidence?.attestationVerificationEvidence ?? [];
2428
+ const trustSummary = normalizedEvidence?.trustSummary ?? buildReleaseTrustSummary(attestationVerificationEvidence);
2429
+ const importedCiFailures = [...new Set(ciEvidenceSummary.flatMap((entry) => entry.failingChecks))];
1804
2430
  const versionResolutions = normalizedEvidence?.versionResolutions ?? [];
1805
2431
  const verificationChecks = normalizedEvidence?.localReadinessChecks ?? [
1806
2432
  {
@@ -1823,6 +2449,13 @@ const releaseAnalystAgent = {
1823
2449
  detail: evidenceSources.length > 0
1824
2450
  ? `Using ${evidenceSources.length} bounded local release evidence source(s).`
1825
2451
  : "No additional local release evidence sources were supplied."
2452
+ },
2453
+ {
2454
+ name: "imported-ci-evidence",
2455
+ status: importedCiEvidence.length > 0 ? "passed" : "skipped",
2456
+ detail: importedCiEvidence.length > 0
2457
+ ? `Using ${importedCiEvidence.length} imported CI evidence export(s) across ${ciEvidenceSummary.map((entry) => entry.provider).join(", ")}.`
2458
+ : "No imported CI evidence exports were supplied."
1826
2459
  }
1827
2460
  ];
1828
2461
  const readinessStatus = normalizedEvidence?.readinessStatus ??
@@ -1845,7 +2478,10 @@ const releaseAnalystAgent = {
1845
2478
  ...(versionResolutions.length > 0
1846
2479
  ? [`Resolved ${versionResolutions.length} workspace version target(s) before any publish or promotion step.`]
1847
2480
  : []),
2481
+ ...dependencyIntegritySignals.map((signal) => `${signal} Review dependency integrity before any publish or promotion step.`),
2482
+ ...trustSummary.map((line) => `${line} Keep publish execution separate from verification.`),
1848
2483
  "Review the bounded QA and security evidence before invoking any publish or promotion step.",
2484
+ ...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before any publish or promotion step.`),
1849
2485
  "Run `agentforge release check --json` and `agentforge release verify --json` before any release cut.",
1850
2486
  ...approvalRecommendations.map((recommendation) => `${recommendation.action}: ${recommendation.classification.replaceAll("_", " ")} (${recommendation.reason})`),
1851
2487
  "Keep trusted publishing and tag or publish actions outside this default read-only workflow path."
@@ -1856,10 +2492,11 @@ const releaseAnalystAgent = {
1856
2492
  ];
1857
2493
  const externalDependencies = [
1858
2494
  ...(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."] : [])
2495
+ ...(securityReportRefs.length > 0 ? ["Validated security report inputs remain available for reviewer inspection."] : []),
2496
+ ...ciEvidenceSummary.map((entry) => `${entry.displayLabel} remains available for reviewer inspection.`)
1860
2497
  ];
1861
2498
  const releaseReport = releaseArtifactSchema.parse({
1862
- ...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseGithubRefs),
2499
+ ...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseScmRefs, releaseGithubRefs),
1863
2500
  artifactKind: "release-report",
1864
2501
  lifecycleDomain: "release",
1865
2502
  payload: {
@@ -1868,9 +2505,12 @@ const releaseAnalystAgent = {
1868
2505
  readinessStatus,
1869
2506
  verificationChecks: verificationChecks.map((check) => ({ ...check })),
1870
2507
  versionResolutions,
2508
+ ciEvidenceSummary,
2509
+ dependencyIntegritySignals,
2510
+ trustSummary,
1871
2511
  approvalRecommendations: approvalRecommendations.map((recommendation) => releaseApprovalRecommendationSchema.parse(recommendation)),
1872
2512
  publishingPlan,
1873
- trustStatus: "trusted-publishing-reviewed-separately",
2513
+ trustStatus: resolveReleaseTrustStatus(attestationVerificationEvidence),
1874
2514
  publishedPackages: [],
1875
2515
  tagRefs: [],
1876
2516
  provenanceRefs: allEvidenceRefs,
@@ -1892,6 +2532,10 @@ const releaseAnalystAgent = {
1892
2532
  qaReportRefs,
1893
2533
  securityReportRefs,
1894
2534
  evidenceSources,
2535
+ ciEvidence: importedCiEvidence,
2536
+ ciEvidenceSummary,
2537
+ dependencyIntegrityEvidence,
2538
+ attestationVerificationEvidence,
1895
2539
  constraints,
1896
2540
  normalizedEvidence: normalizedEvidence ?? null
1897
2541
  },
@@ -1899,18 +2543,20 @@ const releaseAnalystAgent = {
1899
2543
  readinessStatus,
1900
2544
  approvalRecommendations,
1901
2545
  publishingPlan,
1902
- rollbackNotes
2546
+ rollbackNotes,
2547
+ trustSummary,
2548
+ importedCiFailures
1903
2549
  }
1904
2550
  }
1905
2551
  });
1906
2552
  }
1907
2553
  };
1908
- const securityEvidenceNormalizationAgent = {
2554
+ const pipelineIntakeAgent = {
1909
2555
  manifest: agentManifestSchema.parse({
1910
2556
  version: 1,
1911
- name: "security-evidence-normalizer",
1912
- displayName: "Security Evidence Normalizer",
1913
- category: "security",
2557
+ name: "pipeline-intake",
2558
+ displayName: "Pipeline Intake",
2559
+ category: "release",
1914
2560
  runtime: {
1915
2561
  minVersion: "0.1.0",
1916
2562
  kind: "deterministic"
@@ -1919,7 +2565,72 @@ const securityEvidenceNormalizationAgent = {
1919
2565
  model: false,
1920
2566
  network: false,
1921
2567
  tools: [],
1922
- readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
2568
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
2569
+ writePaths: []
2570
+ },
2571
+ inputs: ["workflowInputs", "repo"],
2572
+ outputs: ["summary", "metadata"],
2573
+ contextPolicy: {
2574
+ sections: ["workflowInputs", "repo", "context"],
2575
+ minimalContext: true
2576
+ },
2577
+ catalog: {
2578
+ domain: "release",
2579
+ supportLevel: "internal",
2580
+ maturity: "mvp",
2581
+ trustScope: "official-core-only"
2582
+ },
2583
+ trust: {
2584
+ tier: "core",
2585
+ source: "official",
2586
+ reviewed: true
2587
+ }
2588
+ }),
2589
+ outputSchema: agentOutputSchema,
2590
+ async execute({ stateSlice }) {
2591
+ const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
2592
+ const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
2593
+ const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
2594
+ const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
2595
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
2596
+ if (!pipelineRequest) {
2597
+ throw new Error("pipeline-evidence-review requires a validated pipeline request before runtime execution.");
2598
+ }
2599
+ return agentOutputSchema.parse({
2600
+ summary: `Loaded pipeline request from ${requestFile ?? ".agentops/requests/pipeline.yaml"} for ${pipelineRequest.pipelineScope}.`,
2601
+ findings: [],
2602
+ proposedActions: [],
2603
+ lifecycleArtifacts: [],
2604
+ requestedTools: [],
2605
+ blockedActionFlags: [],
2606
+ metadata: {
2607
+ ...pipelineRequestSchema.parse(pipelineRequest),
2608
+ pipelineIssueRefs,
2609
+ pipelineScmRefs,
2610
+ pipelineGithubRefs,
2611
+ evidenceSourceCount: pipelineRequest.evidenceSources.length +
2612
+ pipelineRequest.qaReportRefs.length +
2613
+ pipelineRequest.securityReportRefs.length +
2614
+ pipelineRequest.releaseReportRefs.length
2615
+ }
2616
+ });
2617
+ }
2618
+ };
2619
+ const pipelineEvidenceNormalizationAgent = {
2620
+ manifest: agentManifestSchema.parse({
2621
+ version: 1,
2622
+ name: "pipeline-evidence-normalizer",
2623
+ displayName: "Pipeline Evidence Normalizer",
2624
+ category: "release",
2625
+ runtime: {
2626
+ minVersion: "0.1.0",
2627
+ kind: "deterministic"
2628
+ },
2629
+ permissions: {
2630
+ model: false,
2631
+ network: false,
2632
+ tools: [],
2633
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
1923
2634
  writePaths: []
1924
2635
  },
1925
2636
  inputs: ["workflowInputs", "repo", "agentResults"],
@@ -1929,7 +2640,7 @@ const securityEvidenceNormalizationAgent = {
1929
2640
  minimalContext: true
1930
2641
  },
1931
2642
  catalog: {
1932
- domain: "security",
2643
+ domain: "release",
1933
2644
  supportLevel: "internal",
1934
2645
  maturity: "mvp",
1935
2646
  trustScope: "official-core-only"
@@ -1942,8 +2653,576 @@ const securityEvidenceNormalizationAgent = {
1942
2653
  }),
1943
2654
  outputSchema: agentOutputSchema,
1944
2655
  async execute({ stateSlice }) {
1945
- const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
1946
- if (!securityRequest) {
2656
+ const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
2657
+ if (!pipelineRequest) {
2658
+ throw new Error("pipeline-evidence-review requires validated pipeline request inputs before evidence normalization.");
2659
+ }
2660
+ const repoRoot = stateSlice.repo?.root;
2661
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
2662
+ const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
2663
+ ? asStringArray(intakeMetadata.qaReportRefs)
2664
+ : pipelineRequest.qaReportRefs;
2665
+ const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
2666
+ ? asStringArray(intakeMetadata.securityReportRefs)
2667
+ : pipelineRequest.securityReportRefs;
2668
+ const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
2669
+ ? asStringArray(intakeMetadata.releaseReportRefs)
2670
+ : pipelineRequest.releaseReportRefs;
2671
+ const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
2672
+ ? asStringArray(intakeMetadata.evidenceSources)
2673
+ : pipelineRequest.evidenceSources;
2674
+ const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...evidenceSources])];
2675
+ const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
2676
+ if (missingEvidenceSources.length > 0) {
2677
+ throw new Error(`Pipeline evidence source not found: ${missingEvidenceSources[0]}`);
2678
+ }
2679
+ const referencedArtifactKinds = [
2680
+ ...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
2681
+ ];
2682
+ const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
2683
+ const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
2684
+ const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
2685
+ const verificationChecks = [
2686
+ {
2687
+ name: "referenced-artifacts",
2688
+ status: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0 ? "passed" : "skipped",
2689
+ detail: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0
2690
+ ? `Validated ${qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length} referenced artifact bundle(s).`
2691
+ : "No upstream lifecycle artifact references were supplied."
2692
+ },
2693
+ {
2694
+ name: "imported-ci-evidence",
2695
+ status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
2696
+ detail: ciEvidence.length === 0
2697
+ ? "No imported CI evidence exports were supplied."
2698
+ : failingChecks.length > 0
2699
+ ? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
2700
+ : `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
2701
+ }
2702
+ ];
2703
+ const reviewStatus = ciEvidence.length === 0 || failingChecks.length > 0
2704
+ ? "blocked"
2705
+ : "ready";
2706
+ const normalization = pipelineEvidenceNormalizationSchema.parse({
2707
+ qaReportRefs,
2708
+ securityReportRefs,
2709
+ releaseReportRefs,
2710
+ normalizedEvidenceSources,
2711
+ missingEvidenceSources: [],
2712
+ ciEvidence,
2713
+ ciEvidenceSummary,
2714
+ referencedArtifactKinds,
2715
+ verificationChecks,
2716
+ reviewStatus,
2717
+ provenanceRefs: normalizedEvidenceSources
2718
+ });
2719
+ return agentOutputSchema.parse({
2720
+ summary: `Normalized pipeline evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
2721
+ findings: [],
2722
+ proposedActions: [],
2723
+ lifecycleArtifacts: [],
2724
+ requestedTools: [],
2725
+ blockedActionFlags: [],
2726
+ metadata: normalization
2727
+ });
2728
+ }
2729
+ };
2730
+ const pipelineAnalystAgent = {
2731
+ manifest: agentManifestSchema.parse({
2732
+ version: 1,
2733
+ name: "pipeline-analyst",
2734
+ displayName: "Pipeline Analyst",
2735
+ category: "release",
2736
+ runtime: {
2737
+ minVersion: "0.1.0",
2738
+ kind: "reasoning"
2739
+ },
2740
+ permissions: {
2741
+ model: true,
2742
+ network: false,
2743
+ tools: [],
2744
+ readPaths: ["**/*"],
2745
+ writePaths: []
2746
+ },
2747
+ inputs: ["workflowInputs", "repo", "agentResults"],
2748
+ outputs: ["lifecycleArtifacts"],
2749
+ contextPolicy: {
2750
+ sections: ["workflowInputs", "repo", "agentResults"],
2751
+ minimalContext: true
2752
+ },
2753
+ catalog: {
2754
+ domain: "release",
2755
+ supportLevel: "internal",
2756
+ maturity: "mvp",
2757
+ trustScope: "official-core-only"
2758
+ },
2759
+ trust: {
2760
+ tier: "core",
2761
+ source: "official",
2762
+ reviewed: true
2763
+ }
2764
+ }),
2765
+ outputSchema: agentOutputSchema,
2766
+ async execute({ state, stateSlice }) {
2767
+ const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
2768
+ const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
2769
+ const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
2770
+ const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
2771
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
2772
+ if (!pipelineRequest) {
2773
+ throw new Error("pipeline-evidence-review requires validated pipeline inputs before analysis.");
2774
+ }
2775
+ const evidenceMetadata = pipelineEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
2776
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
2777
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
2778
+ ? normalizedEvidence.normalizedEvidenceSources
2779
+ : pipelineRequest.evidenceSources;
2780
+ const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
2781
+ const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
2782
+ const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
2783
+ const blockers = [
2784
+ ...verificationChecks
2785
+ .filter((check) => check.status === "failed")
2786
+ .map((check) => check.detail ?? `${check.name} failed during deterministic pipeline review.`)
2787
+ ];
2788
+ const riskSummary = [
2789
+ ...(pipelineRequest.focusAreas.length > 0
2790
+ ? [`Focused review areas still require human interpretation: ${pipelineRequest.focusAreas.join(", ")}.`]
2791
+ : []),
2792
+ ...(referencedArtifactKinds.length > 0
2793
+ ? [`Referenced lifecycle artifacts remain in scope: ${referencedArtifactKinds.join(", ")}.`]
2794
+ : []),
2795
+ ...(ciEvidenceSummary.length > 0
2796
+ ? [`Imported CI provenance spans ${[...new Set(ciEvidenceSummary.map((entry) => entry.provider))].join(", ")}.`]
2797
+ : [])
2798
+ ];
2799
+ const recommendedNextSteps = [
2800
+ ...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before moving to deployment or promotion review.`),
2801
+ ...(pipelineRequest.qaReportRefs.length + pipelineRequest.securityReportRefs.length + pipelineRequest.releaseReportRefs.length > 0
2802
+ ? ["Carry the validated QA, security, and release artifacts forward into deployment-gate-review."]
2803
+ : ["Attach downstream QA, security, or release artifacts before using this pipeline report as a deployment gate input."]),
2804
+ ...(pipelineRequest.constraints.length > 0 ? [`Keep follow-up bounded by: ${pipelineRequest.constraints.join("; ")}.`] : [])
2805
+ ];
2806
+ const reviewStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.reviewStatus ?? "ready";
2807
+ const summary = `Pipeline report prepared for ${pipelineRequest.pipelineScope}.`;
2808
+ const pipelineReport = pipelineArtifactSchema.parse({
2809
+ ...buildLifecycleArtifactEnvelopeBase(state, "Pipeline Evidence Review", summary, [requestFile ?? ".agentops/requests/pipeline.yaml", ...new Set(evidenceSources)], pipelineIssueRefs, pipelineScmRefs, pipelineGithubRefs),
2810
+ artifactKind: "pipeline-report",
2811
+ lifecycleDomain: "release",
2812
+ payload: {
2813
+ pipelineScope: pipelineRequest.pipelineScope,
2814
+ evidenceSources,
2815
+ verificationChecks,
2816
+ ciEvidenceSummary,
2817
+ reviewStatus,
2818
+ blockers,
2819
+ riskSummary,
2820
+ recommendedNextSteps: recommendedNextSteps.length > 0 ? recommendedNextSteps : ["Capture additional bounded CI evidence before follow-on review."],
2821
+ referencedArtifactKinds,
2822
+ provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
2823
+ }
2824
+ });
2825
+ return agentOutputSchema.parse({
2826
+ summary,
2827
+ findings: [],
2828
+ proposedActions: [],
2829
+ lifecycleArtifacts: [pipelineReport],
2830
+ requestedTools: [],
2831
+ blockedActionFlags: [],
2832
+ confidence: 0.76,
2833
+ metadata: {
2834
+ deterministicInputs: {
2835
+ evidenceSources,
2836
+ ciEvidenceSummary,
2837
+ verificationChecks,
2838
+ referencedArtifactKinds,
2839
+ focusAreas: pipelineRequest.focusAreas,
2840
+ constraints: pipelineRequest.constraints
2841
+ },
2842
+ synthesizedAssessment: {
2843
+ reviewStatus,
2844
+ blockers,
2845
+ riskSummary,
2846
+ recommendedNextSteps: pipelineReport.payload.recommendedNextSteps
2847
+ }
2848
+ }
2849
+ });
2850
+ }
2851
+ };
2852
+ const deploymentGateIntakeAgent = {
2853
+ manifest: agentManifestSchema.parse({
2854
+ version: 1,
2855
+ name: "deployment-gate-intake",
2856
+ displayName: "Deployment Gate Intake",
2857
+ category: "release",
2858
+ runtime: {
2859
+ minVersion: "0.1.0",
2860
+ kind: "deterministic"
2861
+ },
2862
+ permissions: {
2863
+ model: false,
2864
+ network: false,
2865
+ tools: [],
2866
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
2867
+ writePaths: []
2868
+ },
2869
+ inputs: ["workflowInputs", "repo"],
2870
+ outputs: ["summary", "metadata"],
2871
+ contextPolicy: {
2872
+ sections: ["workflowInputs", "repo", "context"],
2873
+ minimalContext: true
2874
+ },
2875
+ catalog: {
2876
+ domain: "release",
2877
+ supportLevel: "internal",
2878
+ maturity: "mvp",
2879
+ trustScope: "official-core-only"
2880
+ },
2881
+ trust: {
2882
+ tier: "core",
2883
+ source: "official",
2884
+ reviewed: true
2885
+ }
2886
+ }),
2887
+ outputSchema: agentOutputSchema,
2888
+ async execute({ stateSlice }) {
2889
+ const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
2890
+ const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
2891
+ const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
2892
+ const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
2893
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
2894
+ if (!deploymentRequest) {
2895
+ throw new Error("deployment-gate-review requires a validated deployment request before runtime execution.");
2896
+ }
2897
+ return agentOutputSchema.parse({
2898
+ summary: `Loaded deployment request from ${requestFile ?? ".agentops/requests/deployment.yaml"} for ${deploymentRequest.targetEnvironment}.`,
2899
+ findings: [],
2900
+ proposedActions: [],
2901
+ lifecycleArtifacts: [],
2902
+ requestedTools: [],
2903
+ blockedActionFlags: [],
2904
+ metadata: {
2905
+ ...deploymentRequestSchema.parse(deploymentRequest),
2906
+ deploymentIssueRefs,
2907
+ deploymentScmRefs,
2908
+ deploymentGithubRefs,
2909
+ evidenceSourceCount: deploymentRequest.evidenceSources.length +
2910
+ deploymentRequest.qaReportRefs.length +
2911
+ deploymentRequest.securityReportRefs.length +
2912
+ deploymentRequest.releaseReportRefs.length +
2913
+ deploymentRequest.pipelineReportRefs.length
2914
+ }
2915
+ });
2916
+ }
2917
+ };
2918
+ const deploymentGateEvidenceNormalizationAgent = {
2919
+ manifest: agentManifestSchema.parse({
2920
+ version: 1,
2921
+ name: "deployment-gate-evidence-normalizer",
2922
+ displayName: "Deployment Gate Evidence Normalizer",
2923
+ category: "release",
2924
+ runtime: {
2925
+ minVersion: "0.1.0",
2926
+ kind: "deterministic"
2927
+ },
2928
+ permissions: {
2929
+ model: false,
2930
+ network: false,
2931
+ tools: [],
2932
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
2933
+ writePaths: []
2934
+ },
2935
+ inputs: ["workflowInputs", "repo", "agentResults"],
2936
+ outputs: ["summary", "metadata"],
2937
+ contextPolicy: {
2938
+ sections: ["workflowInputs", "repo", "agentResults"],
2939
+ minimalContext: true
2940
+ },
2941
+ catalog: {
2942
+ domain: "release",
2943
+ supportLevel: "internal",
2944
+ maturity: "mvp",
2945
+ trustScope: "official-core-only"
2946
+ },
2947
+ trust: {
2948
+ tier: "core",
2949
+ source: "official",
2950
+ reviewed: true
2951
+ }
2952
+ }),
2953
+ outputSchema: agentOutputSchema,
2954
+ async execute({ stateSlice }) {
2955
+ const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
2956
+ if (!deploymentRequest) {
2957
+ throw new Error("deployment-gate-review requires validated deployment request inputs before evidence normalization.");
2958
+ }
2959
+ const repoRoot = stateSlice.repo?.root;
2960
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
2961
+ const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
2962
+ ? asStringArray(intakeMetadata.qaReportRefs)
2963
+ : deploymentRequest.qaReportRefs;
2964
+ const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
2965
+ ? asStringArray(intakeMetadata.securityReportRefs)
2966
+ : deploymentRequest.securityReportRefs;
2967
+ const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
2968
+ ? asStringArray(intakeMetadata.releaseReportRefs)
2969
+ : deploymentRequest.releaseReportRefs;
2970
+ const pipelineReportRefs = asStringArray(intakeMetadata.pipelineReportRefs).length > 0
2971
+ ? asStringArray(intakeMetadata.pipelineReportRefs)
2972
+ : deploymentRequest.pipelineReportRefs;
2973
+ const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
2974
+ ? asStringArray(intakeMetadata.evidenceSources)
2975
+ : deploymentRequest.evidenceSources;
2976
+ const normalizedEvidenceSources = [
2977
+ ...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs, ...evidenceSources])
2978
+ ];
2979
+ const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
2980
+ if (missingEvidenceSources.length > 0) {
2981
+ throw new Error(`Deployment evidence source not found: ${missingEvidenceSources[0]}`);
2982
+ }
2983
+ const referencedArtifactKinds = [
2984
+ ...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
2985
+ ];
2986
+ const releaseReportReadiness = releaseReportRefs.map((bundleRef) => repoRoot
2987
+ ? evaluateReleaseReportReadiness(join(repoRoot, bundleRef))
2988
+ : { ready: false, detail: `Release report reference cannot be evaluated without a repository root: ${bundleRef}` });
2989
+ const pipelineReportReadiness = pipelineReportRefs.map((bundleRef) => repoRoot
2990
+ ? evaluatePipelineReportReadiness(join(repoRoot, bundleRef))
2991
+ : { ready: false, detail: `Pipeline report reference cannot be evaluated without a repository root: ${bundleRef}` });
2992
+ const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
2993
+ const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
2994
+ const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
2995
+ const verificationChecks = [
2996
+ {
2997
+ name: "qa-report-refs",
2998
+ status: qaReportRefs.length > 0 ? "passed" : "skipped",
2999
+ detail: qaReportRefs.length > 0 ? `Using ${qaReportRefs.length} validated QA report reference(s).` : "No QA report references were supplied."
3000
+ },
3001
+ {
3002
+ name: "security-report-refs",
3003
+ status: securityReportRefs.length > 0 ? "passed" : "skipped",
3004
+ detail: securityReportRefs.length > 0
3005
+ ? `Using ${securityReportRefs.length} validated security report reference(s).`
3006
+ : "No security report references were supplied."
3007
+ },
3008
+ {
3009
+ name: "release-report-refs",
3010
+ status: releaseReportRefs.length === 0
3011
+ ? "skipped"
3012
+ : releaseReportReadiness.some((entry) => !entry.ready)
3013
+ ? "failed"
3014
+ : "passed",
3015
+ detail: releaseReportRefs.length > 0
3016
+ ? releaseReportReadiness.some((entry) => !entry.ready)
3017
+ ? releaseReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
3018
+ : `Using ${releaseReportRefs.length} ready release report reference(s).`
3019
+ : "No release report references were supplied."
3020
+ },
3021
+ {
3022
+ name: "pipeline-report-refs",
3023
+ status: pipelineReportRefs.length === 0
3024
+ ? "skipped"
3025
+ : pipelineReportReadiness.some((entry) => !entry.ready)
3026
+ ? "failed"
3027
+ : "passed",
3028
+ detail: pipelineReportRefs.length > 0
3029
+ ? pipelineReportReadiness.some((entry) => !entry.ready)
3030
+ ? pipelineReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
3031
+ : `Using ${pipelineReportRefs.length} ready pipeline report reference(s).`
3032
+ : "No pipeline report references were supplied."
3033
+ },
3034
+ {
3035
+ name: "imported-ci-evidence",
3036
+ status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
3037
+ detail: ciEvidence.length === 0
3038
+ ? "No imported CI evidence exports were supplied."
3039
+ : failingChecks.length > 0
3040
+ ? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
3041
+ : `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
3042
+ }
3043
+ ];
3044
+ const gateStatus = ciEvidence.length === 0 || failingChecks.length > 0
3045
+ ? "blocked"
3046
+ : qaReportRefs.length > 0 &&
3047
+ securityReportRefs.length > 0 &&
3048
+ releaseReportRefs.length > 0 &&
3049
+ pipelineReportRefs.length > 0
3050
+ ? "ready_for_approval"
3051
+ : "conditionally_ready";
3052
+ const normalization = deploymentGateEvidenceNormalizationSchema.parse({
3053
+ qaReportRefs,
3054
+ securityReportRefs,
3055
+ releaseReportRefs,
3056
+ pipelineReportRefs,
3057
+ normalizedEvidenceSources,
3058
+ missingEvidenceSources: [],
3059
+ ciEvidence,
3060
+ ciEvidenceSummary,
3061
+ referencedArtifactKinds,
3062
+ verificationChecks,
3063
+ gateStatus,
3064
+ provenanceRefs: normalizedEvidenceSources
3065
+ });
3066
+ return agentOutputSchema.parse({
3067
+ summary: `Normalized deployment-gate evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
3068
+ findings: [],
3069
+ proposedActions: [],
3070
+ lifecycleArtifacts: [],
3071
+ requestedTools: [],
3072
+ blockedActionFlags: [],
3073
+ metadata: normalization
3074
+ });
3075
+ }
3076
+ };
3077
+ const deploymentGateAnalystAgent = {
3078
+ manifest: agentManifestSchema.parse({
3079
+ version: 1,
3080
+ name: "deployment-gate-analyst",
3081
+ displayName: "Deployment Gate Analyst",
3082
+ category: "release",
3083
+ runtime: {
3084
+ minVersion: "0.1.0",
3085
+ kind: "reasoning"
3086
+ },
3087
+ permissions: {
3088
+ model: true,
3089
+ network: false,
3090
+ tools: [],
3091
+ readPaths: ["**/*"],
3092
+ writePaths: []
3093
+ },
3094
+ inputs: ["workflowInputs", "repo", "agentResults"],
3095
+ outputs: ["lifecycleArtifacts"],
3096
+ contextPolicy: {
3097
+ sections: ["workflowInputs", "repo", "agentResults"],
3098
+ minimalContext: true
3099
+ },
3100
+ catalog: {
3101
+ domain: "release",
3102
+ supportLevel: "internal",
3103
+ maturity: "mvp",
3104
+ trustScope: "official-core-only"
3105
+ },
3106
+ trust: {
3107
+ tier: "core",
3108
+ source: "official",
3109
+ reviewed: true
3110
+ }
3111
+ }),
3112
+ outputSchema: agentOutputSchema,
3113
+ async execute({ state, stateSlice }) {
3114
+ const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
3115
+ const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
3116
+ const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
3117
+ const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
3118
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
3119
+ if (!deploymentRequest) {
3120
+ throw new Error("deployment-gate-review requires validated deployment inputs before analysis.");
3121
+ }
3122
+ const evidenceMetadata = deploymentGateEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
3123
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
3124
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
3125
+ ? normalizedEvidence.normalizedEvidenceSources
3126
+ : deploymentRequest.evidenceSources;
3127
+ const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
3128
+ const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
3129
+ const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
3130
+ const blockers = [
3131
+ ...verificationChecks
3132
+ .filter((check) => check.status === "failed")
3133
+ .map((check) => check.detail ?? `${check.name} failed during deterministic deployment-gate review.`)
3134
+ ];
3135
+ const requiredFollowUpChecks = [
3136
+ ...verificationChecks
3137
+ .filter((check) => check.status === "skipped")
3138
+ .map((check) => check.detail ?? `${check.name} still needs explicit follow-up.`),
3139
+ ...ciEvidenceSummary.map((entry) => `Confirm ${entry.displayLabel} remains current for the ${deploymentRequest.targetEnvironment} candidate.`),
3140
+ ...(blockers.length === 0 ? ["Obtain explicit maintainer approval before any deploy, publish, or promotion action."] : [])
3141
+ ];
3142
+ const gateStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.gateStatus ?? "conditionally_ready";
3143
+ const summary = `Deployment gate report prepared for ${deploymentRequest.targetEnvironment}.`;
3144
+ const deploymentGateReport = deploymentGateArtifactSchema.parse({
3145
+ ...buildLifecycleArtifactEnvelopeBase(state, "Deployment Gate Review", summary, [requestFile ?? ".agentops/requests/deployment.yaml", ...new Set(evidenceSources)], deploymentIssueRefs, deploymentScmRefs, deploymentGithubRefs),
3146
+ artifactKind: "deployment-gate-report",
3147
+ lifecycleDomain: "release",
3148
+ payload: {
3149
+ deploymentScope: deploymentRequest.deploymentScope,
3150
+ targetEnvironment: deploymentRequest.targetEnvironment,
3151
+ evidenceSources,
3152
+ verificationChecks,
3153
+ ciEvidenceSummary,
3154
+ gateStatus,
3155
+ blockers,
3156
+ requiredFollowUpChecks,
3157
+ referencedArtifactKinds,
3158
+ provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
3159
+ }
3160
+ });
3161
+ return agentOutputSchema.parse({
3162
+ summary,
3163
+ findings: [],
3164
+ proposedActions: [],
3165
+ lifecycleArtifacts: [deploymentGateReport],
3166
+ requestedTools: [],
3167
+ blockedActionFlags: [],
3168
+ confidence: 0.77,
3169
+ metadata: {
3170
+ deterministicInputs: {
3171
+ targetEnvironment: deploymentRequest.targetEnvironment,
3172
+ evidenceSources,
3173
+ ciEvidenceSummary,
3174
+ verificationChecks,
3175
+ referencedArtifactKinds,
3176
+ constraints: deploymentRequest.constraints
3177
+ },
3178
+ synthesizedAssessment: {
3179
+ gateStatus,
3180
+ blockers,
3181
+ requiredFollowUpChecks: deploymentGateReport.payload.requiredFollowUpChecks
3182
+ }
3183
+ }
3184
+ });
3185
+ }
3186
+ };
3187
+ const securityEvidenceNormalizationAgent = {
3188
+ manifest: agentManifestSchema.parse({
3189
+ version: 1,
3190
+ name: "security-evidence-normalizer",
3191
+ displayName: "Security Evidence Normalizer",
3192
+ category: "security",
3193
+ runtime: {
3194
+ minVersion: "0.1.0",
3195
+ kind: "deterministic"
3196
+ },
3197
+ permissions: {
3198
+ model: false,
3199
+ network: false,
3200
+ tools: [],
3201
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
3202
+ writePaths: []
3203
+ },
3204
+ inputs: ["workflowInputs", "repo", "agentResults"],
3205
+ outputs: ["summary", "metadata"],
3206
+ contextPolicy: {
3207
+ sections: ["workflowInputs", "repo", "agentResults"],
3208
+ minimalContext: true
3209
+ },
3210
+ catalog: {
3211
+ domain: "security",
3212
+ supportLevel: "internal",
3213
+ maturity: "mvp",
3214
+ trustScope: "official-core-only"
3215
+ },
3216
+ trust: {
3217
+ tier: "core",
3218
+ source: "official",
3219
+ reviewed: true
3220
+ }
3221
+ }),
3222
+ outputSchema: agentOutputSchema,
3223
+ async execute({ stateSlice }) {
3224
+ const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
3225
+ if (!securityRequest) {
1947
3226
  throw new Error("security-review requires validated security request inputs before evidence normalization.");
1948
3227
  }
1949
3228
  const repoRoot = stateSlice.repo?.root;
@@ -1965,15 +3244,19 @@ const securityEvidenceNormalizationAgent = {
1965
3244
  const affectedPackages = targetType === "artifact-bundle"
1966
3245
  ? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
1967
3246
  : [];
3247
+ const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", resolveDependencyManifestPaths(repoRoot, ["package.json", ...affectedPackages]));
3248
+ const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
1968
3249
  const securitySignals = [
1969
3250
  ...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
1970
3251
  ...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
1971
3252
  ...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
3253
+ ...dependencyIntegritySignals,
1972
3254
  "Security evidence collection remains local, read-only, and bounded to validated references."
1973
3255
  ];
1974
3256
  const provenanceRefs = [
1975
3257
  securityRequest.targetRef,
1976
3258
  ...securityRequest.evidenceSources,
3259
+ ...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
1977
3260
  ...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
1978
3261
  ];
1979
3262
  const normalization = securityEvidenceNormalizationSchema.parse({
@@ -1984,6 +3267,7 @@ const securityEvidenceNormalizationAgent = {
1984
3267
  missingEvidenceSources: [],
1985
3268
  normalizedFocusAreas,
1986
3269
  securitySignals,
3270
+ dependencyIntegrityEvidence,
1987
3271
  provenanceRefs: [...new Set(provenanceRefs)],
1988
3272
  affectedPackages
1989
3273
  });
@@ -2037,6 +3321,7 @@ const securityAnalystAgent = {
2037
3321
  async execute({ state, stateSlice }) {
2038
3322
  const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
2039
3323
  const securityIssueRefs = getWorkflowInput(stateSlice, "securityIssueRefs") ?? [];
3324
+ const securityScmRefs = getWorkflowInput(stateSlice, "securityScmRefs") ?? [];
2040
3325
  const securityGithubRefs = getWorkflowInput(stateSlice, "securityGithubRefs") ?? [];
2041
3326
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
2042
3327
  if (!securityRequest) {
@@ -2048,6 +3333,8 @@ const securityAnalystAgent = {
2048
3333
  const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? asStringArray(intakeMetadata.referencedArtifactKinds);
2049
3334
  const normalizedFocusAreas = normalizedEvidence?.normalizedFocusAreas ?? asStringArray(intakeMetadata.focusAreas);
2050
3335
  const normalizedConstraints = asStringArray(intakeMetadata.constraints);
3336
+ const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
3337
+ const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
2051
3338
  const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
2052
3339
  ? normalizedEvidence.normalizedEvidenceSources
2053
3340
  : asStringArray(intakeMetadata.evidenceSources).length > 0
@@ -2084,6 +3371,7 @@ const securityAnalystAgent = {
2084
3371
  ];
2085
3372
  const followUpWork = [
2086
3373
  ...(normalizedEvidence?.securitySignals ?? []),
3374
+ ...dependencyIntegritySignals,
2087
3375
  ...(referencedArtifactKinds.length > 0
2088
3376
  ? [`Confirm the security posture for referenced artifacts: ${referencedArtifactKinds.join(", ")}.`]
2089
3377
  : []),
@@ -2094,7 +3382,7 @@ const securityAnalystAgent = {
2094
3382
  ...buildLifecycleArtifactEnvelopeBase(state, "Security Review", summary, [
2095
3383
  requestFile ?? ".agentops/requests/security.yaml",
2096
3384
  ...(normalizedEvidence?.provenanceRefs ?? [securityRequest.targetRef, ...securityRequest.evidenceSources])
2097
- ], securityIssueRefs, securityGithubRefs),
3385
+ ], securityIssueRefs, securityScmRefs, securityGithubRefs),
2098
3386
  artifactKind: "security-report",
2099
3387
  lifecycleDomain: "security",
2100
3388
  redaction: {
@@ -2115,7 +3403,8 @@ const securityAnalystAgent = {
2115
3403
  : securityRequest.releaseContext === "candidate"
2116
3404
  ? "candidate release requires explicit security review before promotion."
2117
3405
  : "no release context was supplied; security output remains advisory.",
2118
- followUpWork
3406
+ followUpWork,
3407
+ dependencyIntegritySignals
2119
3408
  }
2120
3409
  });
2121
3410
  return agentOutputSchema.parse({
@@ -2133,6 +3422,7 @@ const securityAnalystAgent = {
2133
3422
  focusAreas,
2134
3423
  constraints: normalizedConstraints,
2135
3424
  referencedArtifactKinds,
3425
+ dependencyIntegrityEvidence,
2136
3426
  normalizedEvidence: normalizedEvidence ?? null
2137
3427
  },
2138
3428
  synthesizedAssessment: {
@@ -2217,6 +3507,7 @@ const qaEvidenceNormalizationAgent = {
2217
3507
  const allowlistedCommands = new Set(allowedValidationCommands.map((entry) => entry.command));
2218
3508
  const normalizedExecutedChecks = qaRequest.executedChecks.map(normalizeRequestedCommand);
2219
3509
  const unrecognizedExecutedChecks = normalizedExecutedChecks.filter((command) => !allowlistedCommands.has(command));
3510
+ const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
2220
3511
  const githubActions = normalizeGitHubActionsEvidence(repoRoot, normalizedEvidenceSources);
2221
3512
  const normalization = qaEvidenceNormalizationSchema.parse({
2222
3513
  targetRef: qaRequest.targetRef,
@@ -2228,10 +3519,11 @@ const qaEvidenceNormalizationAgent = {
2228
3519
  unrecognizedExecutedChecks,
2229
3520
  affectedPackages,
2230
3521
  allowedValidationCommands,
3522
+ ciEvidence,
2231
3523
  githubActions
2232
3524
  });
2233
3525
  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).`,
3526
+ 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
3527
  findings: [],
2236
3528
  proposedActions: [],
2237
3529
  lifecycleArtifacts: [],
@@ -2280,6 +3572,7 @@ const qaAnalystAgent = {
2280
3572
  async execute({ state, stateSlice }) {
2281
3573
  const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
2282
3574
  const qaIssueRefs = getWorkflowInput(stateSlice, "qaIssueRefs") ?? [];
3575
+ const qaScmRefs = getWorkflowInput(stateSlice, "qaScmRefs") ?? [];
2283
3576
  const qaGithubRefs = getWorkflowInput(stateSlice, "qaGithubRefs") ?? [];
2284
3577
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
2285
3578
  if (!qaRequest) {
@@ -2302,6 +3595,10 @@ const qaAnalystAgent = {
2302
3595
  failingChecks: [],
2303
3596
  provenanceRefs: []
2304
3597
  };
3598
+ const normalizedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
3599
+ const normalizedCiEvidenceSummary = normalizedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
3600
+ const normalizedCiWorkflowNames = [...new Set(normalizedCiEvidence.map((entry) => entry.pipelineName))];
3601
+ const normalizedCiFailingChecks = [...new Set(normalizedCiEvidence.flatMap((entry) => summarizeCiEvidenceFailures(entry)))];
2305
3602
  const targetType = normalizedEvidence?.targetType
2306
3603
  ? normalizedEvidence.targetType
2307
3604
  : typeof intakeMetadata.targetType === "string"
@@ -2341,12 +3638,14 @@ const qaAnalystAgent = {
2341
3638
  ...(focusAreas.includes("coverage") ? ["Coverage evidence still needs deterministic normalization before it can be promoted to an official QA signal."] : []),
2342
3639
  ...(executedChecks.length === 0 ? ["No executed validation checks were recorded in the request."] : []),
2343
3640
  ...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}`)
3641
+ ...normalizedGithubActions.failingChecks.map((checkName) => `GitHub Actions evidence still reports a failing check that needs manual review: ${checkName}`),
3642
+ ...normalizedCiFailingChecks.map((checkName) => `Imported CI evidence still reports a failing check that needs manual review: ${checkName}`)
2345
3643
  ];
2346
3644
  const recommendedNextChecks = [
2347
3645
  ...executedChecks.map((command) => `Review the recorded output for \`${command}\` before promotion.`),
2348
3646
  ...focusAreas.map((focusArea) => `Confirm whether ${focusArea} needs additional deterministic QA evidence.`),
2349
3647
  ...normalizedGithubActions.workflowNames.map((workflowName) => `Review the exported GitHub Actions evidence for workflow \`${workflowName}\` before promotion.`),
3648
+ ...normalizedCiWorkflowNames.map((workflowName) => `Review the imported CI evidence for pipeline \`${workflowName}\` before promotion.`),
2350
3649
  ...(normalizedConstraints.length > 0 ? [`Keep QA follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
2351
3650
  ];
2352
3651
  const summary = `QA report prepared for ${qaRequest.targetRef}.`;
@@ -2356,7 +3655,7 @@ const qaAnalystAgent = {
2356
3655
  ? "candidate release still requires explicit QA review before promotion."
2357
3656
  : "no release context was supplied; QA output remains advisory.";
2358
3657
  const qaReport = qaArtifactSchema.parse({
2359
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaGithubRefs),
3658
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaScmRefs, qaGithubRefs),
2360
3659
  artifactKind: "qa-report",
2361
3660
  lifecycleDomain: "test",
2362
3661
  workflow: {
@@ -2367,13 +3666,21 @@ const qaAnalystAgent = {
2367
3666
  targetRef: qaRequest.targetRef,
2368
3667
  evidenceSources,
2369
3668
  executedChecks,
3669
+ ciEvidenceSummary: normalizedCiEvidenceSummary,
2370
3670
  findings,
2371
3671
  coverageGaps,
2372
3672
  recommendedNextChecks: recommendedNextChecks.length > 0
2373
3673
  ? recommendedNextChecks
2374
3674
  : ["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(", ")}.`
3675
+ releaseImpact: normalizedGithubActions.failingChecks.length > 0 || normalizedCiFailingChecks.length > 0
3676
+ ? `${releaseImpactBase} ${[
3677
+ normalizedGithubActions.failingChecks.length > 0
3678
+ ? `GitHub Actions evidence still shows failing checks: ${normalizedGithubActions.failingChecks.join(", ")}.`
3679
+ : undefined,
3680
+ normalizedCiFailingChecks.length > 0
3681
+ ? `Imported CI evidence still shows failing checks: ${normalizedCiFailingChecks.join(", ")}.`
3682
+ : undefined
3683
+ ].filter((value) => Boolean(value)).join(" ")}`
2377
3684
  : releaseImpactBase
2378
3685
  }
2379
3686
  });
@@ -2393,6 +3700,7 @@ const qaAnalystAgent = {
2393
3700
  executedChecks,
2394
3701
  focusAreas,
2395
3702
  constraints: normalizedConstraints,
3703
+ ciEvidence: normalizedCiEvidence,
2396
3704
  githubActions: normalizedGithubActions
2397
3705
  },
2398
3706
  synthesizedAssessment: {
@@ -2602,7 +3910,7 @@ const implementationPlannerAgent = {
2602
3910
  ];
2603
3911
  const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
2604
3912
  const implementationProposal = implementationArtifactSchema.parse({
2605
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.githubRefs),
3913
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.scmRefs, designRecord.source.githubRefs),
2606
3914
  artifactKind: "implementation-proposal",
2607
3915
  lifecycleDomain: "build",
2608
3916
  workflow: {
@@ -2715,7 +4023,7 @@ const designAnalystAgent = {
2715
4023
  ];
2716
4024
  const summary = `Design record prepared for ${designRequest.decisionTarget}.`;
2717
4025
  const designRecord = designArtifactSchema.parse({
2718
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.githubRefs),
4026
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.scmRefs, planningBrief.source.githubRefs),
2719
4027
  artifactKind: "design-record",
2720
4028
  lifecycleDomain: "design",
2721
4029
  workflow: {
@@ -3013,6 +4321,12 @@ export function createBuiltinAgentRegistry() {
3013
4321
  ["release-intake", releaseIntakeAgent],
3014
4322
  ["release-evidence-normalizer", releaseEvidenceNormalizationAgent],
3015
4323
  ["release-analyst", releaseAnalystAgent],
4324
+ ["pipeline-intake", pipelineIntakeAgent],
4325
+ ["pipeline-evidence-normalizer", pipelineEvidenceNormalizationAgent],
4326
+ ["pipeline-analyst", pipelineAnalystAgent],
4327
+ ["deployment-gate-intake", deploymentGateIntakeAgent],
4328
+ ["deployment-gate-evidence-normalizer", deploymentGateEvidenceNormalizationAgent],
4329
+ ["deployment-gate-analyst", deploymentGateAnalystAgent],
3016
4330
  ["security-evidence-normalizer", securityEvidenceNormalizationAgent],
3017
4331
  ["security-analyst", securityAnalystAgent],
3018
4332
  ["qa-evidence-normalizer", qaEvidenceNormalizationAgent],