@growth-labs/conformance 0.1.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/src/schema.ts ADDED
@@ -0,0 +1,165 @@
1
+ import { z } from 'zod'
2
+
3
+ export const EVIDENCE_SCHEMA_VERSION = 'growth-labs.conformance.v1'
4
+
5
+ export const REQUIRED_NEGATIVE_FIXTURE_CODES = {
6
+ 'csp-unsafe-inline': 'security.csp.unsafe_inline',
7
+ 'headers-static-only-while-ssr-missing': 'security.headers.static_only_for_ssr',
8
+ 'undeclared-na': 'publication.na.undeclared',
9
+ 'stringified-is-accessible-for-free': 'structured_data.is_accessible_for_free.stringified',
10
+ 'video-object-date-only-upload-date': 'structured_data.video_object.upload_date.date_only',
11
+ 'missing-image-dimensions': 'images.dimensions.missing',
12
+ 'hero-under-1200px': 'images.hero.under_1200',
13
+ 'missing-max-image-preview-large': 'metadata.robots.missing_max_image_preview_large',
14
+ 'crawler-public-cache-key-collision': 'cache.crawler_public_key_collision',
15
+ 'runtime-canonical-override-not-applied': 'metadata.canonical.runtime_override_not_applied',
16
+ } as const
17
+
18
+ export type RequiredNegativeFixtureId = keyof typeof REQUIRED_NEGATIVE_FIXTURE_CODES
19
+ export type RequiredNegativeFindingCode =
20
+ (typeof REQUIRED_NEGATIVE_FIXTURE_CODES)[RequiredNegativeFixtureId]
21
+
22
+ export const audienceSchema = z.enum([
23
+ 'public',
24
+ 'crawler',
25
+ 'subscriber',
26
+ 'member',
27
+ 'admin',
28
+ 'private',
29
+ ])
30
+ export const renderModeSchema = z.enum(['ssr', 'static'])
31
+ export const checkStatusSchema = z.enum(['pass', 'fail', 'na'])
32
+ export const findingSeveritySchema = z.enum(['error', 'warning'])
33
+
34
+ export const headerValueSchema = z.union([z.string(), z.array(z.string())])
35
+ export const headerMapSchema = z.record(headerValueSchema)
36
+
37
+ export const surfaceResponseSchema = z.object({
38
+ status: z.number().int().min(100).max(599).default(200),
39
+ url: z.string().url().optional(),
40
+ headers: headerMapSchema.default({}),
41
+ html: z.string().optional(),
42
+ body: z.string().optional(),
43
+ })
44
+
45
+ export const publicationSurfaceSchema = z.object({
46
+ name: z.string().min(1),
47
+ status: z.enum(['present', 'missing', 'na']),
48
+ url: z.string().url().optional(),
49
+ httpStatus: z.number().int().min(100).max(599).optional(),
50
+ contentType: z.string().min(1).optional(),
51
+ bodyBytes: z.number().int().nonnegative().optional(),
52
+ capabilityBoundary: z.string().min(1).optional(),
53
+ evidence: z.string().min(1).optional(),
54
+ })
55
+
56
+ export const cacheVariantSchema = z.object({
57
+ audience: audienceSchema,
58
+ cacheKey: z.string().min(1),
59
+ url: z.string().url().optional(),
60
+ contentClass: z.enum(['html', 'markdown', 'feed', 'asset']).default('html'),
61
+ vary: z.array(z.string().min(1)).default([]),
62
+ headers: headerMapSchema.default({}),
63
+ })
64
+
65
+ export const buildEvidenceSchema = z.object({
66
+ commitSha: z.string().min(7).optional(),
67
+ artifactId: z.string().min(1).optional(),
68
+ generatedAt: z.string().min(1).optional(),
69
+ packageVersions: z.record(z.string().min(1)).default({}),
70
+ staticHeaderPolicy: z.boolean().default(false),
71
+ ssrHeaderPolicy: z.boolean().default(false),
72
+ })
73
+
74
+ export const conformanceExpectedSchema = z.object({
75
+ canonicalUrl: z.string().url().optional(),
76
+ schemaTypes: z.array(z.string().min(1)).default([]),
77
+ discoverEligible: z.boolean().default(true),
78
+ })
79
+
80
+ export const conformanceFixtureSchema = z.object({
81
+ id: z.string().min(1),
82
+ site: z.string().min(1),
83
+ environment: z.string().min(1).default('production'),
84
+ url: z.string().url(),
85
+ renderMode: renderModeSchema,
86
+ audience: audienceSchema,
87
+ response: surfaceResponseSchema,
88
+ expected: conformanceExpectedSchema.default({}),
89
+ publicationSurfaces: z.array(publicationSurfaceSchema).default([]),
90
+ cacheVariants: z.array(cacheVariantSchema).default([]),
91
+ buildEvidence: buildEvidenceSchema.optional(),
92
+ expectedFailingCode: z.string().min(1).optional(),
93
+ })
94
+
95
+ export const conformanceFindingSchema = z.object({
96
+ code: z.string().min(1),
97
+ severity: findingSeveritySchema,
98
+ message: z.string().min(1),
99
+ path: z.string().min(1).optional(),
100
+ evidence: z.string().min(1).optional(),
101
+ remediation: z.string().min(1).optional(),
102
+ })
103
+
104
+ export const conformanceCheckReceiptSchema = z.object({
105
+ name: z.string().min(1),
106
+ status: checkStatusSchema,
107
+ findings: z.array(conformanceFindingSchema),
108
+ })
109
+
110
+ export const evidenceSummarySchema = z.object({
111
+ pass: z.number().int().nonnegative(),
112
+ fail: z.number().int().nonnegative(),
113
+ na: z.number().int().nonnegative(),
114
+ findings: z.number().int().nonnegative(),
115
+ })
116
+
117
+ export const siteEvidenceBundleSchema = z.object({
118
+ schemaVersion: z.literal(EVIDENCE_SCHEMA_VERSION),
119
+ generatedAt: z.string().min(1),
120
+ fixtureId: z.string().min(1),
121
+ site: z.string().min(1),
122
+ environment: z.string().min(1),
123
+ url: z.string().url(),
124
+ status: z.enum(['pass', 'fail']),
125
+ summary: evidenceSummarySchema,
126
+ checks: z.array(conformanceCheckReceiptSchema),
127
+ findings: z.array(conformanceFindingSchema),
128
+ })
129
+
130
+ export const fleetEvidenceBundleSchema = z.object({
131
+ schemaVersion: z.literal(EVIDENCE_SCHEMA_VERSION),
132
+ generatedAt: z.string().min(1),
133
+ status: z.enum(['pass', 'fail']),
134
+ summary: evidenceSummarySchema.extend({
135
+ sites: z.number().int().nonnegative(),
136
+ }),
137
+ sites: z.array(siteEvidenceBundleSchema),
138
+ })
139
+
140
+ export type Audience = z.infer<typeof audienceSchema>
141
+ export type RenderMode = z.infer<typeof renderModeSchema>
142
+ export type HeaderMap = z.infer<typeof headerMapSchema>
143
+ export type SurfaceResponse = z.infer<typeof surfaceResponseSchema>
144
+ export type PublicationSurfaceEvidence = z.infer<typeof publicationSurfaceSchema>
145
+ export type CacheVariant = z.infer<typeof cacheVariantSchema>
146
+ export type BuildEvidence = z.infer<typeof buildEvidenceSchema>
147
+ export type ConformanceFixtureInput = z.input<typeof conformanceFixtureSchema>
148
+ export type ConformanceFixture = z.infer<typeof conformanceFixtureSchema>
149
+ export type ConformanceFinding = z.infer<typeof conformanceFindingSchema>
150
+ export type ConformanceCheckReceipt = z.infer<typeof conformanceCheckReceiptSchema>
151
+ export type EvidenceSummary = z.infer<typeof evidenceSummarySchema>
152
+ export type SiteEvidenceBundle = z.infer<typeof siteEvidenceBundleSchema>
153
+ export type FleetEvidenceBundle = z.infer<typeof fleetEvidenceBundleSchema>
154
+
155
+ export function parseConformanceFixture(input: unknown): ConformanceFixture {
156
+ return conformanceFixtureSchema.parse(input)
157
+ }
158
+
159
+ export function validateSiteEvidenceBundle(input: unknown): SiteEvidenceBundle {
160
+ return siteEvidenceBundleSchema.parse(input)
161
+ }
162
+
163
+ export function validateFleetEvidenceBundle(input: unknown): FleetEvidenceBundle {
164
+ return fleetEvidenceBundleSchema.parse(input)
165
+ }