Hybrid Topologies for Authoritative Registers

Some spatial datasets cannot be federated, and pretending otherwise is how a mesh transition acquires a legal problem. A cadastre, a national address file, an official flood map — these frequently carry a statutory requirement that exactly one custodian holds exactly one copy with one audit trail. The workable answer is not to exempt them from the mesh but to keep the register central and expose it as one domain’s product like any other. This guide builds that hybrid: a single-custodian register with a mesh-conformant output port, versioned contract and published SLO. It is the exception case named in Data Mesh vs Traditional GIS Architecture within Geospatial Data Mesh Fundamentals.

Prerequisites

Requirement Value / Assumption Notes
Tools PostGIS ≥ 3.3, the platform CLI, an audit log store The register’s write path stays unchanged
Constraint A stated single-custodian obligation Hybrid is for statutory cases, not preference
Register An existing authoritative store with its own write controls The mesh does not take over writes
Access roles Register custodian retains write; mesh grants read The split is the whole design
Environment REGISTER_DSN, PRODUCT Exported before running

Step-by-Step Implementation

1. Draw the line between the register and the product

The register is the authoritative store and its write path is untouched. The product is a read-only, contracted projection of it that the mesh routes to.

yaml
# register-product.yaml — the register exposed as one domain's product.
apiVersion: mesh.geospatial/v1
kind: SpatialProduct
metadata:
  domain: cadastre
  name: parcels_authoritative
  owner: team-land-registry           # the statutory custodian, unchanged
spec:
  storage:
    class: external                   # the mesh does NOT provision this store
    connection: "register://land-registry/parcels"
    crs: "EPSG:4326"
  write_path: custodian_only          # the mesh never writes here
  ports:
    - type: features                  # read-only projection
    - type: snapshot
      format: geoparquet
      cadence: P1D
  slo:
    availability: 0.9995
    latency_p95_ms: 400
    freshness: P1D
  access:
    principals: ["*/read"]            # broad read, no write principal exists
    spatialScope: extent
  audit:
    retention: P30Y                   # the register's obligation, not the platform's
    fields: [caller, extent, version, timestamp]

Where the register ends and the mesh product beginsFour layers read bottom to top. The register internal store and its statutory write path are untouched by the mesh. A published view projects only the contract columns, which is what lets the register evolve internally without breaking consumers. The mesh read port exposes that view as an ordinary product with a contract and an SLO. Routing and policy treat it exactly like any other product. The annotations record what each layer must not do, with the write path the one that carries the statutory obligation.Routing & policyordinary path, no special casean exception here spreadsMesh read portcontract + SLOread-only, alwaysPublished viewcontract columns onlyno internal columnsRegister storecustodian write paththe mesh never writes

Verify the mesh holds no write capability against the register — the single most important assertion in the design:

bash
mesh-platform describe --product "$PRODUCT" | jq '.write_principals'
# expected: []  — an empty write principal set, enforced rather than documented.
psql "$REGISTER_DSN" -c "\du mesh_reader" | grep -q 'Cannot login\|NOSUPERUSER'

2. Project the register into a contracted read port

The projection is where the register’s internal schema stops and the published contract begins, which is what lets the register evolve internally without breaking consumers.

sql
-- published_view.sql — the contract surface, not the register's internals.
CREATE OR REPLACE VIEW published.parcels_authoritative AS
SELECT
    r.parcel_uid                                   AS parcel_ref,
    -- The register stores in a national grid; the contract publishes EPSG:4326.
    ST_Transform(r.geom, 4326)                     AS geom,
    r.owner_reference,
    r.tenure_type,
    r.registered_on,
    -- Internal columns deliberately absent: workflow state, staff annotations,
    -- pending-application flags. The boundary is what makes the register evolvable.
    r.register_version                             AS source_version
FROM registry_internal.parcels r
WHERE r.status = 'registered';       -- pending applications are not authoritative

-- Read-only, and grants are explicit rather than inherited.
REVOKE ALL ON published.parcels_authoritative FROM PUBLIC;
GRANT SELECT ON published.parcels_authoritative TO mesh_reader;

Verify that no internal column leaked into the published view, which is the failure that couples every consumer to the register’s workflow:

bash
psql "$REGISTER_DSN" -c "\d+ published.parcels_authoritative" \
  | awk '/^ /{print $1}' | grep -E 'workflow|pending|staff|internal' \
  && echo "LEAK: internal column exposed" || echo "projection is clean"

3. Route to it exactly like any other domain product

The routing plane must not special-case the register, or the hybrid becomes an exception that every future change has to remember.

bash
# Register the coverage so the routing plane resolves it normally.
mesh-platform register --manifest register-product.yaml
mesh-platform check-overlap --product "$PRODUCT"

# A consumer request goes through the ordinary path: identity, contract, residency,
# domain resolution, SLA tier, quota. Nothing about the register is special here.
curl -sS -H "x-spatial-domain: cadastre" \
     -H "x-spatial-crs: EPSG:4326" \
     "https://api.internal/collections/parcels_authoritative/items?bbox=-1,50,1,52&limit=5" \
  | jq '.features | length'

A request to the register takes the ordinary routing pathA consumer request for register data passes the same gates as any other product: identity, contract, residency, domain resolution, and quota. It reaches the published read-only view rather than the register internal store. Nothing in the path special-cases the register, which is the point — an exception in routing survives every refactor and is forgotten by whoever inherits it. The rail records what a bespoke integration bypasses: every one of those gates, and the audit record they produce.Ordinary gatesidentity · contract · quotaPublished viewcontract columnsRegister storeread-only, custodian-ownedadmittedprojectedbespoke integrationBypasses every gateand the audit record too

4. Preserve the register’s audit obligation through the mesh

A statutory register usually has to record who read what. The mesh’s access telemetry becomes that record rather than a second, competing one.

python
# audit_bridge.py — mesh access records satisfying the register's obligation.
def audit_record(request) -> dict:
    """Every field the register's obligation requires, sourced from the mesh's own
    zero-trust layer. One record, not two systems that will eventually disagree."""
    return {
        "caller": request.authenticated_identity,   # from mTLS, not self-asserted
        "extent_wkt": request.bbox_wkt,             # WHAT was read, not merely that
        "product_version": request.resolved_version,
        "timestamp": request.received_at,
        "response_feature_count": request.response_count,
        "register_source_version": request.upstream_version,
    }

Which obligations stay with the custodian and which the mesh can carryFive obligations against who holds each in a hybrid. Write authority and single-copy custody stay entirely with the register custodian. The audit trail can be carried by the mesh access telemetry, which records caller, extent and version in one place rather than two systems that will disagree. Access control moves to the mesh policy layer. Availability becomes a published SLO the custodian commits to. The pattern is that custody stays and the interface moves.Held byNoteWrite authoritycustodianstatutory, unchangedSingle-copy custodycustodianno domain may copy itAudit trailmesh telemetryone record, not twoAccess controlmesh policyper identity and extentAvailability SLOcustodian, publishedconsumers can design around it

Verify the extent is captured — an audit record naming the caller and the endpoint but not the region read cannot answer the question an audit asks:

bash
curl -sS "$AUDIT_API/records?product=$PRODUCT&limit=1" \
  | jq 'keys, (.[0] | has("extent_wkt"))'

Configuration Reference

Element Value Rationale
storage.class external The mesh does not provision or own the register
write_path custodian_only Statutory obligation; enforced, not documented
Published view Contract columns only Lets the register evolve internally
Pending records Excluded Only registered entries are authoritative
Routing Ordinary path An exception in routing becomes an exception everywhere
Audit retention The register’s, not the platform’s The obligation follows the data
Audit fields Include the extent “Who read what” needs the region

Common Failure Modes & Fixes

A consumer breaks when the register refactors internally. Root cause: the published view exposes internal columns. Fix: the view is the contract surface — publish the contract’s columns and nothing else, however convenient the extra ones look.

Two versions of the register’s data exist in the mesh. Root cause: a domain cached or copied the register locally for performance. Fix: this is precisely what a single-custodian obligation forbids; if latency is the problem, the answer is a read replica the custodian owns, not a copy another domain owns.

Routing special-cases the register. Root cause: it was onboarded before it had a manifest. Fix: register it properly; a special case in routing survives every refactor and is forgotten by whoever inherits it.

The audit record cannot answer which parcels a caller saw. Root cause: only the endpoint and caller were logged. Fix: capture the requested extent and the response count; the URL is aggregated away long before the audit is asked.

Pending applications appear in published results. Root cause: the view omits the status filter. Fix: filter to registered entries; a pending application is not authoritative and publishing it can have legal consequence.

FAQ

Is a hybrid an admission that the mesh does not work?

No — it is the mesh’s ownership model applied honestly to a constraint. The mesh’s claim is that domains own their products end to end and expose them through contracts; a statutory register has an owner, a product, a contract, an SLO and an audit trail, so it satisfies every one of those. What it does not do is decompose internally, and nothing in the model requires that. The genuine failure would be exempting it from contracts and routing so that consumers reach it by a private path nobody governs.

Can the register’s data be joined against mesh products?

Yes, through the ordinary federated query path, and this is one of the strongest arguments for the hybrid. Because the register is exposed as a normal product with a declared extent and CRS, a flood-risk join against it works exactly like any other cross-domain join — with pushdown, budgets and per-side provenance. A register accessed by a bespoke integration gets none of that, and every consumer builds their own.

What if the custodian will not expose a read port at all?

Then the constraint is organisational rather than statutory, and it is worth separating the two before designing around it. A genuine legal obligation almost always concerns custody, write authority and audit — not read access, which registers typically already provide through some channel. Where the reluctance is about load or misuse, the mesh’s rate limiting, quota and access policy address it more precisely than a closed door, and the audit bridge gives the custodian better visibility than they had before.

Does the register need to meet an SLO like other products?

It should publish one, and the number should reflect what it actually delivers rather than what the mesh would like. A register with a 99.9% availability and a 400 ms p95 is a fine product; consumers can design around it once it is stated. What causes trouble is an unstated SLO, because consumers then assume the platform default and build accordingly — and the first outage is a surprise on both sides.

Does the hybrid make the register a single point of failure for the mesh?

It makes it a single point of failure for products that depend on it, which was already true before the mesh existed. What the hybrid changes is that the dependency is now visible: the register is a named product with a published SLO and its consumers are enumerable from access telemetry, so the blast radius of an outage can be stated rather than guessed. That visibility is what lets consumers decide whether to cache a read-only projection for resilience, and lets the custodian size their availability commitment against what actually depends on it.