GeoVault Capability Audit
Date: 2026-05-28
Scope: Capability consistency across major object types in the GeoVault monolith (geovault/).
Method: Code and UI review of routes (main.go), API handlers (internal/api/), persistence (internal/db/), and web UI (web/). No code changes were made.
Executive summary
GeoVault is a single Go service with server-rendered HTML and vanilla JS. Most “datasets” share one Version catalog model (file-backed vectors/rasters, URL/S3 imports, ESRI imports) plus registry rows in PostgreSQL. PostGIS and GeoServer remote datasets are registry pointers without file copies. QGIS Projects are external runtime objects proxied to qgis-publish-service.
Versioning is strong for file-based datasets (sequential v1, v2, … on disk + catalog) but there is no rollback to a prior dataset version—only delete non-latest versions or create derived versions (filter/dissolve/clip). Rollback exists only for QGIS runtime project snapshots.
The largest consistency gaps are: uneven publish targets (vector → tileserver, raster → QGIS), GeoServer remote/catalog datasets that look like vectors/rasters but lack file workflows, PostGIS live tables with no schema history, missing clone/rename/archive across types, and fragmented import entry points (Upload, URL, S3, ESRI, GeoServer catalog).
1. Capability matrix
Legend: Y = supported, P = partial / constrained, N = not supported, — = not applicable.
| Type |
Create |
Import |
Preview |
Metadata |
Publish |
Versioning |
Rollback |
Export |
Delete |
Notes |
| Vector (file) |
Y |
Y |
Y |
Y (read-only GDAL) |
Y (tileserver) |
Y |
N |
Y (download) |
Y |
Filter/dissolve/clip → new versions; optional PostGIS + GeoServer on ingest |
| Raster (file) |
Y |
Y |
Y |
Y (read-only) |
Y (QGIS WMS) |
Y |
N |
Y (download) |
Y |
No geoprocessing transforms; raster list omits URL import link |
| PostGIS (registered) |
Y (register) |
P (vector→PostGIS on file ingest) |
P (table info, no map) |
Y (table/schema) |
N (from version row) |
P (single v1 row) |
N |
N |
Y (dataset) |
Live row edit; no file versions; upload card hidden |
| GeoServer remote |
Y (catalog import) |
Y (catalog) |
P (WMS preview on pubs card) |
Y (service URLs in meta) |
N (already published) |
P (single v1 stub) |
N |
N |
Y |
Listed under vector/raster by inferred type; no local file |
| GeoServer connections |
Y |
— |
— |
Y (connection record) |
Y (publish jobs) |
N |
N |
N |
Y |
Workspaces/styles managed; geoserver_jobs for async work only |
| PG connections |
Y |
— |
P (browse/preview rows) |
Y |
— |
N |
N |
N |
Y |
Admin-level service ACL; not versioned |
| PostGIS registry DBs |
Y (adopt/create) |
P (discover) |
— |
P |
— |
N |
N |
N |
P |
Via postgis-service proxy |
| QGIS Projects |
P (via raster publish) |
— |
Y (viewer URL) |
P |
Y (runtime) |
Y (snapshots) |
Y |
Y (ZIP) |
P (external) |
Not in GeoVault DB; history in qgis-publish-service |
| ESRI import |
P |
Y |
— |
P |
— |
P (creates vault datasets) |
N |
N |
— |
PostGIS + GeoJSON file per layer; background jobs |
2. Per-type detail
VECTOR DATASETS (file-based)
Model: models.Version with type: vector; files under /data/projects/{projectId}/{dataset}/v{N}/; metadata JSON in data/metadata/.
A. CRUD + lifecycle
| Capability |
Status |
Implementation |
| Create |
Y |
/upload (filename → dataset name) or first upload on /datasets/{name}/upload |
| Import |
Y |
/import-url, /import-s3 (import mode), optional ESRI → GeoJSON + PostGIS |
| Upload |
Y |
/datasets/{name}/upload, /projects/{id}/upload |
| Edit metadata |
P |
GDAL-extracted metadata read-only; optional message / version_label at ingest only |
| Rename |
N |
No API or UI |
| Clone/copy |
N |
No dataset clone |
| Delete |
Y |
DELETE /datasets/{name} (admin); DELETE .../versions/{version} (non-latest only) |
| Archive/soft delete |
N |
Hard delete only |
| Background jobs |
P |
GeoServer publish jobs; ESRI import jobs (separate flow) |
| Batch operations |
N |
— |
B. Data management
| Capability |
Status |
Notes |
| Preview/view |
Y |
Map preview (Leaflet), /preview, version modal |
| Download/export |
Y |
GET /versions/{id}/download |
| Import into PostGIS |
Y |
Optional panel on upload, URL, S3 |
| Publish to GeoServer |
Y |
Optional panel on ingest; dedicated /geoserver/publish for existing versions |
| Link to QGIS |
P |
Via raster-style publish only on raster; vectors use tileserver |
| Metadata editing |
N (read-only) |
Tab + modal show extracted metadata |
| Tags/categories |
N |
— |
| Search/discovery |
P |
/api/discovery lists dataset names only |
| Permissions/access |
Y |
Federation geovault_dataset resources: view/edit/admin; visibility public/restricted in DB (display only in UI) |
C. Version control / history
| Question |
Answer |
| Versioning exists? |
Yes — integer versions, on-disk dirs, versions table / store.Versions |
| Revision history? |
Yes — list on dataset Versions tab |
| Rollback? |
No — cannot restore v2 from v3; only delete or add versions |
| Snapshotting? |
Implicit — each version is a full file snapshot |
| Audit logs? |
Partial — created_by, created_at, operation, parent_version on derived versions |
| File versioning? |
Yes |
| Lineage/provenance? |
Partial — parent_version + operation for filter/dissolve/clip; not shown in UI tree |
D. UI actions inventory
| Location |
Actions |
| Nav / hub |
Federation workspace; sidebar via shared UI |
/vectors (Vector Files tab) |
Grid: Open, Download (if allowed), badges |
/upload |
File upload + PostGIS import panel + GeoServer publish panel |
/import-url, /import-s3 |
Remote ingest + same sidecar panels |
/datasets/{name} |
Tabs: Preview, Metadata, Versions, Edit Data (placeholder for files) |
| Versions tab |
Upload new version; per row: Preview/Metadata, Publish (Tileserver), Transform (Filter, Dissolve, Clip), Delete (non-latest) |
| Header |
Delete dataset |
| GeoServer card |
List publications, WMS preview |
Missing obvious actions: Clone dataset, rename, rollback to version, edit visibility/permissions in UI, promote version to latest, batch delete, export to formats other than raw file download.
RASTER DATASETS
Same version model with type: raster.
A. CRUD + lifecycle
Same as vector file datasets except: no filter/dissolve/clip derived versions.
B. Data management
| Capability |
Status |
Notes |
| Preview/view |
Y |
Raster preview via GDAL |
| Download/export |
Y |
Same download endpoint |
| Import into PostGIS |
P |
Panel present on upload/URL/S3 but typically N/A for rasters |
| Publish to GeoServer |
Y |
Ingest sidecar + /geoserver/publish |
| Link to QGIS |
Y |
POST /versions/{id}/publish → QGIS publish service |
| Publish to tileserver |
N |
Vectors only |
C. Version control
Same as vector files: multi-version yes, rollback no.
D. UI actions inventory
| Location |
Actions |
/rasters |
Grid (no URL import link in empty state — inconsistency vs vectors) |
/upload, /import-url, /import-s3 |
Same as vector |
/datasets/{name} |
Versions: QGIS publish button (not Tileserver); no Transform menu |
/projects |
Lists QGIS runtime projects created from raster publish |
Missing: URL import discoverability on raster page; tileserver parity; rollback; clone.
POSTGIS CONNECTIONS / DATABASES / REGISTERED DATASETS
Three related concepts:
pg_connections — saved PostgreSQL credentials (/connections, /api/connections/*).
- PostGIS registry —
postgis-service proxy (/discover-postgis, /api/postgis-service/*).
- Registered datasets —
datasource_type: postgis, live table pointer (RegisterPostGISDataset).
A. CRUD + lifecycle (connections)
| Capability |
Connections |
Registered dataset |
| Create |
Y |
Y (register from browse) |
| Import |
— |
P (ogr2ogr from file ingest elsewhere) |
| Upload |
— |
N (upload card hidden on dataset page) |
| Edit metadata |
P |
Connection edit; table schema via API |
| Rename |
N |
N |
| Clone |
N |
N |
| Delete |
Y (connection) |
Y (dataset) |
| Archive |
N |
N |
| Background jobs |
N |
N |
| Batch |
N |
N |
B. Data management
| Capability |
Status |
| Preview/view |
P — row preview in connection browse; dataset: table info, no map |
| Download/export |
N |
| Publish to GeoServer |
P — can publish file versions to GeoServer, not live PostGIS table from dataset row |
| Link to QGIS |
N from PostGIS dataset row |
| Metadata |
Y — schema/geometry via API |
| Tags |
N |
| Search/discovery |
P — appears on vector list PostGIS tab |
| Permissions |
Y — requirePostGISDatasetPermission + dataset ACL |
C. Version control
| Question |
Answer |
| Versioning? |
No meaningful versioning — one catalog row v1, “Registered from PostGIS” |
| Schema/DB change history? |
No — live edits mutate PostGIS directly |
| Rollback? |
No |
| Audit logs? |
No row-level history in GeoVault |
D. UI actions inventory
| Page |
Actions |
/connections |
Add, test, edit, delete connection; browse tables |
/connections/{id}/browse |
Preview rows, register as dataset |
/vectors?tab=postgis |
List registered PostGIS datasets |
/discover-postgis |
Discover/adopt databases (registry) |
/datasets/{name} (PostGIS) |
Edit Data tab: filter, sort, paginate, insert row, save row; Table info; no upload/transform/publish/delete version |
Missing: Schema migration history, point-in-time restore, export table, register URL path, versioned snapshots of table state.
GEOSERVER CONNECTIONS / RESOURCES
A. CRUD + lifecycle (connections)
| Capability |
Status |
| Create/update/delete |
Y — /api/geoserver/connections |
| Test connection |
Y |
| Workspaces |
Y — list/create/delete |
| Styles |
Y — list/upload/assign/preview SLD |
| Catalog import |
Y — POST /api/geoserver/catalog/import → vault dataset |
| Publish |
Y — on ingest or POST /api/geoserver/publish |
| Background jobs |
Y — geoserver_jobs (queued/running/completed) |
B. Data management
| Capability |
Remote catalog dataset |
Publication record |
| Preview |
P — WMS preview button on dataset card |
Service URLs stored |
| Download |
N |
N |
| Metadata |
Y in datasource_meta |
Workspace/layer/style |
| Permissions |
Y (dataset ACL) |
— |
C. Version control
| Question |
Answer |
| GeoServer config history in GeoVault? |
No — only current dataset_geoserver_publications + job log text |
| Dataset versioning for imports? |
Stub — single v1 row, geoserver_remote, no file |
| Rollback publish? |
No |
D. UI actions inventory
| Page |
Actions |
/geoserver |
Hub (federation workspace) |
/geoserver/connections |
CRUD connections |
/geoserver/catalog |
Browse remote layers → Import to GeoVault |
/geoserver/publish |
Upload new + publish, or publish existing dataset version |
/geoserver/workspaces, /geoserver/styles |
Admin |
| Dataset page |
GeoServer publications panel |
Missing: Publication history timeline, unpublish, sync catalog changes, dataset rollback.
QGIS PROJECTS
Runtime projects live in qgis-publish-service, not GeoVault DB.
A. CRUD + lifecycle
| Capability |
Status |
| Create |
P — created when publishing a raster dataset version |
| Import |
N |
| Upload |
N (direct); raster publish uploads raster into project |
| Delete |
P — external service |
| Rename/clone/archive |
N in GeoVault UI |
B. Data management
| Capability |
Status |
| Preview |
Y — viewer URL |
| Download |
Y — current + snapshot ZIPs |
| Publish |
Y — serves via QGIS Server |
| Permissions |
P — editable / project_visibility from remote service |
C. Version control
| Question |
Answer |
| Versioning? |
Yes — snapshots in qgis-publish-service |
| Rollback? |
Yes — POST /api/qgis-projects/{id}/versions/{version}/restore |
| Audit |
P — snapshot timestamps/sizes |
D. UI actions inventory
| Page |
Actions |
/projects |
List projects, open viewer, download current, history link |
/projects/{id}/history |
List snapshots, Restore, download snapshot |
Note: Vector datasets do not create QGIS projects through the same publish path (tileserver instead).
ESRI IMPORTS (current + expected)
Current behavior (implemented in Go: esri_import.go, UI: /esri)
| Capability |
Status |
| Browse service |
Y |
| Import layers |
Y — exports GeoJSON, loads PostGIS, creates vault dataset per layer |
| Background job |
Y — uses geoserver_jobs table with type esri_import |
| Vault file version |
Y — v1 GeoJSON on disk per layer |
| Re-run import |
P — can import again with new dataset names (prefix); no “refresh existing” |
Expected / planned behavior (from legacy esri_browser_page.php and product gaps)
| Expected capability |
Notes |
| Refresh/sync existing layers |
Legacy PHP suggests batch refresh; Go path creates new datasets |
| Import to vault only (no PostGIS) |
Current path always targets PostGIS |
| Map preview in vault |
Not exposed after import |
| Dataset lineage to ESRI service URL |
Stored in datasource_meta |
| Unified job UI |
ESRI jobs share table with GeoServer jobs but no dedicated history page |
| FeatureService incremental sync |
Not in Go importer |
3. Version-control audit
What currently has versioning
| Object |
Mechanism |
Rollback |
| File vector/raster datasets |
Integer versions + on-disk v{N}/ directories + DB/store catalog |
No (delete only) |
| Derived vector versions |
New version from filter/dissolve/clip; parent_version stored |
No |
| QGIS runtime projects |
Snapshots in qgis-publish-service |
Yes (restore) |
| GeoServer publish |
Job records only |
No |
| ESRI import |
One version per new dataset |
No |
What does not (but data changes)
| Object |
Gap |
| PostGIS live tables |
Edits are immediate; no GeoVault history |
| PostGIS schema |
No migration tracking |
| GeoServer remote layers |
Registry pointer; remote server is source of truth |
| Connection credentials |
Update in place |
| Dataset visibility |
DB column exists; no versioned policy history |
Major inconsistencies
- “Version” means different things: file snapshot (datasets) vs runtime snapshot (QGIS) vs single stub row (PostGIS, GeoServer remote).
- Rollback only for QGIS, not for vault dataset versions—users may expect “restore v2” on
/datasets/{name}.
- Raster vs vector publish split (QGIS vs tileserver) without a unified “Publish” abstraction in the UI.
- GeoServer catalog imports appear in vector/raster lists but behave like remote registrations (no download, transform, or version upload).
- S3 remote mode creates a version row but blocks preview, publish, download, transforms until a local import version exists.
- ESRI creates both PostGIS tables and vault versions but no integrated dataset workspace parity with file uploads.
What should probably have versioning (product view)
| Area |
Rationale |
| File datasets |
Already have it; add rollback/promote for parity with QGIS |
| PostGIS registered tables |
Optional snapshot or audit log before bulk edits |
| GeoServer publications |
History of publish/update/replace per dataset |
| Connection config |
Change log for compliance |
| ESRI imports |
Reproducible job definitions + sync checkpoints |
4. Consistency audit (cross-cutting)
Feature exists for one type but not another
| Feature |
Has |
Lacks |
| Multi-version files |
Vector, raster |
PostGIS, GeoServer remote |
| Map preview |
File vector/raster |
PostGIS, remote S3, ESRI (post-import) |
| Download |
File vector/raster, QGIS ZIP |
PostGIS, GeoServer remote, remote S3 |
| Geoprocessing |
Vector file |
Raster, PostGIS, remote |
| Publish button on version row |
Vector (tileserver), raster (QGIS) |
PostGIS, GeoServer remote, remote S3 |
| PostGIS import sidecar |
Upload, URL, S3 |
ESRI (direct to PostGIS in job), not on GeoServer catalog |
| URL import empty-state link |
Vectors list |
Rasters list |
| Rollback |
QGIS projects |
All vault datasets |
| Permission badges on cards |
Dataset lists |
QGIS projects (different model) |
Terminology / workflow fragmentation
| Issue |
Examples |
| Duplicate import concepts |
Upload vs URL vs S3 vs ESRI vs GeoServer catalog vs PostGIS register |
| “Publish” overload |
Tileserver, QGIS, GeoServer, GeoServer catalog import |
| “Dataset” overload |
File dataset, PostGIS registration, GeoServer remote registry |
| “Version” overload |
File vN, QGIS snapshot, stub v1 for remote |
| Hidden types |
geoserver_remote listed under vector/raster filters by type, not datasource |
UI fragmentation
- Hub pages:
/workspace, /geoserver, federation sidebar (shared) vs dedicated list pages.
- PostGIS entry:
/postgis redirects to /vectors?tab=postgis; legacy postgis.html may still exist.
- GeoServer has five subpages; dataset-level publications are only on dataset detail.
- QGIS projects separated from raster list despite being created from raster publish.
Orphan / discoverability risks
- GeoServer-imported datasets may be misleading on vector/raster lists (badge “GeoServer”) without file workflows.
- Remote S3 datasets allow “upload new version” in UI but capabilities remain disabled until local copy exists.
- ESRI import does not link cleanly to resulting datasets from a single job summary page.
data/publishes.json appears legacy vs current publish API responses.
5. Gap analysis (prioritized)
- Unified versioning semantics — Document and/or align “version” across file datasets, PostGIS, GeoServer remote, and QGIS; add dataset version rollback or clearly differentiate from QGIS snapshot restore.
- Publish surface parity — Single mental model for “Publish” (targets: tileserver, QGIS, GeoServer) with type-aware defaults; today vector vs raster buttons differ with no GeoServer on version row.
- Remote / stub datasets — GeoServer catalog and S3-remote: either hide from vector/raster file workflows or implement preview/download/publish rules consistently (including empty-state copy).
- PostGIS vs file split — Users can edit live data without vault history; consider audit trail or snapshot before destructive edits.
- Permissions / visibility UX —
visibility and federation ACLs exist; no UI to manage them on dataset create/edit.
MEDIUM PRIORITY
- Import path consolidation — One “Add data” flow with branches (file, URL, S3, PostGIS register, GeoServer, ESRI) sharing PostGIS/GeoServer sidecars where applicable.
- Rename + clone dataset — Common lifecycle ops missing for all registry types.
- Metadata/message editing —
message / version_label only at ingest; no post-hoc edit on Versions tab.
- Lineage UI —
parent_version and operation exist for transforms but are not shown.
- GeoServer publication history — Jobs table exists; no user-facing timeline per dataset.
- ESRI reproducibility — Re-run/sync same dataset; vault-only import option; job → dataset navigation.
- Discovery —
/api/discovery is name-only; no full-text, tags, or type filters.
LOW PRIORITY
- Archive/soft delete — Hard deletes only.
- Batch operations — No multi-select delete, publish, or import.
- Tags/categories — Not implemented.
- Raster empty state — Add URL/S3 import links to match vectors page.
- Legacy cleanup —
esri_browser_page.php, data/publishes.json, redirect-only /postgis.
- Export formats — Only raw file download (no Shapefile/GeoPackage export pipeline in API).
6. Quick reference — API routes by concern
| Concern |
Primary routes |
| Dataset versions |
GET/POST /datasets/{name}/upload, GET /datasets/{name}/versions, DELETE .../versions/{version}, geoprocessing .../filter|dissolve|clip |
| Publish |
POST /versions/{id}/publish, POST /api/geoserver/publish |
| Import |
POST /import-url, POST /import-s3, POST /api/esri/import, POST /api/geoserver/catalog/import |
| PostGIS |
/api/connections/*, /api/datasets/register, /api/table/*, /api/postgis-service/* |
| GeoServer admin |
/api/geoserver/connections, workspaces, styles, catalog, jobs |
| QGIS |
/api/qgis-projects, .../versions, .../restore, .../download |
| Auth |
Federation login + geovault_dataset + service-level ACL (authz.go) |
VECTOR DATASETS — current actions
- ✓ Upload (
/upload, dataset Versions tab)
- ✓ URL / S3 import
- ✓ Preview (map)
- ✓ Metadata (read-only)
- ✓ Publish to tileserver (version row)
- ✓ Publish to GeoServer (ingest / dedicated page)
- ✓ Import to PostGIS (ingest sidecar)
- ✓ Transform: filter, dissolve, clip
- ✓ Download
- ✓ Delete version (non-latest) / delete dataset
- ✗ Clone
- ✗ Rename
- ✗ Version rollback
- ✗ Edit metadata after ingest
- ✗ Archive
RASTER DATASETS — current actions
- ✓ Upload, URL, S3 import
- ✓ Preview
- ✓ Metadata (read-only)
- ✓ Publish to QGIS (version row)
- ✓ Publish to GeoServer (ingest / page)
- ✓ Download
- ✓ Delete version / dataset
- ✗ Transform menu
- ✗ Tileserver publish
- ✗ Clone / rename / rollback
- ✗ URL import linked from
/rasters empty state
POSTGIS — current actions
- ✓ Connection CRUD
- ✓ Browse tables, row preview
- ✓ Register as dataset
- ✓ Live table edit (insert/update/delete rows)
- ✓ Discover/adopt (registry)
- ✓ Vector file → PostGIS on ingest
- ✗ File versions / upload new version on PostGIS dataset
- ✗ Map preview
- ✗ Download / export
- ✗ Publish from dataset version row
- ✗ Schema history / rollback
GEOSERVER — current actions
- ✓ Connection CRUD, test
- ✓ Workspace/style management
- ✓ Catalog browse + import to vault
- ✓ Publish file datasets (jobs)
- ✓ Per-dataset publication list + WMS preview
- ✗ Config/version history in UI
- ✗ Rollback publication
QGIS PROJECTS — current actions
- ✓ List runtime projects
- ✓ Open viewer
- ✓ Download project ZIP
- ✓ Snapshot history
- ✓ Restore / rollback snapshot
- ✗ Create from vector publish
- ✗ Managed delete in GeoVault
ESRI — current actions
- ✓ Browse FeatureServer/MapServer
- ✓ Import selected layers (PostGIS + vault GeoJSON)
- ✓ Background jobs
- ✗ In-vault map preview post-import
- ✗ Refresh existing dataset
- ✗ Full parity with Upload/URL workflows
End of audit. No code changes were made.