@h9-foundry/agentforge-cli 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +409 -1
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentManifestSchema, agentOutputSchema, 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";
|
|
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,
|
|
@@ -421,6 +421,45 @@ function evaluatePipelineReportReadiness(bundlePath) {
|
|
|
421
421
|
detail: `Using ${bundlePath} as a ready pipeline-report reference.`
|
|
422
422
|
};
|
|
423
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
|
+
}
|
|
424
463
|
function loadBundleArtifactPayloadPaths(bundlePath) {
|
|
425
464
|
if (!existsSync(bundlePath)) {
|
|
426
465
|
return [];
|
|
@@ -3184,6 +3223,372 @@ const deploymentGateAnalystAgent = {
|
|
|
3184
3223
|
});
|
|
3185
3224
|
}
|
|
3186
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
|
+
};
|
|
3187
3592
|
const securityEvidenceNormalizationAgent = {
|
|
3188
3593
|
manifest: agentManifestSchema.parse({
|
|
3189
3594
|
version: 1,
|
|
@@ -4327,6 +4732,9 @@ export function createBuiltinAgentRegistry() {
|
|
|
4327
4732
|
["deployment-gate-intake", deploymentGateIntakeAgent],
|
|
4328
4733
|
["deployment-gate-evidence-normalizer", deploymentGateEvidenceNormalizationAgent],
|
|
4329
4734
|
["deployment-gate-analyst", deploymentGateAnalystAgent],
|
|
4735
|
+
["promotion-approval-intake", promotionApprovalIntakeAgent],
|
|
4736
|
+
["promotion-approval-evidence-normalizer", promotionApprovalEvidenceNormalizationAgent],
|
|
4737
|
+
["promotion-approval-analyst", promotionApprovalAnalystAgent],
|
|
4330
4738
|
["security-evidence-normalizer", securityEvidenceNormalizationAgent],
|
|
4331
4739
|
["security-analyst", securityAnalystAgent],
|
|
4332
4740
|
["qa-evidence-normalizer", qaEvidenceNormalizationAgent],
|