fix(platform): govern geospatial analysis and raster handoffs

This commit is contained in:
Jens
2026-08-30 06:00:15 +02:00
parent 96f90373dc
commit 80a2d1654d
63 changed files with 2335 additions and 312 deletions
+114 -23
View File
@@ -13,8 +13,8 @@ This document freezes the first API shape. Codex may add implementation details
uses the canonical `{"data": ...}` envelope. Readiness runs an OpenAPI audit
that rejects free-form dictionary responses and envelope drift.
- The only successful non-envelope responses are `/health`, `/health/live`,
`/health/ready`, the four documented persisted-raster PNG endpoints and the
export artifact download endpoint.
`/health/ready`, the two documented Authentik redirects, the documented
persisted-raster PNG endpoints and the export artifact download endpoint.
## Shared schemas
@@ -63,7 +63,7 @@ one client/username combination within five minutes temporarily return HTTP
Optional guest access is a configuration-gated demonstration mode. It creates
a shorter signed session with role `guest`, scopes that session to the
idempotently seeded demo project and blocks mutating operator routes. Project
idempotently seeded demo project and blocks administrative operator routes. Project
listing is filtered to the bound demo project. The frontend exposes the same
exploration, assistant, model-selection, analysis, QA and export workspaces as
an operator. Model catalogs are globally readable; every run, result and export
@@ -73,13 +73,33 @@ administrative mutations remain unavailable. This is deliberately **not**
a substitute for user accounts, authorization or tenant isolation; expose it
only on a dedicated demo installation without private or operational data.
The functional demo boundary includes bounded, project-path-scoped official
source acquisition, persisted bbox selections, temporal comparisons and change
detection. Change detection resolves the source dataset first, requires its
project to equal the signed guest-project UUID and validates the target dataset
against that same project before the synchronous job starts. Cross-project
datasets therefore fail before comparison. Generic uploads, arbitrary
clip/buffer/intersect operations, forceful project management and evidence
adjudication remain blocked.
The login/demo seed remains offline and retains its synthetic raster as an
explicit `fixture` for UI context and fixture QA only. That raster is excluded
from configured detection and segmentation selectors and the production
consumption gate continues to reject it. A guest starts real model inference
by drawing a bounded map selection; GeoIntel then acquires an official regional
orthophoto on demand inside the signed demo project, persists its contextual
source registry/snapshot/checksum evidence, and only then tiles and runs the
configured model. No network acquisition occurs merely by logging in.
### GET `/api/v1/auth/session`
Public session probe used by the frontend before it mounts the workbench.
When authentication is disabled, `authenticated` is true and
`authentication_required` is false so local development retains its existing
direct workflow. `guest_access_enabled` tells the landing page whether it may
show the guest action.
show the guest action. `authentik_enabled` indicates that the additive
Authentik operator flow is fully configured; the local operator login remains
available as a recovery path.
```json
{
@@ -90,6 +110,7 @@ show the guest action.
"expires_at": null,
"role": null,
"guest_access_enabled": true,
"authentik_enabled": false,
"guest_project_id": null
}
}
@@ -112,6 +133,23 @@ Successful login sets the session cookie and returns the authenticated session
shape. Invalid credentials return HTTP 401 `INVALID_CREDENTIALS`; username
existence is not disclosed.
### GET `/api/v1/auth/authentik/start`
Starts an authorization-code OIDC flow with PKCE, signed state and nonce when
all Authentik settings are present. Discovery, token and JWKS requests are
restricted to the configured HTTPS issuer origin, reject redirects and enforce
a bounded JSON response size. The flow cookie is HttpOnly, Secure, ten minutes
or less and scoped to the Authentik callback path.
### GET `/api/v1/auth/authentik/callback`
Validates issuer, audience, signature, expiry, state, nonce and the exact
configured verified e-mail address. Success creates the same operator session
as local login and clears the one-use flow cookie. Failure clears that cookie
and redirects to the landing page with a generic error marker; token or
identity details are never returned to the browser. These two redirect routes
are the only additional non-envelope authentication responses.
### POST `/api/v1/auth/guest`
No request body is required. The endpoint is available only when both
@@ -130,6 +168,12 @@ is available for that bound demo project. Unscoped analysis routes require the
same UUID as a `project_id` query parameter; cross-project values fail before
route execution. Coverage resolution additionally verifies the `project_id` in
the request body against the guest-session scope.
Raster tiling for an already-persisted raster in the bound demo project is an
explicitly allowed preparation step for detection and segmentation. It creates
only integrity-bound inference tiles and a job record; uploads, source
acquisition, model management and arbitrary derived-dataset writes remain
operator-only. Guest tiling is server-capped by the configured inference tile
limit before any tile bytes are written.
### POST `/api/v1/auth/logout`
@@ -286,11 +330,13 @@ Request:
Backend responsibilities:
- Validate geometry.
- Repair trivial polygon issues if safe.
- Store geometry in PostGIS.
- Calculate area in square meters using projected CRS.
- Store bbox.
- Accept only a finite, valid `Polygon` or `MultiPolygon` in the declared CRS.
- Reject unknown/non-2D CRS definitions and geometry outside the Belgium and
Belgian North Sea workbench domain.
- Transform the geometry to EPSG:4326 before PostGIS persistence while
retaining the exact declared CRS in `original_crs`.
- Calculate `area_m2` in Belgian Lambert 72 (`EPSG:31370`) and derive the
persisted EPSG:4326 bbox from the normalized geometry.
### GET `/api/v1/projects/{project_id}/areas/{area_id}`
@@ -299,8 +345,12 @@ area list endpoint and includes persisted GeoJSON geometry for map display.
### PATCH `/api/v1/projects/{project_id}/areas/{area_id}`
Updates the area name and/or geometry. Geometry updates follow the same
validation, repair and metric-calculation rules as area creation.
Updates the area name and/or geometry. A replacement geometry follows the same
strict CRS validation, EPSG:4326 normalization and metric-calculation rules as
area creation and atomically recomputes `geometry`, `bbox`, `area_m2` and
`original_crs`. A PATCH containing `crs` without `geometry` fails with
`INVALID_AREA_CRS_UPDATE`; omitted CRS on replacement geometry means
EPSG:4326.
### GET `/api/v1/projects/{project_id}/areas/municipalities`
@@ -409,10 +459,22 @@ validation report/status, provenance/lineage status, quarantine status and an
idempotent ingest key. A malformed or doubtful artifact is retained as
`status=quarantined`; it is not silently discarded or made ready.
Vector uploads remain stored as original files and are also persisted into
`vector_features` as queryable PostGIS state only after their contract passes.
Non-EPSG:4326 vector coordinates are explicitly transformed before canonical
feature persistence; relabelling Lambert coordinates as EPSG:4326 is rejected.
The backend streams uploads to governed storage in bounded 8 MiB reads while
calculating size and SHA-256. `GEOINTEL_MAX_UPLOAD_MB` (legacy alias
`MAX_UPLOAD_MB`, default 500, allowed range 12,048) is enforced by the backend even when a request
bypasses the reverse proxy. The first byte beyond the configured limit aborts
the ingest, removes the partial file and returns HTTP 413 with
`UPLOAD_TOO_LARGE`. Because GeoJSON validation currently requires an in-memory
parse, vector uploads have the additional lower
`GEOINTEL_MAX_IN_MEMORY_VECTOR_MB` limit (default 64 MiB, maximum 256 MiB).
This limit is enforced during streaming and again with a bounded read before
parsing; larger vector sources must use a governed batch-import workflow.
Vector uploads are also persisted into `vector_features` as queryable PostGIS
state only after their contract passes. Non-EPSG:4326 source bytes are retained
as provenance evidence while the normal Dataset `storage_path` points to the
deterministic EPSG:4326 artifact. Relabelling Lambert coordinates as EPSG:4326
is rejected.
### GET `/api/v1/source-registry`
@@ -1053,7 +1115,13 @@ Failure modes:
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/tile`
Generate raster tiles and a manifest for downstream processing. Returns a job payload with `tile_set_id` and manifest metadata.
Generate raster tiles and a versioned manifest for downstream processing.
Returns a job payload with `tile_set_id` and manifest metadata. Contract
`geointel.raster.tile-manifest@2.0.0` binds the tile set to the exact source
Dataset, DatasetVersion, source registry/snapshot and checksum values that are
available in persistence, plus the source Area and Area-geometry checksum when
scoped. Every tile records immutable byte size/SHA-256, explicit CRS and bounds;
the manifest records its source extent and tile count.
If raster processing dependencies are unavailable:
@@ -1594,13 +1662,16 @@ label or model filename.
### GET `/api/v1/detection/model-assets`
Returns governed local runtime model files. This is a read-only catalog.
Returns configured local runtime model files. This is a read-only catalog;
catalog visibility establishes byte identity and runtime availability only.
GeoIntel never downloads, creates, mutates or deletes model weights from this
endpoint.
The backend scans `YOLO_MODELS_DIR` (default `/app/models`). When
`YOLO_MODEL_PATH` resolves to an existing file, production catalog output is
restricted to that explicitly approved active model. When no active model is
restricted to that active runtime model. Active/available is a runtime-selection
state only: it does not assert governed validation, human review, national
coverage or promotion. When no active model is
configured, supported `.pt`, `.onnx` and `.engine` files remain visible for
development/operator discovery but cannot make the configured detector ready.
@@ -1620,8 +1691,12 @@ Response data:
"size_bytes": 123456,
"sha256": "sha256hex",
"active": true,
"status": "approved",
"limitation_message": "Approved local runtime model asset. GeoIntel will not download or mutate model weights.",
"runtime_available": true,
"runtime_status": "active",
"governed_validation_status": "not_verified_by_catalog",
"promotion_status": "not_verified_by_catalog",
"status": "runtime_active",
"limitation_message": "Active local runtime model asset. Runtime selection is not evidence of governed validation or promotion.",
"will_download_models": false
}
],
@@ -1732,10 +1807,21 @@ governed, runtime-produced artifact the persistence model requires. Rejection is
`STORAGE_PATH_OUTSIDE_ROOT`; `GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS` opts out
for provisioning workflows that stage tiles before ingest.
Configured detection and segmentation revalidate the v2 contract before model
loading. The requested Dataset identity, checksum, latest DatasetVersion,
source snapshot and Area binding must still match; every tile checksum is
recomputed; and the EPSG:4326 tile union must remain inside both the declared
manifest extent and persisted raster extent and intersect its Area where one is
bound. Validation fails closed with the typed suffixes
`TILE_MANIFEST_DATASET_MISMATCH`, `TILE_MANIFEST_PROVENANCE_MISMATCH`,
`TILE_MANIFEST_TILE_INTEGRITY_MISMATCH` or
`TILE_MANIFEST_SCOPE_MISMATCH`, prefixed with `DETECTION_` or
`SEGMENTATION_` for the requesting task.
The manifest must carry explicit CRS metadata (`crs`, `source_crs` or
`dataset_crs`). A manifest without it fails with
`DETECTION_TILE_MANIFEST_INVALID` rather than being georeferenced against an
assumed EPSG:4326, which would place detections plausibly but wrongly.
`dataset_crs`). A manifest without it fails rather than being georeferenced
against an assumed EPSG:4326, which would place detections plausibly but
wrongly.
Tiles are read with rasterio: the visible RGB bands are selected explicitly and
percentile-stretched to 8-bit, so 16-bit and 4-band (RGB + NIR) orthophotos
@@ -2095,6 +2181,11 @@ to the synchronous route. A configured model requires an existing
`SEGMENTATION_ACCELERATOR_MISCONFIGURED` when CUDA is required. A valid
zero-polygon run is shown as an empty model result, never as proof that the AOI
contains no relevant objects.
When a raster is selected but no manifest exists, the normal frontend flow
first requests project-scoped 512 px tiles with 64 px overlap and forwards the
server-generated manifest. Inference then applies the complete v2 provenance,
integrity and scope validation before model load; manual server-path entry is
an operator-only advanced control.
Validation errors: