/** Canonical lower-case OCI sha256 digest. */ export type TSha256Digest = `sha256:${string}`; export type TImageDeploymentPolicy = 'legacy-tag' | 'immutable-digest'; export type TOciManifestMediaType = | 'application/vnd.oci.image.index.v1+json' | 'application/vnd.oci.image.manifest.v1+json' | 'application/vnd.docker.distribution.manifest.list.v2+json' | 'application/vnd.docker.distribution.manifest.v2+json'; export type TImmutableImageRolloutMode = 'promotion' | 'automatic' | 'rollback' | 'adoption'; export type TImmutableImageMismatchReason = | 'immutable-plan-missing' | 'requested-digest-missing' | 'requested-digest-malformed' | 'registry-digest-inconsistent' | 'runtime-capability-missing' | 'pull-failed' | 'pulled-digest-missing' | 'pulled-digest-mismatch' | 'service-image-not-digest-pinned' | 'service-image-digest-mismatch' | 'task-image-not-digest-pinned' | 'task-image-digest-mismatch' | 'container-inspection-unavailable' | 'container-image-id-missing' | 'container-repodigest-missing' | 'container-repodigest-mismatch' | 'runtime-attestation-missing' | 'required-replica-missing' /** * A replica exists and has not failed, but its health is not yet determined: * the in-process health prober starts every task at 'unknown' and escalates * to 'unhealthy' only after threshold failures against an established * /healthz contract. Undetermined is NOT unhealthy — this reason marks a * rollout that is still coming up inside its startup grace, so it maps to a * live status rather than a failure. Once the grace is exhausted the * shortfall is reported as 'replica-unhealthy'. */ | 'replica-starting' | 'replica-unhealthy' | 'replica-failed' | 'stale-rollout-report' | 'rollback-target-not-accepted'; export type TRuntimeImageVerificationStatus = | 'not-required' | 'pending' | 'verified' | 'missing' | 'mismatched'; export type TImmutableImageRolloutStatus = | 'pending' | 'deploying' | 'verifying' | 'succeeded' | 'failed' | 'mismatched' | 'rolling-back' | 'rolled-back'; export interface ICoreflowRuntimeCapabilities { immutableImageDeploymentVersion: 1; /** Coreflow can answer the versioned, node-scoped Corestore inventory probe. */ corestoreInventoryVersion?: 1; /** Coreflow can consume exact, cluster-scoped resolved secret manifests. */ secretManifestVersion: 2; sealedSecretMaterialVersion: 1; secretRecipientEnrollmentVersion: 1; workloadInitEnvironmentVersion: 1; /** Coreflow can submit replay-safe schema-v1 secret deployment reports. */ secretDeploymentReportVersion?: 1; } export type TImmutableContainerPlatform = 'linux/amd64' | 'linux/arm64'; export interface IImmutableContainerInvocationPlatformEvidenceV1 { platform: TImmutableContainerPlatform; platformManifestDigest: TSha256Digest; imageConfigDigest: TSha256Digest; /** Effective non-empty OCI argv after applying the deployment containerArgs. */ effectiveArgv: string[]; } export interface IImmutableContainerInvocationV1 { schemaVersion: 1; /** Sorted, unique, non-empty and limited to the authoritative platform union. */ targetPlatforms: TImmutableContainerPlatform[]; /** The one effective argv shared by every target platform. */ argv: string[]; /** Sorted exact one-to-one platform evidence. */ evidence: IImmutableContainerInvocationPlatformEvidenceV1[]; digest: TSha256Digest; } /** * A registry manifest observed by Cloudly. Observation alone does not make the * release deployable: acceptedAt is set only by an explicit promotion or a * configured automatic promotion. */ export interface IImageRelease { id: string; data: { serviceId: string; imageId: string; registryHost: string; repository: string; /** Digest of the registry root descriptor, preserving an index/list when submitted. */ registryRootDigest: TSha256Digest; digestPinnedImageReference: string; manifestMediaType: TOciManifestMediaType; observedTags: string[]; firstObservedAt: number; lastObservedAt: number; observationCount: number; /** Cloudly-created evidence from an authenticated exact-tag registry PUT. */ trustedEvidence: IImageReleaseTrustedEvidence[]; acceptedAt?: number; lastPromotedAt?: number; promotionCount?: number; }; } export interface IImageReleaseTrustedEvidence { source: 'cloudly-registry'; /** Deterministic ID for an authenticated registry acceptance event/retry. */ evidenceId: string; operationId: string; actorUserId: string; registryHost: string; repository: string; tag: string; registryRootDigest: TSha256Digest; manifestMediaType: TOciManifestMediaType; recordedAt: number; } export const selectTrustedImageReleaseEvidence = (argsArg: { release: IImageRelease; operationId: string; actorUserId: string; serviceId: string; imageId: string; registryHost: string; repository: string; tag: string; digest: TSha256Digest; }): { evidence?: IImageReleaseTrustedEvidence; errors: string[] } => { const evidenceFingerprint = (evidenceArg: IImageReleaseTrustedEvidence) => [ evidenceArg.source, evidenceArg.evidenceId, evidenceArg.operationId, evidenceArg.actorUserId, evidenceArg.registryHost, evidenceArg.repository, evidenceArg.tag, evidenceArg.registryRootDigest, evidenceArg.manifestMediaType, evidenceArg.recordedAt, ].join('\u0000'); const uniqueEvidence = new Map(); const errors: string[] = []; for (const evidence of argsArg.release.data.trustedEvidence || []) { const existing = uniqueEvidence.get(evidence.evidenceId); if (existing && evidenceFingerprint(existing) !== evidenceFingerprint(evidence)) { errors.push('trusted release evidence identity is contradictory'); } else { uniqueEvidence.set(evidence.evidenceId, evidence); } } const evidenceMatches = [...uniqueEvidence.values()].filter((evidenceArg) => ( evidenceArg.source === 'cloudly-registry' && evidenceArg.operationId === argsArg.operationId && evidenceArg.actorUserId === argsArg.actorUserId && evidenceArg.registryHost === argsArg.registryHost && evidenceArg.repository === argsArg.repository && evidenceArg.tag === argsArg.tag && evidenceArg.registryRootDigest === argsArg.digest )); if (argsArg.release.data.serviceId !== argsArg.serviceId || argsArg.release.data.imageId !== argsArg.imageId || argsArg.release.data.registryHost !== argsArg.registryHost || argsArg.release.data.repository !== argsArg.repository || argsArg.release.data.registryRootDigest !== argsArg.digest) { errors.push('release target or root digest does not match the deployment fence'); } if (!argsArg.release.data.observedTags.includes(argsArg.tag)) { errors.push('release has not observed the exact deployment tag'); } if (evidenceMatches.length === 0) { errors.push('trusted release evidence is missing'); } else if (evidenceMatches.length > 1) { errors.push('trusted release evidence is ambiguous'); } const evidence = evidenceMatches.length === 1 ? evidenceMatches[0] : undefined; if (evidence && evidence.manifestMediaType !== argsArg.release.data.manifestMediaType) { errors.push('trusted release and release root media types disagree'); } if (![ 'application/vnd.oci.image.index.v1+json', 'application/vnd.docker.distribution.manifest.list.v2+json', ].includes(argsArg.release.data.manifestMediaType) || (evidence && ![ 'application/vnd.oci.image.index.v1+json', 'application/vnd.docker.distribution.manifest.list.v2+json', ].includes(evidence.manifestMediaType))) { errors.push('trusted release evidence must identify an OCI image index'); } return { evidence, errors }; }; const immutableReleaseTagRegex = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/; const immutableIdentifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/; const isImmutableIdentifier = (valueArg: unknown): valueArg is string => typeof valueArg === 'string' && immutableIdentifierRegex.test(valueArg); export const normalizeImmutableReleaseTag = (tagArg: unknown): string | undefined => { if (typeof tagArg !== 'string' || tagArg.trim() !== tagArg || !immutableReleaseTagRegex.test(tagArg) || tagArg.toLowerCase() === 'latest') { return undefined; } return tagArg; }; export interface IImmutableImageTargetScope { targetClusterIds: string[]; targetNodeNames: string[]; replicasPerNode: number; requiredReplicaCount: number; } /** Immutable desired state issued by Cloudly and consumed by Coreflow. */ export interface IImmutableImageDeploymentPlan { policy: 'immutable-digest'; rolloutId: string; rolloutGeneration: number; releaseId: string; operationId: string; releaseTag: string; registryHost: string; repository: string; requestedDigest: TSha256Digest; digestPinnedImageReference: string; manifestMediaType: TOciManifestMediaType; /** Mandatory immutable command/config evidence for every target platform. */ containerInvocation: IImmutableContainerInvocationV1; createdAt: number; mode: TImmutableImageRolloutMode; /** Full pre-adoption service configuration fence, required only for adoption. */ adoptionConfigurationDigest?: TSha256Digest; rollbackOfRolloutId?: string; targetScope: IImmutableImageTargetScope; } /** Runtime evidence collected from Docker service, task, container and image state. */ interface IRuntimeImageEvidenceBase { rolloutId?: string; rolloutGeneration?: number; expectedDigest?: TSha256Digest; serviceImageReference?: string; taskImageReference?: string; taskReportedDigest?: TSha256Digest; containerImageId?: string; containerConfigImageReference?: string; /** Digest observed from local Docker image state without implying rollout verification. */ observedDigest?: TSha256Digest; resolvedDigest?: TSha256Digest; platformManifestDigest?: TSha256Digest; repoDigests?: string[]; evidenceSource?: 'task-spec' | 'local-container-inspect'; observedAt: number; } export interface IVerifiedRuntimeImageEvidence extends IRuntimeImageEvidenceBase { verificationStatus: 'verified'; rolloutId: string; rolloutGeneration: number; expectedDigest: TSha256Digest; serviceImageReference: string; taskImageReference: string; taskReportedDigest: TSha256Digest; containerImageId: string; containerConfigImageReference: string; resolvedDigest: TSha256Digest; repoDigests: string[]; evidenceSource: 'local-container-inspect'; mismatchReason?: never; } export interface IUnverifiedRuntimeImageEvidence extends IRuntimeImageEvidenceBase { verificationStatus: 'missing' | 'mismatched'; mismatchReason: TImmutableImageMismatchReason; } export interface IPendingRuntimeImageEvidence extends IRuntimeImageEvidenceBase { verificationStatus: 'pending' | 'not-required'; mismatchReason?: never; } export type IRuntimeImageEvidence = | IVerifiedRuntimeImageEvidence | IUnverifiedRuntimeImageEvidence | IPendingRuntimeImageEvidence; /** Mandatory security envelope when reporting an immutable rollout. */ export interface IImmutableRolloutReportEnvelope { rolloutId: string; rolloutGeneration: number; reporterSessionId: string; reportSequence: number; reporterCapabilities: ICoreflowRuntimeCapabilities; reporterNodeIds: string[]; reporterNodeNames: string[]; } export interface IImageRolloutReporterStatus { reporterId: string; reporterSessionId: string; clusterId: string; reporterNodeNames: string[]; rolloutId: string; rolloutGeneration: number; reportSequence: number; reportedAt: number; requiredReplicaCount: number; observedReplicaCount: number; healthyReplicaCount: number; verifiedReplicaCount: number; failedReplicaCount: number; mismatchReason?: TImmutableImageMismatchReason; } export interface IImageRolloutStatus { rolloutId: string; rolloutGeneration: number; expectedDigest: TSha256Digest; status: TImmutableImageRolloutStatus; requiredReplicaCount: number; observedReplicaCount: number; healthyReplicaCount: number; verifiedReplicaCount: number; failedReplicaCount: number; mismatchReason?: TImmutableImageMismatchReason; updatedAt: number; reporters: IImageRolloutReporterStatus[]; } const sha256DigestRegex = /^sha256:[a-f0-9]{64}$/; const immutableContainerPlatforms = new Set([ 'linux/amd64', 'linux/arm64', ]); const validateImmutableArgv = (argvArg: unknown): argvArg is string[] => ( Array.isArray(argvArg) && argvArg.length > 0 && argvArg.length <= 128 && argvArg.every((argumentArg) => typeof argumentArg === 'string' && argumentArg.length > 0 && !/[\u0000-\u001f\u007f]/.test(argumentArg) && new TextEncoder().encode(argumentArg).byteLength <= 4096) ); export const createImmutableContainerInvocationDigestInput = ( invocationArg: Omit | IImmutableContainerInvocationV1, ): string => JSON.stringify({ schemaVersion: invocationArg.schemaVersion, targetPlatforms: invocationArg.targetPlatforms, argv: invocationArg.argv, evidence: invocationArg.evidence.map((entryArg) => ({ platform: entryArg.platform, platformManifestDigest: entryArg.platformManifestDigest, imageConfigDigest: entryArg.imageConfigDigest, effectiveArgv: entryArg.effectiveArgv, })), }); export const computeImmutableContainerInvocationDigest = async ( invocationArg: Omit | IImmutableContainerInvocationV1, ): Promise => { const input = new TextEncoder().encode(createImmutableContainerInvocationDigestInput(invocationArg)); const digest = new Uint8Array(await globalThis.crypto.subtle.digest( 'SHA-256', input.buffer as ArrayBuffer, )); return `sha256:${[...digest] .map((byteArg) => byteArg.toString(16).padStart(2, '0')) .join('')}` as TSha256Digest; }; export const verifyImmutableContainerInvocationDigest = async ( invocationArg: IImmutableContainerInvocationV1, ): Promise => invocationArg.digest === await computeImmutableContainerInvocationDigest(invocationArg); export const validateImmutableContainerInvocation = ( invocationArg: unknown, ): string[] => { if (!invocationArg || typeof invocationArg !== 'object' || Array.isArray(invocationArg)) { return ['containerInvocation must be an object']; } const invocation = invocationArg as Record; const errors: string[] = []; if (JSON.stringify(Object.keys(invocation).sort()) !== JSON.stringify([ 'argv', 'digest', 'evidence', 'schemaVersion', 'targetPlatforms', ])) { errors.push('containerInvocation must use its exact schema'); } if (invocation.schemaVersion !== 1) errors.push('containerInvocation schemaVersion must be 1'); const platforms = Array.isArray(invocation.targetPlatforms) ? invocation.targetPlatforms : []; if (platforms.length === 0 || platforms.some((platformArg) => typeof platformArg !== 'string' || !immutableContainerPlatforms.has(platformArg as TImmutableContainerPlatform)) || new Set(platforms).size !== platforms.length || JSON.stringify(platforms) !== JSON.stringify([...platforms].sort())) { errors.push('containerInvocation targetPlatforms must be sorted unique supported platforms'); } if (!validateImmutableArgv(invocation.argv)) { errors.push('containerInvocation argv must be a non-empty canonical argument vector'); } const evidence = Array.isArray(invocation.evidence) ? invocation.evidence : []; const evidencePlatforms: unknown[] = []; for (const [index, entryArg] of evidence.entries()) { if (!entryArg || typeof entryArg !== 'object' || Array.isArray(entryArg)) { errors.push(`containerInvocation evidence[${index}] must be an object`); continue; } const entry = entryArg as Record; if (JSON.stringify(Object.keys(entry).sort()) !== JSON.stringify([ 'effectiveArgv', 'imageConfigDigest', 'platform', 'platformManifestDigest', ])) { errors.push(`containerInvocation evidence[${index}] must use its exact schema`); } evidencePlatforms.push(entry.platform); if (!immutableContainerPlatforms.has(entry.platform as TImmutableContainerPlatform)) { errors.push(`containerInvocation evidence[${index}].platform is unsupported`); } if (!isSha256Digest(entry.platformManifestDigest as string) || !isSha256Digest(entry.imageConfigDigest as string)) { errors.push(`containerInvocation evidence[${index}] digests must be canonical`); } if (!validateImmutableArgv(entry.effectiveArgv) || JSON.stringify(entry.effectiveArgv) !== JSON.stringify(invocation.argv)) { errors.push(`containerInvocation evidence[${index}] effective argv must match`); } } if (JSON.stringify(evidencePlatforms) !== JSON.stringify(platforms)) { errors.push('containerInvocation evidence must exactly cover targetPlatforms in order'); } if (typeof invocation.digest !== 'string' || !isSha256Digest(invocation.digest)) { errors.push('containerInvocation digest must be canonical'); } return errors; }; export const normalizeSha256Digest = (digest: unknown): TSha256Digest | undefined => { if (typeof digest !== 'string') return undefined; const normalizedDigest = digest.trim().toLowerCase(); return sha256DigestRegex.test(normalizedDigest) ? (normalizedDigest as TSha256Digest) : undefined; }; export const isSha256Digest = (digest: string): digest is TSha256Digest => sha256DigestRegex.test(digest); const registryHostRegex = /^(?:localhost|[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?$/; const repositoryRegex = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/; export const buildDigestPinnedImageReference = ( registryHost: string, repository: string, digest: TSha256Digest ): string => { if (!registryHostRegex.test(registryHost) || !repositoryRegex.test(repository)) { throw new Error('registry host and repository must be canonical OCI names'); } if (!isSha256Digest(digest)) { throw new Error('digest must be a canonical sha256 digest'); } return `${registryHost}/${repository}@${digest}`; }; export const getDigestFromPinnedImageReference = ( imageReference: string ): TSha256Digest | undefined => { const separatorIndex = imageReference.lastIndexOf('@'); if (separatorIndex < 0) { return undefined; } return normalizeSha256Digest(imageReference.slice(separatorIndex + 1)); }; export const validateImmutableImageDeploymentPlan = ( planArg: IImmutableImageDeploymentPlan ): string[] => { const errors: string[] = []; if (planArg.policy !== 'immutable-digest') { errors.push('policy must require immutable-digest'); } if (!isImmutableIdentifier(planArg.rolloutId)) { errors.push('rolloutId must be a bounded canonical identifier'); } if (!isImmutableIdentifier(planArg.releaseId)) { errors.push('releaseId must be a bounded canonical identifier'); } if (!Number.isSafeInteger(planArg.rolloutGeneration) || planArg.rolloutGeneration < 1) { errors.push('rolloutGeneration must be a positive safe integer'); } if (!Number.isSafeInteger(planArg.createdAt) || planArg.createdAt < 1) { errors.push('createdAt must be a positive safe integer'); } if (!isImmutableIdentifier(planArg.operationId)) { errors.push('operationId must be a bounded canonical identifier'); } if (!normalizeImmutableReleaseTag(planArg.releaseTag)) { errors.push('releaseTag must be a non-latest canonical OCI tag'); } if (!['promotion', 'automatic', 'rollback', 'adoption'].includes(planArg.mode)) { errors.push('mode must be a supported immutable rollout mode'); } if (![ 'application/vnd.oci.image.index.v1+json', 'application/vnd.docker.distribution.manifest.list.v2+json', ].includes(planArg.manifestMediaType)) { errors.push('immutable rollout root must be an OCI image index'); } errors.push(...validateImmutableContainerInvocation(planArg.containerInvocation)); let expectedReference: string | undefined; try { expectedReference = buildDigestPinnedImageReference( planArg.registryHost, planArg.repository, planArg.requestedDigest ); } catch (error) { errors.push((error as Error).message); } if (expectedReference && planArg.digestPinnedImageReference !== expectedReference) { errors.push('digestPinnedImageReference does not match registry, repository and digest'); } const scope = planArg.targetScope; if (!Number.isSafeInteger(scope.replicasPerNode) || scope.replicasPerNode < 1) { errors.push('replicasPerNode must be a positive safe integer'); } if (scope.targetClusterIds.length < 1 || scope.targetNodeNames.length < 1) { errors.push('immutable rollout target scope must contain clusters and nodes'); } if (new Set(scope.targetClusterIds).size !== scope.targetClusterIds.length || new Set(scope.targetNodeNames).size !== scope.targetNodeNames.length) { errors.push('immutable rollout target scope must not contain duplicates'); } if (scope.requiredReplicaCount !== scope.targetNodeNames.length * scope.replicasPerNode) { errors.push('requiredReplicaCount does not match target nodes and replicasPerNode'); } if (planArg.mode === 'rollback' && !planArg.rollbackOfRolloutId) { errors.push('rollback rollout must identify the rollout it replaces'); } if (planArg.mode !== 'rollback' && planArg.rollbackOfRolloutId) { errors.push('only rollback rollouts may set rollbackOfRolloutId'); } if (planArg.mode === 'adoption') { if (planArg.rolloutGeneration !== 1) { errors.push('adoption must create rollout generation one'); } if (!planArg.adoptionConfigurationDigest || !isSha256Digest(planArg.adoptionConfigurationDigest)) { errors.push('adoption must bind a canonical service configuration digest'); } } else if (planArg.adoptionConfigurationDigest !== undefined) { errors.push('only adoption may bind a service configuration digest'); } return errors; }; /** Coreflow uses this async verifier before resolving or opening secret material. */ export const verifyImmutableImageDeploymentPlan = async ( planArg: IImmutableImageDeploymentPlan, ): Promise => validateImmutableImageDeploymentPlan(planArg).length === 0 && await verifyImmutableContainerInvocationDigest(planArg.containerInvocation); export const validateImageRolloutStatus = (statusArg: IImageRolloutStatus): string[] => { const errors: string[] = []; if (!isImmutableIdentifier(statusArg.rolloutId)) { errors.push('rolloutId must be a bounded canonical identifier'); } if (!isSha256Digest(statusArg.expectedDigest)) { errors.push('expectedDigest must be a canonical sha256 digest'); } if (![ 'pending', 'deploying', 'verifying', 'succeeded', 'failed', 'mismatched', 'rolling-back', 'rolled-back', ].includes(statusArg.status)) { errors.push('status must be a supported immutable rollout status'); } if (!Number.isSafeInteger(statusArg.updatedAt) || statusArg.updatedAt < 1) { errors.push('updatedAt must be a positive safe integer'); } const counts = [ statusArg.requiredReplicaCount, statusArg.observedReplicaCount, statusArg.healthyReplicaCount, statusArg.verifiedReplicaCount, statusArg.failedReplicaCount, ]; if (counts.some((countArg) => !Number.isSafeInteger(countArg) || countArg < 0)) { errors.push('rollout replica counts must be non-negative safe integers'); } const reporterIds = statusArg.reporters.map((reporterArg) => reporterArg.reporterId); const reporterSessionIds = statusArg.reporters.map((reporterArg) => reporterArg.reporterSessionId); const reporterNodeNames = statusArg.reporters.flatMap( (reporterArg) => reporterArg.reporterNodeNames ); if (new Set(reporterIds).size !== reporterIds.length || new Set(reporterSessionIds).size !== reporterSessionIds.length) { errors.push('rollout status must contain one latest report per reporter and session'); } for (const reporter of statusArg.reporters) { const reporterCounts = [ reporter.requiredReplicaCount, reporter.observedReplicaCount, reporter.healthyReplicaCount, reporter.verifiedReplicaCount, reporter.failedReplicaCount, ]; if (reporterCounts.some((countArg) => !Number.isSafeInteger(countArg) || countArg < 0)) { errors.push('reporter replica counts must be non-negative safe integers'); } if (reporter.rolloutId !== statusArg.rolloutId || reporter.rolloutGeneration !== statusArg.rolloutGeneration) { errors.push('reporter rollout identity does not match aggregate rollout identity'); } if ( reporter.observedReplicaCount > reporter.requiredReplicaCount || reporter.healthyReplicaCount > reporter.observedReplicaCount || reporter.verifiedReplicaCount > reporter.observedReplicaCount ) { errors.push('reporter replica counts are contradictory'); } } if (statusArg.status === 'succeeded' || statusArg.status === 'rolled-back') { if ( statusArg.requiredReplicaCount < 1 || statusArg.observedReplicaCount !== statusArg.requiredReplicaCount || statusArg.healthyReplicaCount !== statusArg.requiredReplicaCount || statusArg.verifiedReplicaCount !== statusArg.requiredReplicaCount || statusArg.failedReplicaCount !== 0 ) { errors.push('successful rollout status requires every replica to be observed, healthy and verified'); } if (statusArg.mismatchReason || statusArg.reporters.length < 1) { errors.push('successful rollout status requires reporters without mismatch reasons'); } if (new Set(reporterNodeNames).size !== reporterNodeNames.length) { errors.push('successful rollout status must not count a node in multiple reporters'); } if (statusArg.reporters.some((reporterArg) => ( reporterArg.requiredReplicaCount < 1 || reporterArg.observedReplicaCount !== reporterArg.requiredReplicaCount || reporterArg.healthyReplicaCount !== reporterArg.requiredReplicaCount || reporterArg.verifiedReplicaCount !== reporterArg.requiredReplicaCount || reporterArg.failedReplicaCount !== 0 || Boolean(reporterArg.mismatchReason) ))) { errors.push('successful rollout requires every reporter replica to be healthy and verified'); } const reporterTotals = statusArg.reporters.reduce((totalsArg, reporterArg) => ({ required: totalsArg.required + reporterArg.requiredReplicaCount, observed: totalsArg.observed + reporterArg.observedReplicaCount, healthy: totalsArg.healthy + reporterArg.healthyReplicaCount, verified: totalsArg.verified + reporterArg.verifiedReplicaCount, failed: totalsArg.failed + reporterArg.failedReplicaCount, }), { required: 0, observed: 0, healthy: 0, verified: 0, failed: 0 }); if ( reporterTotals.required !== statusArg.requiredReplicaCount || reporterTotals.observed !== statusArg.observedReplicaCount || reporterTotals.healthy !== statusArg.healthyReplicaCount || reporterTotals.verified !== statusArg.verifiedReplicaCount || reporterTotals.failed !== statusArg.failedReplicaCount ) { errors.push('successful rollout aggregate must exactly match successful reporter evidence'); } } return errors; };