check-manifests.sh: the source-manifest schema gate
atlas-data/ingest/src/sources/check-manifests.sh is the CI gate that fails a PR if any per-source manifest.yml is shape-invalid, or if a manifest references a tags.topic or publisher that isn't declared in the companion YAML files.
It is the sibling of check-osmosis.sh (which gates dbt schema.yml column documentation). Non-overlapping scopes; both gates must be green to merge a PR touching source manifests.
This page covers what the gate enforces, when it runs, what failure looks like, and how to fix it.
What the gate enforces
Two layers, both run by default:
1. Schema validation
Each manifest.yml is validated against manifest.schema.json — a JSON Schema 2020-12 document covering required fields, types, enums, regex patterns, and URI format.
Catches:
- Missing required fields (
lifecycle,time_coverage,tags.topic,dimensions, …) - Wrong types (
upstream_id: 07459silently parses as integer 7459 — schema requires string; quoting forces preservation) - Invalid enum values (
lifecycle: maintained— not in[stable, beta, deprecated, broken]) - Malformed URLs in
upstream_url/upstream_landing_page/feedback_url - Malformed
periodicity(must match ISO 8601 duration or literalirregular)
2. Cross-file resolution
Two further checks, separate from the per-manifest schema:
- Every manifest's
tags.topicmust resolve to a topicidintopics.yaml. - Every manifest's
publisherfield must match adisplay_nameinpublishers.yaml.
These keep manifests consistent with the curated editorial files the catalog renders from.
When the gate runs
-
Locally — run it before any commit touching
atlas-data/ingest/src/sources/:cd atlas-data/ingest/src/sources./check-manifests.shOr via the npm script wrapper:
cd atlas-data/ingestnpm run sources:check-manifests -
In CI — wired up as a required check on PRs and on push to
mainin.github/workflows/check-manifests.yml. Triggers only on changes to the manifests themselves, the schema, the validator, the companion files, or the workflow.
What failure looks like
Schema failure
→ schema check: 41 manifests under src/sources/
✗ ssb-07459/manifest.yml
/upstream_id: must be string
/: must have required property 'lifecycle'
...
✗ 1 schema failure(s), 0 cross-file failure(s)
exit=1
The error lines name the file and the JSONPath of the failing property. Most failures fall into:
- Missing required field — open the manifest, add it. See adding-a-source.md § Step 4b for the v2 field reference.
- Wrong type — usually
upstream_idparsed as integer. Wrap the value in double quotes:upstream_id: "07459". - Bad enum — typo. Allowed values for
lifecycle:stable | beta | deprecated | broken. Allowed values foreu_theme: the 13 DCAT-AP codes.
Cross-file failure
→ cross-file checks
✗ 1 manifest(s) have tags.topic not in topics.yaml:
ssb-new-source/manifest.yml: topic 'demographics-and-housing'
The fix is one of:
- The topic was a typo — correct it in the manifest.
- The category genuinely doesn't exist yet — add an entry to
topics.yamlwithid,name,description,emoji,order. New categories are editorial choices — discuss in the PR.
Same pattern for publisher failures — either fix the typo in the manifest, or add an entry to publishers.yaml.
How to fix a failure
Case A — you added a new source
- After
npm run sources:bootstrap-manifest -- <id>, fill in the v2 fields the bootstrap leaves empty (lifecycle,time_coverage,keywords). See adding-a-source.md § Step 4b. - If the source is from a new publisher, add an entry to
publishers.yaml+ a placeholder logo SVG. - If the source is in a new editorial category, add an entry to
topics.yaml. - Re-run
./check-manifests.sh.
Case B — you renamed a field or changed a value
If the schema rejects a value you believe is correct, the schema is the contract — either fix the value or, if the schema is genuinely too strict for a legitimate case, propose a schema change in the same PR. Schema changes need maintainer review; per-source workarounds do not.
Case C — VS Code complains before you run the gate
Add this to your workspace .vscode/settings.json to get hover docs + autocomplete on manifest fields:
{
"yaml.schemas": {
"atlas-data/ingest/src/sources/manifest.schema.json": "atlas-data/ingest/src/sources/*/manifest.yml"
}
}
Prerequisites
- Node ≥ 20.
- Dependencies installed under
atlas-data/ingest/(npm ci). - The script uses
ajv(JSON Schema validator),ajv-formats(URI format), andjs-yaml(YAML parser), all declared inatlas-data/ingest/package.json.
Why a manifest gate matters
The per-source manifest.yml is the editorial contract between Atlas's ingest team and every downstream consumer: mart_meta_sources, the dbt docs at /lineage/, the public datasets catalogue under /datasets/, and any future DCAT-AP-NO emitter for harvesting by data.norge.no.
Until this gate landed, manifest shape was conventional but unenforced — silent corruption was possible. The first run of this validator exposed 20 manifests whose upstream_id had silently lost a leading zero (07459 → integer 7459) and 6 manifests with empty / TODO upstream_landing_page values. Both bugs had been in production for months without anyone noticing.
At 200 sources, this kind of silent drift is not optional to catch.
See INVESTIGATE-sources-catalog-at-scale.md for the design rationale.
Cross-references
- adding-a-source.md — full contributor workflow; manifest fields documented in Step 4b
- check-osmosis.md — sibling gate (
check-osmosis.sh) covering dbt schema.yml hygiene; non-overlapping scope atlas-data/ingest/src/sources/check-manifests.sh— the script itselfatlas-data/ingest/src/sources/validate-manifests.ts— the validator implementationatlas-data/ingest/src/sources/manifest.schema.json— the schema being validated against- PLAN-001-sources-manifest-schema-v2.md — the plan that shipped this gate