Add backend API contract audit
This commit is contained in:
@@ -7,6 +7,13 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Sprint 48 Backend API contract audit (2026-06-17)
|
||||||
|
|
||||||
|
- Added `scripts/audit_api_contracts.py` to compare the active FastAPI route surface with `docs/API_CONTRACTS.md`.
|
||||||
|
- Added the API contract audit to the readiness gate so undocumented routes and stale documented routes fail release checks.
|
||||||
|
- Corrected API contract drift for area detail/update, vector stats, dataset content and future analysis/YOLO export placeholders.
|
||||||
|
- Added regression tests for the contract audit and readiness integration.
|
||||||
|
|
||||||
## Sprint 47 Workbench interaction smoke (2026-06-17)
|
## Sprint 47 Workbench interaction smoke (2026-06-17)
|
||||||
|
|
||||||
- Added stable `data-testid` anchors to the existing project, area, map, dataset, QA/QC and export controls for browser regression checks.
|
- Added stable `data-testid` anchors to the existing project, area, map, dataset, QA/QC and export controls for browser regression checks.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_readiness_gate_runs_api_contract_audit() -> None:
|
||||||
|
script = ROOT / "scripts" / "run_readiness_check.sh"
|
||||||
|
content = script.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "scripts/audit_api_contracts.py" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_contract_audit_checks_openapi_against_docs() -> None:
|
||||||
|
script = ROOT / "scripts" / "audit_api_contracts.py"
|
||||||
|
content = script.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "create_app" in content
|
||||||
|
assert "docs/API_CONTRACTS.md" in content
|
||||||
|
assert "Missing documented API route" in content
|
||||||
|
assert "Documented API route is not implemented" in content
|
||||||
|
assert "Allowed non-envelope endpoint is not implemented" in content
|
||||||
|
assert "/api/v1/exports/{export_id}/download" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_contract_docs_include_current_implemented_route_surface() -> None:
|
||||||
|
docs = (ROOT / "docs" / "API_CONTRACTS.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert 'GET `/api/v1/projects/{project_id}/areas/{area_id}`' in docs
|
||||||
|
assert 'PATCH `/api/v1/projects/{project_id}/areas/{area_id}`' in docs
|
||||||
|
assert 'GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`' in docs
|
||||||
|
assert 'GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/content`' in docs
|
||||||
|
assert 'POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`' not in docs
|
||||||
+23
-6
@@ -161,6 +161,16 @@ Backend responsibilities:
|
|||||||
- Calculate area in square meters using projected CRS.
|
- Calculate area in square meters using projected CRS.
|
||||||
- Store bbox.
|
- Store bbox.
|
||||||
|
|
||||||
|
### GET `/api/v1/projects/{project_id}/areas/{area_id}`
|
||||||
|
|
||||||
|
Returns one project area. The payload uses the same `AreaRead` shape as the
|
||||||
|
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.
|
||||||
|
|
||||||
## Datasets
|
## Datasets
|
||||||
|
|
||||||
### POST `/api/v1/projects/{project_id}/datasets/upload`
|
### POST `/api/v1/projects/{project_id}/datasets/upload`
|
||||||
@@ -339,7 +349,7 @@ Apply buffer distance to vector features.
|
|||||||
|
|
||||||
Intersect source vector dataset with another vector dataset.
|
Intersect source vector dataset with another vector dataset.
|
||||||
|
|
||||||
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`
|
||||||
|
|
||||||
Return vector stats (feature counts and geometry summary).
|
Return vector stats (feature counts and geometry summary).
|
||||||
|
|
||||||
@@ -347,6 +357,12 @@ Return vector stats (feature counts and geometry summary).
|
|||||||
|
|
||||||
Return vector bounds and feature count.
|
Return vector bounds and feature count.
|
||||||
|
|
||||||
|
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/content`
|
||||||
|
|
||||||
|
Returns stored vector dataset content through the canonical API envelope.
|
||||||
|
Vector content is returned as GeoJSON/JSON payload data. Raster content is not
|
||||||
|
served through this endpoint.
|
||||||
|
|
||||||
## Jobs
|
## Jobs
|
||||||
|
|
||||||
### POST `/api/v1/projects/{project_id}/jobs`
|
### POST `/api/v1/projects/{project_id}/jobs`
|
||||||
@@ -670,11 +686,12 @@ Response persists a `quality_check` and `metrics` rows through the existing QA/Q
|
|||||||
|
|
||||||
If the reference dataset has no persisted vector features, the endpoint returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
If the reference dataset has no persisted vector features, the endpoint returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
|
||||||
|
|
||||||
### POST `/api/v1/analysis/building-stats`
|
#### Future analysis route: `/api/v1/analysis/building-stats`
|
||||||
|
|
||||||
Input: area + vector building layer.
|
Not implemented in the active API surface. Future input is expected to combine
|
||||||
|
an area with a vector building layer.
|
||||||
|
|
||||||
### POST `/api/v1/analysis/object-detection`
|
#### Future analysis route: `/api/v1/analysis/object-detection`
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
||||||
@@ -693,7 +710,7 @@ Request:
|
|||||||
|
|
||||||
Response: `AnalysisRunRead`.
|
Response: `AnalysisRunRead`.
|
||||||
|
|
||||||
### POST `/api/v1/analysis/segmentation`
|
#### Future analysis route: `/api/v1/analysis/segmentation`
|
||||||
|
|
||||||
Same pattern as object detection, but output includes masks and polygonized geometries.
|
Same pattern as object detection, but output includes masks and polygonized geometries.
|
||||||
|
|
||||||
@@ -1027,7 +1044,7 @@ intentionally does not use the JSON envelope because it is a browser/file-downlo
|
|||||||
path; callers that need canonical API JSON should use `/content` for JSON/GeoJSON
|
path; callers that need canonical API JSON should use `/content` for JSON/GeoJSON
|
||||||
artifacts.
|
artifacts.
|
||||||
|
|
||||||
### POST `/api/v1/exports/yolo`
|
#### Future export route: `/api/v1/exports/yolo`
|
||||||
|
|
||||||
Export annotations/detections to YOLO format.
|
Export annotations/detections to YOLO format.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,38 @@
|
|||||||
|
## Sprint 48 Backend API contract audit (2026-06-17)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Added `scripts/audit_api_contracts.py` to import the FastAPI app, enumerate the implemented `GET`/`POST`/`PATCH`/`DELETE` route surface and compare it with active `### METHOD route` headings in `docs/API_CONTRACTS.md`.
|
||||||
|
- Added the API contract audit to `scripts/run_readiness_check.sh`.
|
||||||
|
- Corrected contract drift in `docs/API_CONTRACTS.md`:
|
||||||
|
- documented `GET/PATCH /api/v1/projects/{project_id}/areas/{area_id}`;
|
||||||
|
- corrected vector stats from `POST` to implemented `GET`;
|
||||||
|
- documented `GET /api/v1/projects/{project_id}/datasets/{dataset_id}/content`;
|
||||||
|
- changed non-implemented building-stats, legacy analysis object-detection/segmentation and YOLO export entries from active route headings to future-route notes.
|
||||||
|
- Added `backend/tests/test_sprint48_api_contract_audit.py`.
|
||||||
|
- Updated `scripts/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- RED: `cd backend && python -m pytest tests/test_sprint48_api_contract_audit.py -q` failed before implementation because the audit script, readiness integration and route docs were missing.
|
||||||
|
- RED: `python scripts/audit_api_contracts.py` reported missing docs for 4 implemented routes and 5 stale documented routes.
|
||||||
|
- `python scripts/audit_api_contracts.py` passed: 76 implemented routes matched docs and 2 explicit non-envelope endpoints were tracked.
|
||||||
|
- `cd backend && python -m pytest tests/test_sprint48_api_contract_audit.py tests/test_readiness_gate.py -q` passed: 12 tests.
|
||||||
|
- `python -m py_compile scripts/audit_api_contracts.py` passed.
|
||||||
|
- `python -m compileall backend/app` passed.
|
||||||
|
- `cd backend && python -m pytest -W error::DeprecationWarning` passed: 201 tests.
|
||||||
|
- `bash scripts/run_readiness_check.sh` passed and included `API contract audit OK`.
|
||||||
|
- `cd frontend && npm run typecheck` passed.
|
||||||
|
- `cd frontend && npm run build` passed.
|
||||||
|
- `cd backend && python -m alembic heads` passed: `202606120900 (head)`.
|
||||||
|
- `cd backend && python -m alembic upgrade head --sql` passed.
|
||||||
|
- `bash -n scripts/live_migration_smoke.sh` passed.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- This pass audits route documentation presence, implemented/stale route drift and explicit non-envelope exceptions. It does not yet exercise every error path response body at runtime.
|
||||||
|
- No API behavior, migrations, provider fetching, AI behavior or product capabilities changed.
|
||||||
|
|
||||||
|
Next recommended pass:
|
||||||
|
- Add an error-envelope runtime audit for representative invalid/missing-resource paths across projects, datasets, providers, detection, segmentation, QA and exports.
|
||||||
|
|
||||||
## Sprint 47 Workbench interaction smoke (2026-06-17)
|
## Sprint 47 Workbench interaction smoke (2026-06-17)
|
||||||
|
|
||||||
Changed:
|
Changed:
|
||||||
|
|||||||
+2
-1
@@ -43,6 +43,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Dry-run-first demo export artifact cleanup tooling.
|
- [x] Dry-run-first demo export artifact cleanup tooling.
|
||||||
- [x] Browser-facing default workbench state smoke for the offline demo project.
|
- [x] Browser-facing default workbench state smoke for the offline demo project.
|
||||||
- [x] Browser-facing workbench interaction backing-state smoke and stable UI test anchors.
|
- [x] Browser-facing workbench interaction backing-state smoke and stable UI test anchors.
|
||||||
|
- [x] Backend API contract audit comparing implemented FastAPI routes with `docs/API_CONTRACTS.md`.
|
||||||
- [x] Live Docker/PostGIS validation on Tower/Unraid.
|
- [x] Live Docker/PostGIS validation on Tower/Unraid.
|
||||||
- [x] Real YOLO compatibility smoke with optional AI extras and local model file.
|
- [x] Real YOLO compatibility smoke with optional AI extras and local model file.
|
||||||
- [x] Detection and segmentation workflow hook extraction beyond Sprint 10.
|
- [x] Detection and segmentation workflow hook extraction beyond Sprint 10.
|
||||||
@@ -58,7 +59,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Demo workflow orchestration hook decomposition.
|
- [x] Demo workflow orchestration hook decomposition.
|
||||||
- [x] Final `App.tsx` import/encoding cleanup and size audit.
|
- [x] Final `App.tsx` import/encoding cleanup and size audit.
|
||||||
- [x] Optional final bootstrap-effect extraction.
|
- [x] Optional final bootstrap-effect extraction.
|
||||||
- [ ] Decide next V1 stabilization focus: browser screenshot artifact automation, backend service contract audit, or golden dataset expansion.
|
- [ ] Decide next V1 stabilization focus: browser screenshot artifact automation, backend error-envelope audit, or golden dataset expansion.
|
||||||
|
|
||||||
## Sprint 8 status
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ Setup-, import-, demo- en maintenance-scripts voor GeoIntel.
|
|||||||
|
|
||||||
## Runtime verification
|
## Runtime verification
|
||||||
|
|
||||||
|
Audit the active backend route surface against `docs/API_CONTRACTS.md`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/audit_api_contracts.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The audit imports the FastAPI app, compares implemented `GET`/`POST`/`PATCH`/
|
||||||
|
`DELETE` routes with active API contract headings and tracks the explicit
|
||||||
|
non-envelope exceptions (`/health` and export downloads). It fails when a route
|
||||||
|
exists without docs or when docs claim an endpoint that is not implemented.
|
||||||
|
|
||||||
Verify the browser-facing Docker/LAN runtime:
|
Verify the browser-facing Docker/LAN runtime:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
BACKEND = ROOT / "backend"
|
||||||
|
DOCS = ROOT / "docs" / "API_CONTRACTS.md" # docs/API_CONTRACTS.md
|
||||||
|
|
||||||
|
ALLOWED_NON_ENVELOPE_ENDPOINTS = {
|
||||||
|
("GET", "/health"),
|
||||||
|
("GET", "/api/v1/exports/{export_id}/download"),
|
||||||
|
}
|
||||||
|
|
||||||
|
IGNORED_OPENAPI_PATHS = {
|
||||||
|
"/openapi.json",
|
||||||
|
"/docs",
|
||||||
|
"/docs/oauth2-redirect",
|
||||||
|
"/redoc",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_app_routes() -> set[tuple[str, str]]:
|
||||||
|
sys.path.insert(0, str(BACKEND))
|
||||||
|
from app.main import create_app
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
routes: set[tuple[str, str]] = set()
|
||||||
|
for route in app.routes:
|
||||||
|
path = getattr(route, "path", None)
|
||||||
|
methods = getattr(route, "methods", set())
|
||||||
|
if not path or path in IGNORED_OPENAPI_PATHS:
|
||||||
|
continue
|
||||||
|
for method in sorted(methods - {"HEAD", "OPTIONS"}):
|
||||||
|
routes.add((method, path))
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
|
def _load_documented_routes() -> set[tuple[str, str]]:
|
||||||
|
docs = DOCS.read_text(encoding="utf-8")
|
||||||
|
return set(re.findall(r"###\s+(GET|POST|PATCH|DELETE)\s+`([^`]+)`", docs))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
implemented_routes = _load_app_routes()
|
||||||
|
documented_routes = _load_documented_routes()
|
||||||
|
|
||||||
|
missing_docs = sorted(implemented_routes - documented_routes)
|
||||||
|
stale_docs = sorted(documented_routes - implemented_routes)
|
||||||
|
missing_non_envelope = sorted(ALLOWED_NON_ENVELOPE_ENDPOINTS - implemented_routes)
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
for method, path in missing_docs:
|
||||||
|
errors.append(f"Missing documented API route: {method} {path}")
|
||||||
|
for method, path in stale_docs:
|
||||||
|
errors.append(f"Documented API route is not implemented: {method} {path}")
|
||||||
|
for method, path in missing_non_envelope:
|
||||||
|
errors.append(f"Allowed non-envelope endpoint is not implemented: {method} {path}")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise SystemExit("\n".join(errors))
|
||||||
|
|
||||||
|
print(
|
||||||
|
"API contract audit OK: "
|
||||||
|
f"{len(implemented_routes)} implemented routes match docs; "
|
||||||
|
f"{len(ALLOWED_NON_ENVELOPE_ENDPOINTS)} explicit non-envelope endpoints tracked."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -32,6 +32,7 @@ bash scripts/check_repo_structure.sh
|
|||||||
${PYTHON_BIN} scripts/smoke_docs.py
|
${PYTHON_BIN} scripts/smoke_docs.py
|
||||||
${PYTHON_BIN} scripts/validate_fixtures.py
|
${PYTHON_BIN} scripts/validate_fixtures.py
|
||||||
${PYTHON_BIN} scripts/smoke_contracts.py
|
${PYTHON_BIN} scripts/smoke_contracts.py
|
||||||
|
${PYTHON_BIN} scripts/audit_api_contracts.py
|
||||||
${PYTHON_BIN} scripts/preimplementation_audit.py
|
${PYTHON_BIN} scripts/preimplementation_audit.py
|
||||||
${PYTHON_BIN} scripts/validate_m13_codex_assets.py
|
${PYTHON_BIN} scripts/validate_m13_codex_assets.py
|
||||||
${PYTHON_BIN} scripts/validate_m14_launch_assets.py
|
${PYTHON_BIN} scripts/validate_m14_launch_assets.py
|
||||||
|
|||||||
Reference in New Issue
Block a user