Defining scoping rules for enterprise spatial products

In a domain-driven geospatial data mesh, product scoping is not a documentation exercise; it is a hard enforcement boundary. When spatial products cross domain lines without explicit extent, resolution, and coordinate reference system (CRS) constraints, downstream consumers hit silent join failures, raster-vector misalignment, and cascading pipeline timeouts. This page is the field-by-field implementation reference for the gates introduced in Scoping Rules for Spatial Products, which itself sits inside the Geospatial Data Mesh Fundamentals reference. It assumes you have already partitioned your estate into autonomous domains using Spatial Domain Boundary Design; scoping rules answer the narrower question of under exactly which CRS, bounding box, and resolution may this product publish, enforced declaratively at the registry ingestion layer before any asset becomes visible to a consumer.

Manifest admission pipeline with a single rejection branch A spatial product manifest flows left to right through three sequential gates: JSON Schema validation, then OPA/Rego policy evaluation enforcing CRS, bounding-box and resolution checks, then registry admission ending in a 200 OK result. Both the schema gate and the policy gate divert a failing manifest downward to a single shared outcome labelled 409 Conflict — manifest rejected, never published. Product manifest spatial_scope.yaml JSON Schema validation structural gate OPA / Rego policy crs · bbox · resolution containment gate stateless eval Registry admission catalog write 200 OK pass pass pass fail deny 409 Conflict — manifest rejected never published; no catalog entry, no reconciliation debt

Prerequisites

Requirement Value / Constraint
Manifest format YAML conforming to the spatial_scope JSON Schema; one manifest per product version
Policy engine OPA 0.60+ run in stateless mode (opa eval) with explicit input binding
CLI tools opa, jq, gdalinfo, ogr2ogr (GDAL 3.6+), python3 with jsonschema
Storage CRS Canonical geometry in a declared projected CRS (e.g. EPSG:32633); never auto-projected to EPSG:4326 at ingest
Access roles Data Steward (corrects manifests), Platform Engineer (re-runs ingestion), Domain Architect (approves extent changes)
Env vars REGISTRY_ENDPOINT, POLICY_BUNDLE_PATH, SCHEMA_PATH, DOMAIN_OWNER

Step-by-Step Implementation

1. Author the declarative manifest

Each spatial product declares a spatial_scope block that bounds it to a single domain’s operational envelope. Treat this block as the authoritative dependency contract: the registry rejects any asset whose physical headers disagree with the declared values.

yaml
product_id: "urban-canopy-coverage-v2"
domain_owner: "environmental-analytics"
spatial_scope:
  crs: "EPSG:32633"
  bounding_box: [10.5, 45.2, 12.1, 46.8]
  resolution_meters: 1.0
  temporal_cadence: "P1Y"
  allowed_formats: ["GeoParquet", "Cloud-Optimized GeoTIFF"]

Verify the file parses and carries every required key before submission:

bash
python3 -c "import sys,yaml; d=yaml.safe_load(open('manifest.yaml')); \
  assert {'crs','bounding_box','resolution_meters'} <= d['spatial_scope'].keys()" \
  && echo "manifest keys OK"

2. Validate the manifest against a strict JSON Schema

Schema validation is the cheapest gate and must run first — in a pre-commit hook and again in CI — so structural drift never reaches the policy engine. Field semantics here stay synchronised with Metadata Cataloging for Raster/Vector, so a manifest that validates also indexes cleanly into the catalog.

bash
python3 -c "import json,yaml,jsonschema; \
  jsonschema.validate(yaml.safe_load(open('manifest.yaml')), json.load(open('spatial_scope.schema.json')))" \
  && echo "schema valid"

Idempotency requirement: treat the manifest as a declarative state file. Re-submitting an identical payload must return 200 OK without mutating lineage graphs or triggering a redundant reprojection job. Submission scripts therefore key on the manifest content hash, not on a wall-clock timestamp.

3. Enforce containment with an OPA/Rego policy

The registry evaluates the manifest against a Rego policy before committing any metadata to the catalog. The policy enforces strict containment — preventing extent bleed into adjacent domains and rejecting non-conforming CRS definitions — and is version-controlled alongside the infrastructure code it gates.

rego
package spatial_scoping

import rego.v1

default allow = false

allow if {
  input.spatial_scope.crs == "EPSG:32633"
  bbox := input.spatial_scope.bounding_box
  bbox[0] >= 10.0
  bbox[1] >= 45.0
  bbox[2] <= 13.0
  bbox[3] <= 47.0
  input.spatial_scope.resolution_meters <= 2.0
}

Evaluate the policy with no side effects by running OPA statelessly with explicit input binding:

bash
opa eval \
  -i manifest.yaml \
  -d policy.rego \
  "data.spatial_scoping.allow" \
  --format pretty \
  --strict

For advanced Rego syntax and CI/CD integration patterns, reference the official Open Policy Agent Documentation. This layer operationalises Product Thinking for GIS Datasets by treating spatial boundaries as immutable product features rather than mutable metadata annotations.

4. Gate publication at the registry ingestion edge

Wire schema validation and policy evaluation into the ingestion endpoint so a failing manifest is rejected at publication time rather than reconciled after the fact. The combined gate returns 409 Conflict on any violation and emits a structured log line the runbook below consumes.

bash
opa eval -i manifest.yaml -d policy.rego "data.spatial_scoping.allow" --format raw \
  | grep -q true \
  && curl -sf -X PUT "$REGISTRY_ENDPOINT/products" --data-binary @manifest.yaml \
  || { echo "policy denied — not publishing"; exit 1; }

Configuration Reference

Field Type Required Constraint / Notes
product_id string yes Stable identity; a major CRS or resolution change requires a new id, not an in-place patch
domain_owner string yes Must match an authorised domain in the boundary registry
spatial_scope.crs string yes EPSG identifier in the domain allowlist (e.g. EPSG:32633); rejected if absent
spatial_scope.bounding_box [minx,miny,maxx,maxy] yes Coordinate order and units must match the declared CRS; must lie within the domain extent
spatial_scope.resolution_meters number yes Upper bound enforced by policy; finer resolutions may carry residency constraints
spatial_scope.temporal_cadence ISO 8601 duration no Refresh interval, e.g. P1Y; feeds product freshness SLAs
spatial_scope.allowed_formats string[] yes Subset of GeoParquet, Cloud-Optimized GeoTIFF; ingest rejects other encodings

Common Failure Modes & Fixes

Symptom: 409 Conflict on manifest submission. Root cause: a spatial_scope value disagrees with the physical asset header — most often CRS or bounding box. Fix: extract the violation from the structured ingestion log with a deterministic jq filter, then correct the manifest to match the asset.

bash
jq -r 'select(.status == 409) | {timestamp: .ts, product: .product_id, violation: .policy_failures}' \
  ingestion-pipeline.log

A typical violation payload:

json
{
  "timestamp": "2024-05-12T14:22:01Z",
  "product_id": "urban-canopy-coverage-v2",
  "policy_failures": ["spatial_scope.crs_mismatch", "spatial_scope.bbox_extent_exceeded"]
}

Symptom: policy rejects on crs_mismatch though the manifest looks correct. Root cause: legacy shapefiles and rasters are frequently auto-projected to WGS84 (EPSG:4326) during ogr2ogr or rioxarray ingest without the manifest being updated. Fix: trace the lineage to the reprojection step and verify the physical header before trusting the manifest.

bash
gdalinfo input.tif | grep "Coordinate System"

Consult the GDAL Warp Documentation for deterministic reprojection flags (-s_srs, -t_srs, -r bilinear) that guarantee idempotent raster alignment, then re-declare crs to the post-warp value.

Symptom: bbox_extent_exceeded on coordinates that visually fall inside the domain. Root cause: decimal degrees are being evaluated against meter-based thresholds, or the coordinate order is transposed. Fix: normalise all inputs to the declared CRS before evaluation and confirm [minx, miny, maxx, maxy] ordering.

Symptom: pipeline timeout during a raster-vector join after admission. Root cause: resolution or CRS misalignment that passed structural checks but not physical alignment. Fix: re-run ingestion with explicit -t_srs and -te (target extent) flags so the join operands share a CRS and grid.

Symptom: cross-domain extent bleed detected in production. Root cause: a domain extent was widened without a boundary renegotiation. Fix: freeze affected downstream consumer pipelines, run a formal impact assessment, and treat the change as a new product version. The escalation path below assigns each tier an owner.

Severity Symptom Resolution path Owner
L1 409 Conflict on submission Correct spatial_scope to match physical headers Data Steward
L2 Pipeline timeout on raster-vector join Re-run ingest with explicit -t_srs / -te; verify resolution Platform Engineer
L3 Cross-domain extent bleed in production Renegotiate domain boundary; freeze consumer pipelines Domain Architect

FAQ

Why enforce scoping at ingestion instead of in a downstream catalog check?

A centralized catalog validates after an asset is already published, which creates reconciliation debt and lets a non-conforming product be read before anyone notices. Evaluating the JSON Schema and Rego policy at the ingestion edge makes the boundary an admission gate: a manifest that violates its CRS, extent, or resolution declaration never becomes visible, so there is no drift to reconcile later.

How do I change a product’s CRS or resolution without breaking consumers?

Do not patch the existing product_id in place. A major CRS or resolution change is a contract break, so mint a new product id and let the old version deprecate through Spatial Product Lifecycle Management. Version bumps within the same id must preserve a backward-compatible spatial envelope and pass a downstream join-compatibility check before promotion.

What makes the submission idempotent?

The manifest is treated as declarative state and keyed on its content hash. Re-submitting an identical payload returns 200 OK without mutating lineage or triggering a reprojection, and OPA is run statelessly with explicit input binding so policy evaluation has no side effects. Retries therefore collapse to the same result instead of accumulating duplicate jobs.

My manifest passes schema validation but the policy still denies it — why?

Schema validation only proves the manifest is structurally well-formed. The Rego policy enforces semantic containment: the CRS must be in the domain allowlist, the bounding box must sit inside the domain extent, and the resolution must clear its ceiling. Run opa eval ... --explain=full to trace exactly which rule failed.

Where do jurisdictional limits like sub-metre imagery attach?

To the resolution_meters field. A restricted product cannot be admitted at a resolution finer than policy allows regardless of consumer role, so the resolution gate doubles as a residency and export control evaluated in the same policy path that enforces extent.