refactor: validate generated evidence artifacts
This commit is contained in:
@@ -1 +1,425 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export * from "../../src/contracts/release-artifacts.ts";
|
||||
|
||||
const nonEmptyString = z.string().min(1);
|
||||
const timestamp = z.iso.datetime();
|
||||
const sha256 = z.string().regex(/^[a-f0-9]{64}$/u);
|
||||
const jsonObject = z.record(z.string(), z.json());
|
||||
|
||||
export const moduleInventoryArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
chunks: z.array(
|
||||
z
|
||||
.object({
|
||||
fileName: nonEmptyString,
|
||||
modules: z.array(nonEmptyString),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const jsonSchemaDocumentArtifactSchema = z
|
||||
.object({
|
||||
$schema: z.literal("https://json-schema.org/draft/2020-12/schema"),
|
||||
})
|
||||
.catchall(z.json());
|
||||
|
||||
const dependencyInventoryRowSchema = z
|
||||
.object({
|
||||
name: nonEmptyString,
|
||||
version: nonEmptyString,
|
||||
direct: z.boolean(),
|
||||
scope: z.enum(["production", "development"]),
|
||||
optional: z.boolean(),
|
||||
license: nonEmptyString,
|
||||
integrity: z.string().regex(/^sha512-/u),
|
||||
dependencies: z.array(nonEmptyString),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const dependencyInventoryArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(2),
|
||||
packageManager: nonEmptyString,
|
||||
lockfileSha256: sha256,
|
||||
dependencyCount: z.int().nonnegative(),
|
||||
directDependencyCount: z.int().nonnegative(),
|
||||
dependencies: z.array(dependencyInventoryRowSchema),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((inventory, context) => {
|
||||
if (inventory.dependencyCount !== inventory.dependencies.length) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["dependencyCount"],
|
||||
message: "must equal dependencies.length",
|
||||
});
|
||||
}
|
||||
const actualDirect = inventory.dependencies.filter(
|
||||
(dependency) => dependency.direct,
|
||||
).length;
|
||||
if (inventory.directDependencyCount !== actualDirect) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["directDependencyCount"],
|
||||
message: "must equal the number of direct dependencies",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const dependencyUpgradeSchema = z
|
||||
.object({
|
||||
name: nonEmptyString,
|
||||
from: nonEmptyString,
|
||||
to: nonEmptyString,
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const dependencyDiffSchema = z
|
||||
.object({
|
||||
added: z.array(nonEmptyString),
|
||||
removed: z.array(nonEmptyString),
|
||||
changed: z.array(nonEmptyString),
|
||||
upgrades: z.array(dependencyUpgradeSchema),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const supplyChainVerificationArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
localStatus: z.enum(["PASS", "FAIL"]),
|
||||
promotionStatus: z.enum(["PASS", "FAIL_UNVERIFIED"]),
|
||||
lockfileSha256: sha256,
|
||||
sourceSetSha256: sha256,
|
||||
distSha256: sha256,
|
||||
sbomSha256: sha256,
|
||||
dependencyDiff: dependencyDiffSchema,
|
||||
highRiskReview: z.array(nonEmptyString),
|
||||
vulnerabilityStatus: z.enum(["PASS", "FAIL", "FAIL_UNVERIFIED"]),
|
||||
provenanceAttestationStatus: z.enum(["PASS", "FAIL_UNVERIFIED"]),
|
||||
failures: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const registryChangeSchema = z
|
||||
.object({
|
||||
changeId: nonEmptyString,
|
||||
registryId: nonEmptyString,
|
||||
rowName: nonEmptyString,
|
||||
field: nonEmptyString,
|
||||
kind: nonEmptyString,
|
||||
impact: z.enum(["none", "additive", "behavior-change", "breaking"]),
|
||||
before: z.json().optional(),
|
||||
after: z.json().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const registryArtifactRowSchema = z
|
||||
.object({
|
||||
registryId: nonEmptyString,
|
||||
owner: nonEmptyString,
|
||||
source: nonEmptyString,
|
||||
rowCount: z.int().nonnegative(),
|
||||
contract: jsonObject,
|
||||
rows: jsonObject,
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const registrySnapshotArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(2),
|
||||
generatedAt: timestamp,
|
||||
baselineDigest: sha256.nullable(),
|
||||
currentDigest: sha256,
|
||||
compatibility: z
|
||||
.object({
|
||||
impact: z.enum([
|
||||
"not-evaluated",
|
||||
"none",
|
||||
"additive",
|
||||
"behavior-change",
|
||||
"breaking",
|
||||
]),
|
||||
changes: z.array(registryChangeSchema),
|
||||
})
|
||||
.strict(),
|
||||
failures: z.array(z.string()),
|
||||
registries: z.array(registryArtifactRowSchema),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const outputDigestSchema = z
|
||||
.object({
|
||||
path: nonEmptyString,
|
||||
bytes: z.int().nonnegative(),
|
||||
gzipBytes: z.int().nonnegative(),
|
||||
sha256,
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const bundlePerformanceArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
generatedAt: timestamp,
|
||||
context: z
|
||||
.object({
|
||||
nodeVersion: nonEmptyString,
|
||||
packageManager: nonEmptyString,
|
||||
runnerImage: nonEmptyString,
|
||||
})
|
||||
.strict(),
|
||||
outputs: z.array(outputDigestSchema).min(1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const cyclonedxComponentSchema = z
|
||||
.object({
|
||||
type: z.literal("library"),
|
||||
"bom-ref": nonEmptyString,
|
||||
name: nonEmptyString,
|
||||
version: nonEmptyString,
|
||||
scope: z.enum(["optional", "required"]),
|
||||
hashes: z.array(
|
||||
z.object({ alg: z.literal("SHA-512"), content: nonEmptyString }).strict(),
|
||||
),
|
||||
licenses: z.array(
|
||||
z.object({ expression: nonEmptyString }).strict(),
|
||||
),
|
||||
properties: z.array(
|
||||
z.object({ name: nonEmptyString, value: nonEmptyString }).strict(),
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const sbomArtifactSchema = z
|
||||
.object({
|
||||
bomFormat: z.literal("CycloneDX"),
|
||||
specVersion: z.literal("1.6"),
|
||||
serialNumber: nonEmptyString,
|
||||
version: z.literal(1),
|
||||
metadata: z
|
||||
.object({
|
||||
component: z
|
||||
.object({
|
||||
type: z.literal("application"),
|
||||
name: nonEmptyString,
|
||||
version: nonEmptyString,
|
||||
})
|
||||
.strict(),
|
||||
properties: z.array(
|
||||
z.object({ name: nonEmptyString, value: nonEmptyString }).strict(),
|
||||
),
|
||||
})
|
||||
.strict(),
|
||||
components: z.array(cyclonedxComponentSchema),
|
||||
dependencies: z.array(
|
||||
z
|
||||
.object({ ref: nonEmptyString, dependsOn: z.array(nonEmptyString) })
|
||||
.strict(),
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const provenanceArtifactSchema = z
|
||||
.object({
|
||||
_type: z.literal("https://in-toto.io/Statement/v1"),
|
||||
subject: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
name: z.literal("dist"),
|
||||
digest: z.object({ sha256 }).strict(),
|
||||
})
|
||||
.strict(),
|
||||
)
|
||||
.length(1),
|
||||
predicateType: z.literal("https://slsa.dev/provenance/v1"),
|
||||
predicate: z
|
||||
.object({
|
||||
buildDefinition: z
|
||||
.object({
|
||||
buildType: nonEmptyString,
|
||||
externalParameters: jsonObject,
|
||||
internalParameters: jsonObject,
|
||||
resolvedDependencies: z.array(
|
||||
z
|
||||
.object({ uri: nonEmptyString, digest: z.object({ sha256 }).strict() })
|
||||
.strict(),
|
||||
),
|
||||
})
|
||||
.strict(),
|
||||
runDetails: z
|
||||
.object({
|
||||
builder: z.object({ id: nonEmptyString }).strict(),
|
||||
metadata: z.object({ invocationId: nonEmptyString }).strict(),
|
||||
})
|
||||
.strict(),
|
||||
materials: z
|
||||
.object({ lockfileSha256: sha256, sourceSetSha256: sha256, sbomSha256: sha256 })
|
||||
.strict(),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const dependencyDiffArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(2),
|
||||
baselineDigest: sha256.nullable(),
|
||||
currentDigest: sha256,
|
||||
...dependencyDiffSchema.shape,
|
||||
highRisk: z.array(nonEmptyString),
|
||||
reviewFailures: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const licenseReportArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
status: z.enum(["PASS", "FAIL"]),
|
||||
dependencyCount: z.int().nonnegative(),
|
||||
results: z.array(
|
||||
z
|
||||
.object({
|
||||
package: nonEmptyString,
|
||||
license: nonEmptyString,
|
||||
passed: z.boolean(),
|
||||
reason: z.string().nullable(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
failures: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const vulnerabilityReportArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
provider: nonEmptyString,
|
||||
scannedLockfileSha256: sha256,
|
||||
status: z.enum(["PASS", "FAIL", "FAIL_UNVERIFIED"]),
|
||||
findings: z.array(jsonObject),
|
||||
exceptionsApplied: z.array(jsonObject),
|
||||
failures: z.array(z.string()),
|
||||
blocking: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const fieldWebVitalsArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
generatedAt: timestamp,
|
||||
window: z
|
||||
.object({ days: z.literal(28), start: timestamp, end: timestamp })
|
||||
.strict(),
|
||||
context: z
|
||||
.object({
|
||||
source: nonEmptyString,
|
||||
sourceSystem: z.string().nullable(),
|
||||
exportId: z.string().nullable(),
|
||||
network: z.literal("production-real-user"),
|
||||
routeAggregation: z.literal("route-id-only"),
|
||||
releaseId: z.string().nullable(),
|
||||
privacyApprovalRef: z.string().nullable(),
|
||||
thresholdDecisionRef: z.string().nullable(),
|
||||
validationFailures: z.array(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
metrics: z
|
||||
.object({
|
||||
p75LcpMs: z.number().finite().nonnegative().nullable(),
|
||||
p75Cls: z.number().finite().nonnegative().nullable(),
|
||||
p75InpMs: z.number().finite().nonnegative().nullable(),
|
||||
})
|
||||
.strict(),
|
||||
thresholds: z
|
||||
.object({
|
||||
p75LcpMs: z.number().finite().nonnegative(),
|
||||
p75Cls: z.number().finite().nonnegative(),
|
||||
p75InpMs: z.number().finite().nonnegative(),
|
||||
minimumEligibleSamples: z.int().positive().nullable(),
|
||||
})
|
||||
.strict(),
|
||||
eligibility: z
|
||||
.object({
|
||||
consentRequired: z.literal(true),
|
||||
totalSamples: z.int().nonnegative(),
|
||||
eligibleSamples: z.int().nonnegative(),
|
||||
minimumEligibleSamples: z.int().positive().nullable(),
|
||||
routeSamples: z.record(z.string(), z.int().nonnegative()),
|
||||
})
|
||||
.strict(),
|
||||
status: z.enum(["PASS", "FAIL_THRESHOLD", "FAIL_UNVERIFIED"]),
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const labPerformanceArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
generatedAt: timestamp,
|
||||
context: jsonObject,
|
||||
metrics: jsonObject,
|
||||
thresholds: jsonObject,
|
||||
fixtures: z.array(
|
||||
z.object({ name: nonEmptyString, passed: z.boolean() }).strict(),
|
||||
),
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const releaseVerificationArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
generatedAt: timestamp,
|
||||
artifact: z
|
||||
.object({
|
||||
checked: z.boolean(),
|
||||
compatible: z.boolean(),
|
||||
mismatches: z.array(z.string()),
|
||||
releaseId: nonEmptyString,
|
||||
})
|
||||
.strict(),
|
||||
fixtures: z.array(
|
||||
z
|
||||
.object({
|
||||
name: nonEmptyString,
|
||||
expectedCompatible: z.boolean(),
|
||||
actualCompatible: z.boolean(),
|
||||
mismatches: z.array(z.string()),
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const runbookRecordArtifactSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(1),
|
||||
runbookId: z.string().regex(/^FE-RB-00[1-5]$/u),
|
||||
releaseId: nonEmptyString,
|
||||
drillTimestamp: timestamp,
|
||||
triggerInjected: nonEmptyString,
|
||||
triggerAsserted: z.boolean(),
|
||||
containmentAsserted: z.boolean(),
|
||||
escalationPathAsserted: z.boolean(),
|
||||
recoveryAssertions: z.array(
|
||||
z
|
||||
.object({
|
||||
assertion: nonEmptyString,
|
||||
evidence: nonEmptyString,
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
negativeFixtureFailedAsExpected: z.boolean(),
|
||||
windowObservedBucket: nonEmptyString,
|
||||
providerVerificationRequired: z.boolean(),
|
||||
passed: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
Reference in New Issue
Block a user