Architecture

Storage, serving, and authorization are separate on purpose.

STAC defines the storage model. OGC API — Features is the serving surface. One gateway and one permission service decide every request. No service holds a second copy of the rules.

Four backend servicesOne trust boundaryCloud-agnosticEnvironment-only config

System shape

One request path, one set of rules.

Clients reach the gateway. The gateway authenticates the caller, asks the permission service for a decision, enforces the request quota in Redis, strips any client-supplied tenant header, and injects the trusted context upstream. featureflow serves features from PostGIS; admin-api runs onboarding and loads. Neither decides access.

Sole trust boundary

The gateway is the only place where identity and tenant context are established.

One durable store

PostGIS holds tenants, collections, items, licenses, API-key hashes, and the usage outbox.

No proprietary client

QGIS, a CLI, or a custom app use the same open HTTP surface.

Request path

The whole system on one page.

Public catalogue listings are the only anonymous branch. They return metadata and never serve feature data.

LocateFlow request pathClients reach the gateway, which authenticates the caller, asks the permission service for a decision, enforces quota counters in Redis, and injects tenant context into featureflow and admin-api. Both services use the data plane: PostGIS for durable state, Redis for quota counters, and blob storage for files. Keycloak is the identity provider. Public catalogue listings branch at the gateway before authentication and return metadata only.Clientsauthenticated by defaultQGIS / desktop GISWeb apps (Vue)CLI / machine clientsPublic visitorsOIDC · API keyOAuth2 client-credentialspublic listing: anonymousKeycloakOIDC · realms · machine clientsthe only IdPGateway · PEP1 authenticate2 authorize via PDP3 request quota (Redis)4 strip + inject contextsole trust boundaryPermission service · PDPownership · visibilitylicenses · terms · quotascapabilities · suspensionfirst match: owner → host→ public-open → licensed → denyfeatureflowOGC API — FeaturesCore + Part 3 / CQL2read-only · no auth decisionsadmin-apiadmin + loader pipelineonboarding · publish · licenseusage read · uploadsData planedurable state · ephemeral counters · filesPostGISSTAC · items · licensesRedisquota countersBlob storageschemas · assetstokensdecideinjected contextadmin routesrules / datapublic listing: metadata onlyauthenticated request pathpublic metadata branch
All feature-data requests go through the gateway and the PDP. The public catalogue listing is the one anonymous branch, and it never serves feature data. Source: platform architecture (ADR-0037, ADR-0038, ADR-0050).

Services

What each deployable owns.

The mixed legacy binary is gone. Each service can be deployed and scaled independently.

ServiceRoleOwns
gatewayPolicy enforcement point (PEP)Authentication, PDP call, quota enforcement, context stripping/injection, routing.
permission-servicePolicy decision point (PDP)Ownership, visibility, licenses, terms, capabilities, quotas, suspension, discovery scope.
featureflowServingOGC API — Features Core + Part 3/CQL2, bbox/datetime, keyset pagination. No authorization state.
admin-apiAdministration + loaderOnboarding, uploads, load jobs, publishing, licenses, API keys, usage reads.
frontendVue SPAAdmin UI, public portal, tenant discovery views, sign-in and registration.

Decision flow

What happens on a feature-data request.

The sequence is deliberately boring: authenticate, decide, enforce, inject, serve, audit.

  1. Authenticate

    The gateway accepts an OIDC user token, a tenant API key, or an OAuth2 client-credentials token. Public listing paths are the only exception.

  2. Decide

    The gateway calls the permission service. The PDP resolves the first matching rule: owner → host → public-open → licensed → trial → deny.

  3. Enforce quota

    If the license carries a request quota, the gateway increments an atomic Redis counter for the fixed UTC window. Redis outage is fail-open with a warning.

  4. Inject context

    The gateway strips client-supplied tenant context and injects the authenticated tenant, roles, granted subset, and restriction terms.

  5. Serve

    featureflow applies the injected context as data filters and returns whole features from PostGIS. It never validates or decides access.

  6. Audit

    The gateway appends one usage/audit event per reached decision to the append-only log. Denials and quota outcomes are recorded too.

Trust boundary

The serving layer cannot be argued with.

featureflow has no rules engine, no tenant lookup, and no client context. It receives a decision and applies it as filters. That keeps the authorization path auditable and prevents a second, divergent implementation of access control.

Public listing ≠ access. The public catalogue is the only unauthenticated family. It returns a fixed metadata projection and never feature data, geometry, or asset bytes. A listing cannot substitute for a PDP decision.

Deployment

Cloud-agnostic by construction.

The reference deployment is a k3s cluster with Traefik, Cloudflare Tunnel, an external PostGIS VM, Redis, and Keycloak. Configuration is environment variables only.

k3s + Traefik

Two namespaces (dev and prod), default-deny network policies, and a single ingress layer.

PostGIS 17

External VM, migrations embedded in the images, forward-only and applied once.

Cloudflare Tunnel

Public hostnames terminate TLS at the edge; Traefik receives plain HTTP inside the cluster.

Keycloak

One IdP for users and machine clients, with separate prod and dev realms.

One image per deployable

gateway, featureflow, admin-api, permission-service, and the Vue frontend.

GitOps scaffold

Argo CD manifests exist; the deploy repository remains owner-only and is applied explicitly.

Status & versions