Data Contracts for Spatial Products

A data contract for a spatial product is the machine-checkable agreement that pins down five things at once — schema, coordinate reference system, geometry precision, service-level guarantees, and semantics — and enforces them at the exact boundary where a producing domain hands data to a consuming one. This reference sits inside Geospatial Data Mesh Fundamentals and treats the contract as executable infrastructure, not a wiki page: if a producer changes its CRS from EPSG:4326 to EPSG:3857, drops a required attribute, or coarsens geometry precision below the promised tolerance, the boundary rejects the payload before any consumer resolves it. Platform engineers and GIS data stewards should read this alongside Schema Contracts for Vector Tile Data, which applies the same discipline to the tile-serving layer, and Product Thinking for GIS Datasets, which supplies the ownership and SLA model that a contract encodes. Where the two diverge, the version stamp v1.2.0-crs:EPSG:4326-res:10m is the arbiter — it is the immutable identity a consumer pins to.

Figure — A spatial data contract mediates the producer/consumer boundary, checking schema, CRS, geometry precision, SLA, and semantics before stamping an immutable version on the payload.

Producer to contract gate to consumer flow with contract fields and version stamp A producing domain team sends a spatial product left to right into a central data-contract gate positioned at the producer/consumer boundary. The gate checks five fields listed inside it: schema, CRS plus axis order, geometry precision, SLA, and semantics. A payload that passes flows on to the consumer, which machine-checks the contract on receipt; a payload that violates any field is blocked. The gate emits an immutable version stamp reading v1.2.0-crs:EPSG:4326-res:10m that the consumer pins to. pass Producer domain team Data contract producer/consumer boundary schema CRS + axis order geometry precision SLA semantics Consumer machine-checked v1.2.0-crs:EPSG:4326-res:10m immutable version stamp

Architectural Boundaries & Design Rationale

The contract exists because a federated mesh removes the single geodatabase that once forced every producer and consumer to share one implicit schema. Once each domain owns and publishes independently, “the data changed and nothing told me” becomes the dominant failure mode. A data contract restores the missing signal by making the boundary itself refuse any product that no longer matches what consumers agreed to depend on. It is enforced at ingress — the same zero-trust posture the catalog applies in Metadata Cataloging for Raster/Vector — so a non-conforming payload never reaches storage, discovery, or a downstream join.

Five surfaces make up the contract, and each maps to a distinct class of silent breakage:

  • Schema — attribute names, types, nullability, and cardinality. Removing a required field or narrowing a type breaks every consumer that projected it.
  • CRS — the declared crs_epsg and axis order. A producer that flips from EPSG:4326 to EPSG:32633, or serves lat/lon where consumers expect lon/lat, corrupts every spatial predicate downstream. The governance model for this surface lives in CRS Governance and Reprojection Standards.
  • Geometry precision — coordinate resolution and the topology rule set. A product promising sub-metre planar topology that ships decimated geometry fails validation rather than quietly degrading a routing graph.
  • SLA — freshness, availability, and latency targets. These are the guarantees a consumer plans capacity against, drawn from the same model as Product Thinking for GIS Datasets.
  • Semantics — units, enumerations, null conventions, and the meaning of each field. A population column that switches from count to density is schema-valid and semantically catastrophic.

The rationale for enforcing all five at one gate rather than scattering checks across pipelines is idempotency and single-point accountability: a payload validated once against a pinned contract version behaves identically on every retry, and a breach has exactly one place to page. Contract evolution is governed by Versioning Spatial Data Contracts with SemVer, which decides whether a change is a compatible MINOR bump or a breaking MAJOR that forces a parallel version.

Specification & Contract Reference

A spatial data contract is a versioned document, stored beside the product and referenced by its stamp. The mandatory fields below are the minimal surface every producer must publish and every consumer may rely on.

Field Type Required Constraint / Convention
contract_version string yes v1.2.0-crs:EPSG:4326-res:10m site convention; immutable once published
product_id UUID yes Stable identity across contract versions
crs_epsg integer yes EPSG code; change is always a MAJOR bump
axis_order enum yes lon_lat | lat_lon; explicit to avoid silent inversion
geometry_type enum yes point | linestring | polygon | multipolygon | raster
coordinate_precision integer yes Decimal places retained; drives precision validation
topology_rule_set enum vector only none | planar | network | 3d_solid
schema_fields array yes Ordered {name, type, nullable, unit} records
freshness_sla_minutes integer yes 0 means real-time; drives drift alerts
availability_target number yes Fractional, e.g. 0.999; consumer capacity input
semantics object yes Per-field units, enums, and null meaning
product_owner_id email yes Accountable steward for breaches and incidents

The enforcement surface — where the contract is evaluated — is carried in transport and policy inputs rather than the payload body, so the same contract can gate a REST ingress, a stream consumer, or a CI check.

Surface Key Purpose
HTTP header X-Contract-Version Pins the exact contract the payload claims to satisfy
Policy input input.contract.crs_epsg Validated against the CRS allowlist and the pinned version
Policy input input.payload.schema_hash Detects drift between declared and actual schema
Idempotency key SHA-256(product_id ‖ contract_version ‖ payload_hash) Makes re-validation of the same payload side-effect free
Registry entry contract://<product_id>/<contract_version> Canonical, immutable storage path for the contract document

Consumers subscribe to a specific contract_version and are notified only when a compatible MINOR/PATCH supersedes it; a MAJOR requires an explicit, tested migration rather than an automatic upgrade.

Production Implementation

The contract is authored as a machine-readable document and enforced by a policy engine at the boundary. Below is a data-contract schema in YAML (the human-authored source) and its JSON Schema projection used for structural validation, followed by an opa eval gate that checks the cross-cutting rules JSON Schema cannot express — CRS allowlisting, version pinning, and ownership. The gate is idempotent and zero-trust by default: identity is verified upstream, default allow = false denies anything not explicitly permitted, and re-evaluating the same payload against the same contract version always yields the same decision.

yaml
# contract://parcels_cadastre/v1.2.0  — authored source of truth
contract_version: "v1.2.0-crs:EPSG:4326-res:10m"
product_id: "7c9e6a1b-2f34-4d8a-9b11-0a5c2e7f4d21"
crs_epsg: 4326
axis_order: lon_lat
geometry_type: multipolygon
coordinate_precision: 6          # ~0.11 m at the equator
topology_rule_set: planar
schema_fields:
  - { name: parcel_id,   type: string,  nullable: false, unit: none }
  - { name: area_m2,     type: number,  nullable: false, unit: square_metre }
  - { name: land_use,    type: string,  nullable: false, unit: enum }
freshness_sla_minutes: 1440
availability_target: 0.999
semantics:
  land_use: { enum: [residential, commercial, agricultural, industrial] }
  area_m2:  { null_means: not_applicable }
product_owner_id: cadastre-steward@example.org
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "SpatialDataContract",
  "type": "object",
  "required": ["contract_version", "product_id", "crs_epsg", "axis_order",
               "geometry_type", "coordinate_precision", "schema_fields",
               "freshness_sla_minutes", "availability_target", "product_owner_id"],
  "properties": {
    "contract_version": { "type": "string", "pattern": "^v\\d+\\.\\d+\\.\\d+-crs:EPSG:\\d{4,5}-res:.+$" },
    "product_id": { "type": "string", "format": "uuid" },
    "crs_epsg": { "type": "integer", "minimum": 1000, "maximum": 99999 },
    "axis_order": { "type": "string", "enum": ["lon_lat", "lat_lon"] },
    "geometry_type": { "type": "string",
      "enum": ["point", "linestring", "polygon", "multipolygon", "raster"] },
    "coordinate_precision": { "type": "integer", "minimum": 0, "maximum": 15 },
    "topology_rule_set": { "type": "string", "enum": ["none", "planar", "network", "3d_solid"] },
    "schema_fields": { "type": "array", "minItems": 1 },
    "freshness_sla_minutes": { "type": "integer", "minimum": 0 },
    "availability_target": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
    "product_owner_id": { "type": "string", "format": "email" }
  },
  "additionalProperties": false
}
rego
package contract.boundary

import rego.v1

default allow = false

# Zero-trust: the payload is admitted only if it pins the published contract
# version, declares an allowlisted CRS, and its schema hash matches the contract.
allow if {
    input.header["x-contract-version"] == input.contract.contract_version
    input.contract.crs_epsg in [4326, 3857, 32610, 32611, 32612, 32633]
    input.payload.schema_hash == input.contract.schema_hash
    input.payload.coordinate_precision >= input.contract.coordinate_precision
}

Run the JSON Schema check and the policy together at ingress. In CI, opa eval -i request.json -d boundary.rego "data.contract.boundary.allow" fails the producer’s merge before a breaking payload is ever published; at runtime the same bundle runs as an admission sidecar so the boundary, the registry, and the consumer all enforce the identical contract independently. A false decision returns 409 Conflict with the offending field, and the producer must either fix the payload or, if the change is intentional, cut a new contract version.

Diagnostic Runbook

When a consumer reports broken or empty results downstream, isolate the failing contract surface in order from transport to semantics.

  1. Version pin mismatch (HTTP 409). Compare the X-Contract-Version header against the registry entry the consumer subscribed to. A mismatch means a producer published a new version without a compatible migration. Remediation: re-tag the payload with the pinned version, or coordinate a MAJOR migration per Versioning Spatial Data Contracts with SemVer.
  2. Schema-hash drift. If input.payload.schema_hash differs from input.contract.schema_hash, the producer changed columns without bumping the contract. Remediation: diff the field list, classify the change, and republish under the correct version bump.
  3. CRS or axis-order rejection. An allow = false with a matching version usually means crs_epsg fell outside the allowlist or axis_order was inverted. Remediation: reproject at the boundary per CRS Governance and Reprojection Standards; never widen the allowlist to force a pass.
  4. Precision violation. coordinate_precision below the contract minimum fails the policy. Remediation: stop decimating geometry upstream, or bump the contract if the coarser precision is a deliberate, communicated change.
  5. Semantic breakage that passes schema. Values are schema-valid but wrong (units flipped, enum extended). Remediation: add the changed enum/unit to the contract semantics block and treat an incompatible change as MAJOR.
  6. Idempotency collision. The same product_id and contract_version produced two divergent payload hashes. Remediation: return the existing record for identical payloads; reject divergent ones with 409 and require a version bump.
  7. SLA breach without a schema fault. Data is valid but stale or unavailable. Remediation: page the product_owner_id, trigger a producer re-run, and check the freshness monitor against freshness_sla_minutes.

SLA Targets & Performance Baselines

Contract enforcement is itself a product surface with guarantees. These baselines apply per boundary and feed the alerting that governs remediation.

Metric Target Alert Threshold Remediation Action
Contract validation latency (p95) < 30 ms > 90 ms for 10 min Cache compiled policy bundle; co-locate the gate sidecar
Breaking-change escape rate 0 any breach reaching a consumer Add the missing rule; block on CI, not runtime
Schema-drift detection lag < 1 publish cycle drift found post-publish Move the hash check into the producer’s merge gate
Contract-version pin coverage 100% of consumers any unpinned consumer Require X-Contract-Version; deny unpinned reads
Freshness vs freshness_sla_minutes within SLA 2× SLA exceeded Re-run producer pipeline; page owner
Idempotent re-validation 100% identical decisions any nondeterministic result Pin policy bundle version; remove clock/random inputs

Governance & Compliance Notes

Every contract version is immutable and append-only. Publishing v1.3.0 never mutates v1.2.0; the older document stays queryable for a defined retention window (typically 90 days) so audit and rollback can reconstruct exactly which contract a historical payload satisfied. Each boundary decision emits an audit event capturing the actor, the product_id, the resolved contract_version, the policy result, and the offending field on denial — the same evidentiary discipline the catalog applies at registration. The CRS allowlist in the Rego gate is the single enforcement point for approved projections, so an unapproved crs_epsg cannot enter the mesh regardless of which producer publishes it. Ownership is non-negotiable: a contract without an accountable product_owner_id is itself invalid, because a breach with no steward has no path to resolution. Managed this way, the contract becomes the durable interface of a spatial product — consumers plan against it, producers evolve behind it, and the version stamp guarantees the two never drift apart silently.