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.
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.
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:
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.
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.
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:
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.
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.
jq -r 'select(.status == 409) | {timestamp: .ts, product: .product_id, violation: .policy_failures}' \
ingestion-pipeline.log
A typical violation payload:
{
"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.
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.
Related
- Up to the parent reference: Scoping Rules for Spatial Products — the gate specification this implementation detail belongs to
- Spatial Domain Boundary Design — defines the extent each scoping rule enforces
- Metadata Cataloging for Raster/Vector — the index admitted manifests are written into
- Spatial Product Lifecycle Management — the state machine a scoped product advances through