Self-Serve Platform Capabilities for Spatial Teams
The platform’s job is to make the correct way to publish a spatial product the easiest way, and every capability it offers should be judged against that.
Domain ownership only produces autonomy if a domain team can actually stand a product up without waiting on anyone. Where it cannot — where provisioning PostGIS means a ticket, where publishing a tile endpoint means a platform engineer’s afternoon, where wiring metrics means reading three internal wikis — federation has moved the coordination cost rather than removed it, and domains quietly revert to whatever central system still works. This topic, inside Geospatial Data Mesh Fundamentals, specifies the capability set a spatial platform has to provide, how those capabilities are exposed, and how to tell a platform that enables autonomy from one that merely relocates the bottleneck. It is the practical counterpart to the ownership model in Spatial Domain Boundary Design: boundaries without a platform produce twelve teams each rebuilding a reprojection pipeline.
Architectural Boundaries & Design Rationale
A self-serve platform is defined by what it refuses to do as much as by what it offers. Three boundaries decide whether it stays a platform or becomes a new monolith.
The platform owns capability, domains own data. The platform provides the machinery to reproject, validate, tile, publish and monitor; it never holds a domain’s data, never runs a domain’s business logic, and never has a schema of its own that domains must conform to. The moment the platform holds a shared table that domains write into, every schema change is a coordination event again and the mesh is over.
The platform is opt-out, not mandatory. A capability a domain cannot decline is not a platform capability; it is a policy. Domains must be able to substitute their own implementation — their own orchestrator, their own tile renderer — provided the output port and its contract are unchanged. This constraint is what keeps the platform honest: a capability teams would abandon if allowed to is a capability that needs improving, and mandating it merely hides that signal.
The platform’s interface is a declaration, not an API call sequence. A domain declares what it wants — this product, this CRS, this resolution, these ports — and the platform reconciles reality toward that declaration. It does not offer a create endpoint, an update endpoint, and a delete endpoint that a domain must orchestrate correctly. Declarative interfaces are idempotent by construction, which means a partially failed provisioning is fixed by re-applying rather than by reasoning about what already happened.
The load-bearing consequence of all three is that the platform must be usable without understanding it. A domain engineer publishing their first spatial product should not need to know how the tile cache invalidates, how the catalog indexes extents, or which reprojection library runs underneath. They should need to know their data, their CRS, and their consumers. Every unit of platform internals a domain must learn is a unit of coupling that will resist change later, which is why platform capability is measured in time-to-first-published-product rather than in feature count.
Specification & Contract Reference
The capability set below is the minimum for a spatial mesh. Each is expressed as a declaration a domain makes, and each carries an explicit escape hatch.
| Capability | Domain declares | Platform provides | Escape hatch |
|---|---|---|---|
| Domain provisioning | Domain id, perimeter, owning team | Isolated store, credentials, namespace | Bring your own store, register the port |
| Storage | Storage class, retention, CRS | PostGIS instance or object prefix | Self-managed backend |
| Reprojection | Source and target CRS, transformation | Pinned PROJ pipeline + grid package | Own warp step, declare the pipeline |
| Validation | Indicator set, blocking thresholds | Quality gate, report, catalog write | Own gate, publish the same indicators |
| Tiling | Matrix set, zoom range, format | Tile pyramid build and publish | Own renderer behind the same port |
| Output ports | Port types, versions | Endpoint, TLS, routing registration | Own endpoint, register with the router |
| Catalog registration | Manifest | Extent normalization, index, discovery | None — registration is mandatory |
| Observability | SLO targets | Metrics, traces, dashboards, alert routing | Own exporter, same metric names |
| Access control | Allowed principals, spatial scope | mTLS identity, policy enforcement | None — enforcement is mandatory |
| Cost attribution | — | Per-consumer serving cost | None — attribution is automatic |
Three capabilities have no escape hatch, and the pattern is deliberate: catalog registration, access control, and cost attribution are the ones where an opt-out would externalise cost onto everybody else. An unregistered product is invisible and therefore unroutable; an unenforced access policy is a hole in the estate’s perimeter; unattributed cost is a bill somebody else pays. Everything a domain can absorb the consequences of alone is optional; everything whose consequences land on the mesh is not.
The declaration itself is a single manifest, and its shape is the platform’s real interface.
# product.yaml — the whole declaration a domain makes to publish a spatial product.
apiVersion: mesh.geospatial/v1
kind: SpatialProduct
metadata:
domain: cadastral
name: parcels
owner: team-cadastral
spec:
storage:
class: postgis
crs: "EPSG:4326" # canonical storage CRS
retention: P7Y
source:
crs: "EPSG:32633" # declared, never inferred
transformation: "NTv2:national-grid-2024" # pinned, not auto-selected
quality:
blocking: [geometry_validity_rate, crs_conformance, coordinate_precision]
precision_bound: 6
ports:
- type: tiles
matrixSet: WebMercatorQuad
zoom: { min: 6, max: 18 }
- type: features # OGC API – Features
- type: snapshot
format: geoparquet
cadence: P1D
slo:
availability: 0.9995
latency_p95_ms: 300
freshness: P1D
access:
principals: ["logistics/*", "planning/reporting"]
spatialScope: extent # scope-limited, not estate-wide
Production Implementation
The platform reconciles that declaration. The reconciler is idempotent by construction: it computes desired state from the manifest, compares it against observed state, and applies only the difference — so re-running after a partial failure completes the work rather than duplicating it.
# reconcile.py — declarative provisioning for a spatial product.
# Idempotent: applying the same manifest twice is a no-op, which is what makes a
# half-failed provisioning recoverable by re-applying rather than by unwinding.
import hashlib
import subprocess
import yaml
STEPS = ("store", "transformation", "quality_gate", "ports", "catalog", "policy", "slo")
def desired_state(manifest: dict) -> dict:
"""Everything the manifest implies, keyed so drift is detectable per step."""
spec = manifest["spec"]
domain = manifest["metadata"]["domain"]
name = manifest["metadata"]["name"]
return {
"store": {"class": spec["storage"]["class"], "crs": spec["storage"]["crs"],
"namespace": f"{domain}-{name}"},
"transformation": {"pipeline": spec["source"]["transformation"]},
"quality_gate": {"blocking": sorted(spec["quality"]["blocking"]),
"precision": spec["quality"]["precision_bound"]},
"ports": sorted(p["type"] for p in spec["ports"]),
"catalog": {"domain": domain, "product": name},
"policy": {"principals": sorted(spec["access"]["principals"]),
"scope": spec["access"]["spatialScope"]},
"slo": spec["slo"],
}
def fingerprint(step_state: dict) -> str:
"""Stable digest of one step's desired state — the drift comparison key."""
canonical = yaml.safe_dump(step_state, sort_keys=True).encode()
return hashlib.sha256(canonical).hexdigest()[:16]
def reconcile(manifest_path: str, dry_run: bool = False) -> int:
manifest = yaml.safe_load(open(manifest_path))
desired = desired_state(manifest)
changed = 0
for step in STEPS:
want = fingerprint(desired[step])
have = subprocess.run(
["mesh-platform", "get-fingerprint", step, manifest["metadata"]["name"]],
capture_output=True, text=True,
).stdout.strip()
if want == have:
continue # already reconciled — do nothing
changed += 1
if dry_run:
print(f"would apply: {step} ({have or 'absent'} -> {want})")
continue
subprocess.run(
["mesh-platform", "apply", step, "--manifest", manifest_path],
check=True, # fail closed: a failed step stops the run
)
return changed
Applying it, and confirming that a second application is genuinely a no-op:
# First apply provisions everything the manifest implies.
mesh-platform apply --manifest product.yaml
# Re-apply: the reconciler should report zero changes. A non-zero count on an
# unchanged manifest means a step is not computing its fingerprint deterministically.
mesh-platform apply --manifest product.yaml --dry-run
# expected: "0 changes"
# Confirm the product is discoverable and its ports resolve.
curl -s "$CATALOG_API/products/cadastral/parcels" | jq '{version, ports: [.ports[].type], slo}'
Diagnostic Runbook
- When a domain reports “provisioning is stuck”, re-apply before investigating. The reconciler is idempotent, so re-applying is free and completes any step that failed transiently. Only a step that fails twice is a real fault.
- Read the per-step fingerprints, not the logs.
mesh-platform get-fingerprint <step> <product>shows exactly which step’s observed state diverges from the manifest. A stuck provisioning almost always has one divergent step, and the logs contain every step. - A port that provisions but never serves usually means routing registration, not the port itself. Confirm the router’s compiled table contains the product before debugging the backend; a port with no route is healthy and unreachable.
- If the same manifest yields different results in two environments, compare the pinned transformation and grid package first. This is the most common environment-specific divergence in a spatial platform, and it produces subtly different coordinates rather than an error.
- A domain that has bypassed a capability should be visible, not hidden. Query the catalog for products whose declared ports do not match platform-provisioned ports. A domain running its own renderer is fine; a domain running its own renderer that nobody knows about is an outage waiting to be misdiagnosed.
- When time-to-first-product regresses, instrument the path rather than surveying the team. Record timestamps at manifest submission, first successful reconcile, and first successful publish. The gap that grew names the capability that needs work.
- For a capability nobody uses, ask whether it is undiscoverable or unnecessary before improving it. Both look identical in usage metrics, and only one is worth engineering effort.
The Golden Path, and Why It Must Stay Narrow
A capability list is not yet a platform. What turns it into one is a golden path: a single, opinionated, documented route from “we have some spatial data” to “it is published, discoverable, monitored and governed”, which works end to end without a decision the team is not equipped to make.
The golden path’s value comes from being narrow. Every option it offers is a decision a new domain must research, and a path with twelve choices at each step is a research project rather than a route. The correct default for a spatial mesh is opinionated to the point of being slightly uncomfortable: storage in PostGIS at EPSG:4326, tiles in WebMercatorQuad, snapshots in GeoParquet, blocking quality gates on validity and precision, an availability SLO of 99.9%. A domain with a genuine reason to differ can differ — the escape hatches exist — but it has to have the reason, and needing one is the point.
| Golden-path step | Opinionated default | When a domain should deviate |
|---|---|---|
| Storage CRS | EPSG:4326 |
Survey-grade metric work inside one UTM zone |
| Tile matrix set | WebMercatorQuad |
A polar or national grid the consumers already use |
| Snapshot format | GeoParquet |
Raster products, which take COG instead |
| Blocking gates | Validity, CRS, precision | Never — these are the floor |
| Availability SLO | 99.9% |
A product whose consumers genuinely need more, or less |
| Update cadence | Daily | Source cadence differs materially |
Two properties keep the path honest over time. It must be executable, not documented: a page describing the steps decays, while a template repository that provisions a working product on first run cannot decay without failing visibly. And it must be the path the platform team themselves use to stand up their own reference products, because a golden path its authors bypass is a path that has stopped matching reality and nobody has noticed.
The measure of the golden path is not adoption but time. A new domain reaching a published, monitored, discoverable product in under a day means the path works; three days means there is a manual step somewhere the platform team has stopped seeing because they know the workaround.
SLA Targets & Performance Baselines
| Metric | Target | Alert threshold | Remediation |
|---|---|---|---|
| Time to first published product | < 1 day for a new domain |
> 3 days |
Instrument the path; find the manual step |
| Reconcile idempotency | 0 changes on unchanged manifest |
Any non-zero | A step’s fingerprint is non-deterministic |
| Reconcile duration | < 10 min full provisioning |
> 30 min |
Parallelise independent steps |
| Platform capability availability | 99.9% |
< 99.5% rolling 1h |
Platform on-call |
| Escape-hatch usage | Tracked, not minimised | Rising sharply on one capability | That capability needs work |
| Manual platform tickets | < 2 per domain per quarter |
> 5 |
A capability is missing or undiscoverable |
The escape-hatch row is deliberately not a target to minimise. A platform whose escape hatches are never used is either perfect or mandatory, and it is rarely the first. Rising substitution of one capability is the clearest signal available that the capability is worse than what a domain can build themselves, and suppressing it removes the signal without fixing the cause.
A note on documentation, because it is where most platform effort is wasted. A platform whose capabilities are documented but not discoverable from the tools a domain already uses will be under-used regardless of how good the documentation is. The discoverability that actually works is in-band: the reconciler naming the capability that would have handled a step a domain wrote by hand, the manifest schema carrying descriptions that surface in an editor, the error message from a failed publish naming the gate and linking to what it checks. Every one of those reaches a domain engineer at the moment they need it, which no wiki page does.
The corollary is that platform errors are a user interface. An error saying “reconcile failed” teaches nothing and produces a support ticket; one saying “step transformation diverged: manifest declares NTv2:national-grid-2024, provisioned pipeline is Helmert:7param-1998 — re-apply to converge” resolves itself. Investing in the error text of the ten most common failures typically removes more platform toil than any new capability.
None of this survives without the platform team using their own path regularly. A team that provisions its reference products by hand, because they know the shortcuts, will not notice the golden path decaying until a domain reports it — by which point the domain has already built a workaround they will keep using.
Governance & Compliance Notes
The platform team’s accountability is for capability, not for correctness of domain data — and confusing the two is how platform teams become approval bodies. If a domain publishes a product with poor accuracy, that is the domain’s to answer for; if the platform’s quality gate failed to run, that is the platform’s. Keeping the line clear is what allows the platform to be operated as a product with its own SLOs rather than as a governance function.
Compliance obligations do attach to the platform where it holds the enforcement point. Because access control and cost attribution have no escape hatch, the platform is the authoritative record of who accessed which product over which extent, and that record inherits the retention requirements of the most regulated domain it serves. Where a domain is subject to jurisdictional residency constraints, the platform’s provisioning must be able to place that domain’s store and compute in a compliant region from the manifest alone — a capability whose absence forces the domain out of the platform entirely, which is the worst available outcome for both compliance and visibility.
Finally, the manifest schema is itself a governed artifact. Adding a required field to it is a breaking change for every domain, and it deserves the same versioning discipline the platform asks of spatial products: a version bump, a migration window, and a period where both shapes are accepted. A platform that changes its own interface without that discipline while requiring it of others will not be trusted with the parts of the estate that matter.
Related
- Spatial Domain Boundary Design — the ownership model this platform serves
- Spatial Data Quality and Validation Standards — the gate the platform runs on every publish
- Scoping Rules for Spatial Products — the manifest validation the platform enforces at registration
- Orchestrating Spatial Pipelines in Python — the orchestration a domain may substitute for the platform’s
- Geospatial Data Mesh Fundamentals — up to the section overview