@growth-labs/conformance
v0.7.1
Published
Reusable fleet publication conformance harness for Growth Labs sites: headers, cache, metadata, structured data, images, publication surfaces, and evidence bundles.
Readme
@growth-labs/conformance
Reusable publication conformance harness for Growth Labs sites. It validates per-site fixtures and emits machine-parseable evidence bundles for render, cache, security headers, metadata, structured data, images, publication surfaces, Discover eligibility, and build evidence.
This package is consumed by sites and CI jobs. It is not a central fleet runtime and it does not fetch, mutate, deploy, or store fleet state.
Cache Safety
import {
applyPrivateNoStore,
evaluateCacheSafety,
scanRequestStateLeaks,
} from '@growth-labs/conformance/cache-safety'The cache-safety contract keeps the default gateway uncached, requires an empty
public HTML allowlist, verifies a non-public and side-effect-free inner renderer
with an explicit forwarded-header allowlist, and proves HIT still executes the
outer gateway side effects. applyPrivateNoStore() writes the complete
Cache-Control/CDN-Cache-Control/Cloudflare-CDN-Cache-Control triplet and removes
Cookie from Vary.
Omitted cache evidence fails with cache.evidence.missing. A public response
with Set-Cookie fails unless it carries the complete triplet. Vary: Cookie
always fails—even alongside the private triplet—because Cookie variance is not
part of the supported contract; remove it after marking the response private.
The body scanner accepts controlled request-state sentinels and detects their
literal, URI, JSON, and base64 forms. Its receipt exposes only state categories
and encodings—never raw sentinel values. cacheSafetyLiveResultSchema validates
the bounded result.
Install
pnpm add @growth-labs/conformanceBasic Usage
import { runFleetConformance, runSiteConformance } from '@growth-labs/conformance'
const fixture = {
id: 'fronts:article:public',
site: 'fronts',
environment: 'production',
url: 'https://fronts.co/example',
renderMode: 'ssr',
audience: 'public',
response: {
status: 200,
headers: {
'content-security-policy': "default-src 'self'; script-src 'self' 'nonce-runtime'",
'strict-transport-security': 'max-age=31536000',
'cross-origin-opener-policy': 'same-origin',
'cross-origin-resource-policy': 'same-origin',
'x-frame-options': 'DENY',
'x-content-type-options': 'nosniff',
'referrer-policy': 'strict-origin-when-cross-origin',
'permissions-policy': 'camera=(), microphone=(), geolocation=()',
'cache-control': 'public, max-age=0, s-maxage=300',
},
html: '<!doctype html>...',
},
expected: {
canonicalUrl: 'https://fronts.co/example',
schemaTypes: ['NewsArticle'],
discoverEligible: true,
framePolicy: {
ancestors: ["'none'"],
legacyHeader: 'DENY',
},
},
publicationSurfaces: [
{ name: 'sitemap', status: 'present', httpStatus: 200, bodyBytes: 1024 },
{ name: 'robots', status: 'present', httpStatus: 200, bodyBytes: 256 },
{ name: 'feed', status: 'na', capabilityBoundary: 'No editorial feed', evidence: 'site type' },
],
cacheVariants: [
{ audience: 'public', cacheKey: 'fronts:public:/example' },
{ audience: 'crawler', cacheKey: 'fronts:crawler:/example' },
],
cacheSafety: {
defaultGatewayCacheEnabled: false,
publicHtmlAllowlist: [],
inner: {
publiclyRoutable: false,
forwardedHeaders: ['accept-language'],
allowedForwardedHeaders: ['accept-language'],
sideEffects: false,
},
gateway: { hitRunsSideEffects: true },
response: {
personalized: false,
headers: { 'cache-control': 'public, s-maxage=300' },
body: '<!doctype html>...',
sentinels: [],
},
},
securityReceipts: {
functionalProbes: [
{ name: 'oauth', status: 'pass', evidence: 'sanitized consumer probe receipt' },
{ name: 'media', status: 'pass', evidence: 'sanitized consumer probe receipt' },
{ name: 'images', status: 'pass', evidence: 'sanitized consumer probe receipt' },
{ name: 'analytics', status: 'pass', evidence: 'sanitized consumer probe receipt' },
{ name: 'embeds', status: 'pass', evidence: 'sanitized consumer probe receipt' },
{ name: 'error-pages', status: 'pass', evidence: 'sanitized consumer probe receipt' },
],
cspReports: [
{
name: 'csp-report-canary',
status: 'pass',
endpoint: 'consumer-owned-report-endpoint',
evidence: 'sanitized CSP report or blocked-canary receipt',
},
],
inlineStyleAttributes: {
commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
occurrenceCount: 4,
evidence: {
tool: 'platform-foundations:inline-style-inventory@1',
outputHash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
},
},
},
buildEvidence: {
commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
packageVersions: { '@growth-labs/conformance': '0.6.0', '@growth-labs/seo': '0.9.0' },
staticHeaderPolicy: true,
ssrHeaderPolicy: true,
},
}
const siteBundle = runSiteConformance(fixture)
const fleetBundle = runFleetConformance([fixture])Deployment Endpoint Adapter
Consumers own the route, the conformance artifact, and the Cloudflare
[version_metadata] binding. The CI/build probe step runs
runSiteConformance(), serializes the result to a JSON artifact file, and
bundles that file with the Worker. The adapter reads the pre-built artifact at
module load (not per request) and serves it:
import artifact from './conformance-artifact.json' with { type: 'json' }
import { initConformanceEndpoint } from '@growth-labs/conformance/endpoint'
// Artifact produced by the CI probe step before deployment.
// Static import: a missing conformance-artifact.json is a build-time error —
// the Worker never starts if the artifact is absent.
// Artifact is parsed, schema-stripped, bounded, and serialized ONCE here.
const handler = initConformanceEndpoint(artifact, 'fronts')
// src/pages/.well-known/growth-labs-conformance.json.ts
export function GET({ locals, request }) {
return handler.handle({
method: request.method,
versionMetadata: locals.runtime.env.CF_VERSION_METADATA,
})
}
export const HEAD = GETinitConformanceEndpoint parses the artifact, validates it against the v2
schema, checks environment: 'production' and the expected site, requires
buildEvidence.commitSha (lowercase 40-character hex) and
buildEvidence.generatedAt (timezone-qualified ISO-8601 datetime), and
serializes the schema-stripped body bytes once. Per-request handling only
validates the method and compares versionMetadata.tag against the baked commit
SHA — no I/O, no probe execution, no re-serialization. Missing/malformed
artifacts and wrong site/environment produce a handler that always returns 503.
The adapter never reads or serializes environment bindings, request headers,
Cloudflare version IDs, Cloudflare version timestamps, internal exceptions, or
fields outside the parsed v2 schema. It does not perform heuristic secret
detection: documented free-form strings such as finding messages, finding
evidence, remediation text, and receipt evidence remain unchanged and are the
consumer's sanitization responsibility.
Shared Header Policy
Use buildSharedPublicationHeaders() when a site wants the package-owned
baseline for SSR and static response headers. Astro 7.1+ sites should let Astro
own the per-route hash CSP and retain the remaining shared headers. The helper's
peer contract is intentionally pinned to the first Astro release that supports
attribute-scoped style resources:
import {
buildAstroCspConfig,
buildSharedPublicationHeaders,
} from '@growth-labs/conformance/policy'
const csp = buildAstroCspConfig({
scriptSources: ['https://challenges.cloudflare.com'],
styleSources: ['https://fonts.googleapis.com'],
inlineStyleAttributes: {
commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
occurrenceCount: 4,
evidence: {
tool: 'platform-foundations:inline-style-inventory@1',
outputHash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
},
},
})
const headers = buildSharedPublicationHeaders({
renderMode: 'ssr',
audience: 'public',
contentSecurityPolicy: false,
markdownTwinUrl: 'https://example.com/article.md',
})The fixture default is the non-embeddable policy above. For a deliberately
embeddable route, declare its exact response class and omit a conflicting
X-Frame-Options header:
expected: {
framePolicy: {
ancestors: ['https://trusted.example'],
legacyHeader: 'omit',
},
}Accepted ancestors are 'none', 'self', https:, and exact HTTPS origins;
wildcards, paths, credentials, query strings, and fragments are rejected.
DENY is valid only with 'none', and SAMEORIGIN only with 'self'; broader
embeddable policies must use legacyHeader: 'omit' to avoid contradictory
browser behavior.
Astro 7.1+ consumers that prove they still emit HTML style attributes may set
inlineStyleAttributes to the validated inventory receipt. There is no boolean
escape hatch. The helper then scopes 'unsafe-inline' to style-src-attr
while preserving hash protection for scripts and <style> elements. The
receipt requires the exact lowercase 40-character build commit SHA, a positive
occurrence count, the versioned scanner identity, and the lowercase SHA-256
content hash of its machine output. The same receipt must appear in the
conformance fixture and its commit must match buildEvidence.commitSha. Do not
enable this option as a generic compatibility fix.
The policy emits CSP, HSTS, frame protection, MIME sniffing protection,
COOP, CORP, referrer policy, permissions policy, cache semantics, and optional
markdown twin Link metadata. Private audiences use private, no-store;
public SSR uses short shared-cache semantics; public static surfaces get longer
shared-cache semantics.
HSTS defaults to max-age=31536000 only. Add includeSubDomains and preload
through strictTransportSecurity: { includeSubDomains: true, preload: true }
only after the consumer has separately verified the full host inventory can
enforce HTTPS. COOP defaults to same-origin and CORP defaults to
same-origin; consumers may override them only when verified OAuth popup,
media, or embed constraints require a looser policy and the receipt proves the
result under enforced CSP.
Fixture Commands
The root repo exposes:
pnpm run conformance:test-fixtures
pnpm run conformance:verify-schemaThe fixture command proves the required negative fixtures fail loudly with stable finding codes:
csp-unsafe-inlinemissing-coopmissing-corphsts-below-one-yearcsp-wildcard-default-srccsp-unsafe-evalcsp-inline-executable-without-nonce-or-hashconflicting-duplicate-security-headermissing-functional-probe-receiptmissing-csp-report-receiptfailed-csp-report-receiptheaders-static-only-while-ssr-missingundeclared-nastringified-is-accessible-for-freevideo-object-date-only-upload-datemissing-image-dimensionshero-under-1200pxmissing-max-image-preview-largecrawler-public-cache-key-collisionruntime-canonical-override-not-appliedllms-runtime-static-shadowdefault-entrypoint-cache-enabledpublic-html-allowlist-nonemptyraw-credential-forwarded-to-innercached-body-request-state-leakgateway-hit-side-effects-bypassedpersonalized-public-responsepublic-set-cookie-responsevary-cookie-response
Evidence Shape
runSiteConformance() returns a SiteEvidenceBundle; runFleetConformance()
returns a FleetEvidenceBundle. Both are validated by exported Zod schemas
and carry schemaVersion: "growth-labs.conformance.v2" so downstream jobs can
parse them without relying on console text.
Schema v2 adds securityReceipts to fixtures and site evidence bundles. Existing
v1 fixtures must add explicit functional-probe receipts for oauth, media,
images, analytics, embeds, and error-pages, plus at least one passing CSP
reporting/canary receipt. The package does not perform those live probes itself;
consumer CI supplies sanitized pass/fail/NA receipts.
