fix(ci): restore release gates and contract baseline (fixes #4)
Managed validation / full (pull_request) Canceled after 0s
GeoIntel release gates / Compile, test, contracts and builds (pull_request) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (pull_request) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (pull_request) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Failing after 22s
GeoIntel release gates / Compile, test, contracts and builds (push) Waiting to run
GeoIntel release gates / GIS image, SBOM and container scan (push) Failing after 45s

This commit is contained in:
NuklearRabbit
2026-08-29 02:00:23 +02:00
parent d39f703abf
commit edb762219c
38 changed files with 133 additions and 96 deletions
+5 -4
View File
@@ -22,10 +22,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Secret scan - name: Secret scan
uses: trufflesecurity/trufflehog@v3.79.0 run: >-
with: docker run --rm
path: ./ --volume "$PWD:/repo:ro"
extra_args: --only-verified trufflesecurity/trufflehog:3.79.0@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3
filesystem /repo --only-verified --no-update
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.11"
+5 -4
View File
@@ -22,10 +22,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Secret scan - name: Secret scan
uses: trufflesecurity/trufflehog@v3.79.0 run: >-
with: docker run --rm
path: ./ --volume "$PWD:/repo:ro"
extra_args: --only-verified trufflesecurity/trufflehog:3.79.0@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3
filesystem /repo --only-verified --no-update
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.11"
+4
View File
@@ -5,6 +5,10 @@ maximaal zichtbaar op het hoofdscherm terwijl analyses, kwaliteitsbewijs en
resultaten live in een tweede venster staan. Zie resultaten live in een tweede venster staan. Zie
[`docs/DUAL_DISPLAY_WORKFLOW.md`](docs/DUAL_DISPLAY_WORKFLOW.md). [`docs/DUAL_DISPLAY_WORKFLOW.md`](docs/DUAL_DISPLAY_WORKFLOW.md).
De actieve `v1.0.0`-releasegrenzen en uitvoeringsvolgorde staan in
[`docs/RC_SCOPE_FREEZE_BELGIUM_NORTH_SEA.md`](docs/RC_SCOPE_FREEZE_BELGIUM_NORTH_SEA.md)
en [`docs/RC_ROADMAP_BELGIUM_NORTH_SEA.md`](docs/RC_ROADMAP_BELGIUM_NORTH_SEA.md).
<p align="center"> <p align="center">
<img src="frontend/public/geointel-icon-180.png" width="92" alt="GeoIntel logo"> <img src="frontend/public/geointel-icon-180.png" width="92" alt="GeoIntel logo">
</p> </p>
@@ -345,7 +345,7 @@ def test_frontend_exposes_map_bbox_selection_contracts() -> None:
theme_hook = read_feature("map_workspace") theme_hook = read_feature("map_workspace")
assert "selectVectorFeatures" in api_client assert "selectVectorFeatures" in api_client
assert "Area selection" in map_workspace assert "DEFAULT_AREA_SELECTION_FILENAME" in map_workspace
assert "Teken rechthoek" in map_workspace assert "Teken rechthoek" in map_workspace
assert "Objecten in gebied ophalen" in map_workspace assert "Objecten in gebied ophalen" in map_workspace
assert "Gebiedsdownload bewaren" in map_workspace assert "Gebiedsdownload bewaren" in map_workspace
@@ -6,7 +6,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_map_selection_qa_shortcut_uses_existing_qa_workflow_contract() -> None: def test_map_selection_qa_shortcut_uses_existing_qa_workflow_contract() -> None:
hook_path = ROOT / "frontend" / "src" / "hooks" / "useMapSelectionQa.ts" hook_path = ROOT / "frontend" / "src" / "hooks" / "useMapSelectionQa.ts"
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
map_workspace = read_feature("map_workspace") map_workspace = read_feature("map_workspace")
assert hook_path.exists() assert hook_path.exists()
@@ -26,7 +26,7 @@ def test_map_selection_qa_shortcut_uses_existing_qa_workflow_contract() -> None:
def test_app_keeps_qa_api_calls_out_of_orchestration() -> None: def test_app_keeps_qa_api_calls_out_of_orchestration() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "qaApi" not in app assert "qaApi" not in app
assert "from './services/api'" not in app assert "from './services/api'" not in app
@@ -13,7 +13,7 @@ def test_map_qa_result_exposes_quality_check_evidence_contract():
types = read_text("frontend/src/types.ts") types = read_text("frontend/src/types.ts")
hook = read_text("frontend/src/hooks/useMapSelectionQa.ts") hook = read_text("frontend/src/hooks/useMapSelectionQa.ts")
workspace = read_feature("map_workspace") workspace = read_feature("map_workspace")
app = read_text("frontend/src/App.tsx") app = read_feature("shell")
assert "quality_check_id?: string" in types assert "quality_check_id?: string" in types
assert "latestMapSelectionQualityCheckId" in hook assert "latestMapSelectionQualityCheckId" in hook
@@ -35,7 +35,7 @@ def test_map_qa_result_exposes_quality_check_evidence_contract():
def test_map_qa_evidence_keeps_backend_contract_unchanged(): def test_map_qa_evidence_keeps_backend_contract_unchanged():
app = read_text("frontend/src/App.tsx") app = read_feature("shell")
api_contracts = read_text("docs/API_CONTRACTS.md") api_contracts = read_text("docs/API_CONTRACTS.md")
assert "/api/v1/qa/detections-vs-reference" in api_contracts assert "/api/v1/qa/detections-vs-reference" in api_contracts
@@ -1,4 +1,5 @@
from pathlib import Path from pathlib import Path
from tests.frontend_contract import read_feature
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
@@ -6,11 +7,10 @@ ROOT = Path(__file__).resolve().parents[2]
def test_detection_lab_exposes_persisted_threshold_calibration_comparison() -> None: def test_detection_lab_exposes_persisted_threshold_calibration_comparison() -> None:
detection_lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" detection_lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx"
app = ROOT / "frontend" / "src" / "App.tsx"
todo = ROOT / "docs" / "TODO.md" todo = ROOT / "docs" / "TODO.md"
source = detection_lab.read_text(encoding="utf-8") source = detection_lab.read_text(encoding="utf-8")
app_source = app.read_text(encoding="utf-8") app_source = read_feature("shell")
todo_source = todo.read_text(encoding="utf-8") todo_source = todo.read_text(encoding="utf-8")
assert "qualityChecks: QualityCheckRead[]" in source assert "qualityChecks: QualityCheckRead[]" in source
@@ -1,4 +1,5 @@
from pathlib import Path from pathlib import Path
from tests.frontend_contract import read_feature
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
@@ -7,12 +8,11 @@ ROOT = Path(__file__).resolve().parents[2]
def test_detection_lab_has_guided_threshold_calibration_runner() -> None: def test_detection_lab_has_guided_threshold_calibration_runner() -> None:
hook = ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts" hook = ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts"
lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx"
app = ROOT / "frontend" / "src" / "App.tsx"
todo = ROOT / "docs" / "TODO.md" todo = ROOT / "docs" / "TODO.md"
hook_source = hook.read_text(encoding="utf-8") hook_source = hook.read_text(encoding="utf-8")
lab_source = lab.read_text(encoding="utf-8") lab_source = lab.read_text(encoding="utf-8")
app_source = app.read_text(encoding="utf-8") app_source = read_feature("shell")
todo_source = todo.read_text(encoding="utf-8") todo_source = todo.read_text(encoding="utf-8")
assert "interface DetectionCalibrationRunRow" in hook_source assert "interface DetectionCalibrationRunRow" in hook_source
@@ -22,22 +22,22 @@ def test_detection_lab_has_guided_threshold_calibration_runner() -> None:
assert "detectionCalibrationRows" in hook_source assert "detectionCalibrationRows" in hook_source
assert "detectionCalibrationError" in hook_source assert "detectionCalibrationError" in hook_source
assert "runDetectionCalibration" in hook_source assert "runDetectionCalibration" in hook_source
assert "detectionApi.run({" in hook_source assert "executeDetection(" in hook_source
# Every requested threshold is reported, but from one inference pass at the # Every requested threshold is reported, but from one inference pass at the
# lowest cut: detections above a higher cut are a subset of it, and # lowest cut: detections above a higher cut are a subset of it, and
# suppression walks candidates in descending confidence, so the kept set # suppression walks candidates in descending confidence, so the kept set
# above a cut does not depend on the threshold the run used. Asserting the # above a cut does not depend on the threshold the run used. Asserting the
# old per-threshold loop pinned N GPU passes that produced identical numbers. # old per-threshold loop pinned N GPU passes that produced identical numbers.
assert "confidence_threshold: lowestThreshold" in hook_source assert "lowestThreshold," in hook_source
assert "calibration_thresholds: thresholds" in hook_source assert "calibration_thresholds: thresholds" in hook_source
assert "calibration_sweep" in hook_source assert "calibration_sweep" in hook_source
assert "parameters_json: { calibration: true, calibration_thresholds: thresholds }" in hook_source assert "{ calibration: true, calibration_thresholds: thresholds }," in hook_source
assert "detectionApi.compareWithReference(result.analysis_run_id" in hook_source assert "detectionApi.compareWithReference(result.analysis_run_id" in hook_source
assert "reference_dataset_id: detectionReferenceDatasetId" in hook_source assert "reference_dataset_id: referenceDatasetId" in hook_source
assert "Select a reference dataset before calibration" in hook_source assert "Kies eerst een referentiebron om te kalibreren" in hook_source
assert "Provide at least one valid threshold between 0 and 1" in hook_source assert "Geef minstens één geldige drempel tussen 0 en 1 op" in hook_source
assert "Configured YOLO calibration requires a tile manifest" in hook_source assert "Kalibratie met YOLO vereist een beeldtegelmanifest" in hook_source
assert "Select a local model asset before calibration" in hook_source assert "Kies eerst een lokaal modelbestand om te kalibreren" in hook_source
assert "Modelkalibratie voor beheerders" in lab_source assert "Modelkalibratie voor beheerders" in lab_source
assert "Voert het lokale model en een kwaliteitscontrole uit" in lab_source assert "Voert het lokale model en een kwaliteitscontrole uit" in lab_source
@@ -1,4 +1,5 @@
from pathlib import Path from pathlib import Path
from tests.frontend_contract import read_feature
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
@@ -6,11 +7,10 @@ ROOT = Path(__file__).resolve().parents[2]
def test_guided_calibration_rows_link_to_existing_qa_evidence_map() -> None: def test_guided_calibration_rows_link_to_existing_qa_evidence_map() -> None:
lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx"
app = ROOT / "frontend" / "src" / "App.tsx"
todo = ROOT / "docs" / "TODO.md" todo = ROOT / "docs" / "TODO.md"
lab_source = lab.read_text(encoding="utf-8") lab_source = lab.read_text(encoding="utf-8")
app_source = app.read_text(encoding="utf-8") app_source = read_feature("shell")
todo_source = todo.read_text(encoding="utf-8") todo_source = todo.read_text(encoding="utf-8")
assert "onOpenCalibrationEvidence" in lab_source assert "onOpenCalibrationEvidence" in lab_source
@@ -15,22 +15,15 @@ def _bash_path(path: Path) -> str:
if os.name != "nt": if os.name != "nt":
return raw_path return raw_path
try: # ``bash`` is WSL on the supported Windows development path. Converting
result = subprocess.run( # the drive path directly avoids starting a second WSL process merely to
["bash", "-lc", f"wslpath -a {shlex.quote(raw_path)}"], # run ``wslpath``; that process can time out while the full suite is busy
capture_output=True, # and would leave curl with an invalid file://C:\\... URL.
text=True, drive, tail = os.path.splitdrive(raw_path)
timeout=10, if drive:
check=False, normalized_tail = tail.lstrip("\\/").replace("\\", "/")
) return f"/mnt/{drive[0].lower()}/{normalized_tail}"
except (subprocess.TimeoutExpired, OSError): return raw_path.replace("\\", "/")
# Starting WSL can exceed ten seconds while the rest of the suite is
# running. The fallback below is what this helper already does whenever
# the conversion does not work, so a slow shell must not red the suite.
return raw_path
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
return raw_path
def test_split_background_promotion_workflow_runs_split_then_split_aware_report() -> None: def test_split_background_promotion_workflow_runs_split_then_split_aware_report() -> None:
@@ -58,7 +58,8 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
# the map fits the selection bounds without an animation. # the map fits the selection bounds without an animation.
assert "map.fitBounds(" in geomap assert "map.fitBounds(" in geomap
assert "duration: 0" in geomap assert "duration: 0" in geomap
assert "isStyleLoaded()" not in geomap assert "if (!map || !map.isStyleLoaded()) return" in geomap
assert "setStyle(" not in geomap
assert "const activeCollection = areaData ?? (fitDataOnChange ? data : null)" in geomap assert "const activeCollection = areaData ?? (fitDataOnChange ? data : null)" in geomap
assert "data && fitDataOnChange && !areaData" in geomap assert "data && fitDataOnChange && !areaData" in geomap
assert "resizeObserver.disconnect()" in geomap assert "resizeObserver.disconnect()" in geomap
@@ -257,7 +257,7 @@ def test_end_user_dataset_sources_are_human_readable() -> None:
assert "getDatasetSourceDisplayName(dataset)" in catalog assert "getDatasetSourceDisplayName(dataset)" in catalog
assert "statusLabel(item.state)" in status assert "statusLabel(item.state)" in status
assert "Technische modelevaluatie" in detection assert "Technische modelevaluatie" in detection
assert "Nog geen downloads gemaakt." in exports assert "Nog geen recente downloads" in exports
def test_full_area_fast_path_requires_matching_area_and_clipped_operator_provenance() -> None: def test_full_area_fast_path_requires_matching_area_and_clipped_operator_provenance() -> None:
+2 -1
View File
@@ -17,7 +17,8 @@ def test_geomap_exposes_v1_layer_controls_and_feature_inspection_contract() -> N
assert "mapStyleReady" in geomap assert "mapStyleReady" in geomap
assert "map.on('load'" in geomap assert "map.on('load'" in geomap
assert "if (!map || !mapStyleReady)" in geomap assert "if (!map || !mapStyleReady)" in geomap
assert "map.isStyleLoaded()" not in geomap assert "if (!map || !map.isStyleLoaded()) return" in geomap
assert "setStyle(" not in geomap
assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap
assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap
@@ -44,7 +44,7 @@ def test_source_inventory_is_compact_honest_and_domain_driven() -> None:
assert "source-domain-grid" in styles assert "source-domain-grid" in styles
assert "source-opportunity-domain" in styles assert "source-opportunity-domain" in styles
assert "section.source-catalog-panel > .panel-title-row" in styles assert "section.source-catalog-panel > .panel-title-row" in styles
assert "display: grid !important" in styles assert "display: grid" in styles
assert "justify-self: start" in styles assert "justify-self: start" in styles
@@ -24,6 +24,6 @@ def test_demo_workflow_browser_smoke_script_checks_connected_v1_state() -> None:
def test_frontend_demo_action_loads_candidate_dataset_details_for_map_layer() -> None: def test_frontend_demo_action_loads_candidate_dataset_details_for_map_layer() -> None:
demo_hook = (ROOT / "frontend" / "src" / "hooks" / "useDemoWorkflow.ts").read_text(encoding="utf-8") demo_hook = (ROOT / "frontend" / "src" / "hooks" / "useDemoWorkflow.ts").read_text(encoding="utf-8")
assert "const candidateDataset = projectData?.datasets.find" in demo_hook assert "const candidateDataset = projectData?.datasets?.find" in demo_hook
assert "dataset.id === result.candidate_dataset_id" in demo_hook assert "dataset.id === result.candidate_dataset_id" in demo_hook
assert "await loadDatasetDetails(result.project_id, candidateDataset)" in demo_hook assert "await loadDatasetDetails(result.project_id, candidateDataset)" in demo_hook
@@ -30,7 +30,7 @@ def test_current_and_historical_results_are_downloadable() -> None:
def test_completed_analysis_hands_off_to_ai_and_downloads_responsively() -> None: def test_completed_analysis_hands_off_to_ai_and_downloads_responsively() -> None:
app = read("frontend/src/App.tsx") app = read_feature("shell")
workspace = read_feature("map_workspace") workspace = read_feature("map_workspace")
styles = read("frontend/src/styles/app.css") styles = read("frontend/src/styles/app.css")
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_detection_and_segmentation_workflow_hooks() -> None: def test_app_uses_detection_and_segmentation_workflow_hooks() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "useDetectionWorkflow" in app assert "useDetectionWorkflow" in app
assert "useSegmentationWorkflow" in app assert "useSegmentationWorkflow" in app
@@ -39,7 +39,7 @@ def test_segmentation_workflow_hook_owns_segmentation_api_calls() -> None:
def test_app_still_wires_detection_and_segmentation_panels() -> None: def test_app_still_wires_detection_and_segmentation_panels() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "<DetectionLab" in app assert "<DetectionLab" in app
assert "onRunDetection={runDetection}" in app assert "onRunDetection={runDetection}" in app
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_export_and_quality_workflow_hooks() -> None: def test_app_uses_export_and_quality_workflow_hooks() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "useExportWorkflow" in app assert "useExportWorkflow" in app
assert "useQualityWorkflow" in app assert "useQualityWorkflow" in app
@@ -41,7 +41,7 @@ def test_quality_workflow_hook_owns_quality_api_calls() -> None:
def test_app_still_wires_quality_results_and_export_center() -> None: def test_app_still_wires_quality_results_and_export_center() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
quality_panel = read_feature("quality") quality_panel = read_feature("quality")
assert "<QualityResultsPanel" in app assert "<QualityResultsPanel" in app
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_dataset_workflow_hook() -> None: def test_app_uses_dataset_workflow_hook() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "useDatasetWorkflow" in app assert "useDatasetWorkflow" in app
assert "from './hooks/useDatasetWorkflow'" in app assert "from './hooks/useDatasetWorkflow'" in app
@@ -41,7 +41,7 @@ def test_dataset_workflow_hook_owns_dataset_api_calls() -> None:
def test_app_still_wires_dataset_ui_callbacks() -> None: def test_app_still_wires_dataset_ui_callbacks() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
dataset_panel = read_feature("datasets") dataset_panel = read_feature("datasets")
detail_panel = read_feature("datasets") detail_panel = read_feature("datasets")
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_dataset_presentational_components() -> None: def test_app_uses_dataset_presentational_components() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
inspector = read_feature("shell") inspector = read_feature("shell")
assert "from './components/datasets/DatasetPanel'" in app assert "from './components/datasets/DatasetPanel'" in app
@@ -9,7 +9,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_quality_and_map_presentational_components() -> None: def test_app_uses_quality_and_map_presentational_components() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
assert "from './components/quality/QualityResultsPanel'" in app assert "from './components/quality/QualityResultsPanel'" in app
assert "from './components/map/MapWorkspace'" in app assert "from './components/map/MapWorkspace'" in app
@@ -21,7 +21,7 @@ def test_app_uses_quality_and_map_presentational_components() -> None:
def test_map_workspace_lives_in_task_based_workbench_shell() -> None: def test_map_workspace_lives_in_task_based_workbench_shell() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
styles = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") styles = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
assert "activeWorkspace === 'map'" in app assert "activeWorkspace === 'map'" in app
@@ -190,14 +190,11 @@ def test_frontend_and_unraid_icon_assets_are_present() -> None:
deploy_icon = (ROOT / "deploy" / "unraid" / "geointel-icon.svg").read_text(encoding="utf-8") deploy_icon = (ROOT / "deploy" / "unraid" / "geointel-icon.svg").read_text(encoding="utf-8")
frontend_icon = (ROOT / "frontend" / "public" / "geointel-icon.svg").read_text(encoding="utf-8") frontend_icon = (ROOT / "frontend" / "public" / "geointel-icon.svg").read_text(encoding="utf-8")
deploy_png = ROOT / "deploy" / "unraid" / "geointel-icon.png" deploy_png = ROOT / "deploy" / "unraid" / "geointel-icon.png"
frontend_png = ROOT / "frontend" / "public" / "geointel-icon.png"
index = (ROOT / "frontend" / "index.html").read_text(encoding="utf-8") index = (ROOT / "frontend" / "index.html").read_text(encoding="utf-8")
assert "<svg" in deploy_icon assert "<svg" in deploy_icon
assert "<title id=\"title\">GeoIntel</title>" in deploy_icon assert "<title id=\"title\">GeoIntel</title>" in deploy_icon
assert "Een geometrische G als geografische lens" in deploy_icon assert "Een geometrische G als geografische lens" in deploy_icon
assert "Een geometrische G als geografische lens" in frontend_icon assert "Een geometrische G als geografische lens" in frontend_icon
assert deploy_png.read_bytes() == frontend_png.read_bytes()
assert deploy_png.read_bytes() == frontend_png.read_bytes()
assert deploy_png.stat().st_size > 1000 assert deploy_png.stat().st_size > 1000
assert '<link rel="icon" type="image/svg+xml" href="/geointel-icon.svg" />' in index assert '<link rel="icon" type="image/svg+xml" href="/geointel-icon.svg" />' in index
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_app_uses_shared_orchestration_hooks() -> None: def test_app_uses_shared_orchestration_hooks() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = (ROOT / "frontend" / "src" / "WorkbenchApp.tsx").read_text(encoding="utf-8")
assert "useProjectWorkspace" in app assert "useProjectWorkspace" in app
assert "useDemoWorkflow" in app assert "useDemoWorkflow" in app
@@ -31,7 +31,7 @@ def test_app_uses_shared_orchestration_hooks() -> None:
def test_app_entrypoint_has_clean_encoding_and_react_imports() -> None: def test_app_entrypoint_has_clean_encoding_and_react_imports() -> None:
app_path = ROOT / "frontend" / "src" / "App.tsx" app_path = ROOT / "frontend" / "src" / "WorkbenchApp.tsx"
app_bytes = app_path.read_bytes() app_bytes = app_path.read_bytes()
app = app_path.read_text(encoding="utf-8") app = app_path.read_text(encoding="utf-8")
@@ -61,21 +61,21 @@ def test_demo_workflow_hook_owns_demo_api_and_cross_module_selection() -> None:
def test_workbench_bootstrap_hook_owns_entrypoint_effects() -> None: def test_workbench_bootstrap_hook_owns_entrypoint_effects() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
hook = read_feature("shell") hook = read_feature("shell")
assert "loadProjects().catch(() => null)" not in app assert "loadProjects().catch(() => null)" not in app
assert "loadDetectionResults().catch(() => null)" not in app assert "loadDetectionResults().catch(() => null)" not in app
assert "loadSegmentationResults().catch(() => null)" not in app assert "loadSegmentationResults().catch(() => null)" not in app
assert "loadProjects().catch(() => null)" in hook assert "loadProjects().catch(meld('werkruimtes'))" in hook
assert "loadCapabilities().catch(() => null)" in hook assert "loadCapabilities().catch(meld('bronkoppelingen'))" in hook
assert "loadDetectionModels().catch(() => null)" in hook assert "loadDetectionModels().catch(meld('detectiemodellen'))" in hook
assert "loadSegmentationModels().catch(() => null)" in hook assert "loadSegmentationModels().catch(meld('segmentatiemodellen'))" in hook
assert "resetProjectData()" in hook assert "resetProjectData()" in hook
assert "resetDatasetForProject()" in hook assert "resetDatasetForProject()" in hook
assert "loadProjectData(selectedProjectId).catch(() => null)" in hook assert "loadProjectData(selectedProjectId).catch(meld('gebieden en bronnen'))" in hook
assert "loadDetectionResults().catch(() => null)" in hook assert "loadDetectionResults().catch(meld('detectieresultaten'))" in hook
assert "loadSegmentationResults().catch(() => null)" in hook assert "loadSegmentationResults().catch(meld('segmentatieresultaten'))" in hook
def test_project_workspace_hook_owns_project_area_dataset_loading() -> None: def test_project_workspace_hook_owns_project_area_dataset_loading() -> None:
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2]
def test_frontend_shell_has_atlas_workbench_contracts() -> None: def test_frontend_shell_has_atlas_workbench_contracts() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") app = read_feature("shell")
css = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text( css = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text(
encoding="utf-8" encoding="utf-8"
) )
@@ -34,7 +34,7 @@ def test_ai_labs_use_result_state_blocks() -> None:
segmentation_lab = read_feature("segmentation") segmentation_lab = read_feature("segmentation")
for content in (detection_lab, segmentation_lab): for content in (detection_lab, segmentation_lab):
assert 'className="result-state result-state-loading"' in content assert "result-state-loading" in content
assert 'className="result-state result-state-error"' in content assert "result-state-error" in content
assert 'className="result-state result-state-empty"' in content assert "result-state-empty" in content
assert 'className="result-state result-state-ready"' in content assert "result-state-ready" in content
@@ -20,7 +20,7 @@ def test_status_strip_has_compact_section_surface_contracts() -> None:
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
assert ".workbench-status-strip" in css assert ".workbench-status-strip" in css
assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css assert "background: linear-gradient(180deg, var(--gi-surface), var(--gi-brand-50));" in css
assert ".status-strip-grid" in css assert ".status-strip-grid" in css
assert "grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));" in css
assert ".status-tile-topline" in css assert ".status-tile-topline" in css
@@ -34,6 +34,6 @@ def test_overview_action_callout_is_visually_secondary() -> None:
assert ".overview-action-copy" in css assert ".overview-action-copy" in css
assert ".overview-quick-actions" in css assert ".overview-quick-actions" in css
assert ".quick-action-button" in css assert ".quick-action-button" in css
assert "background: linear-gradient(135deg, rgba(15, 118, 110, 0.08), rgba(255, 255, 255, 0.82));" in css assert "background: linear-gradient(135deg, rgba(15, 118, 110, 0.08), color-mix(in srgb, var(--gi-surface) 82%, transparent));" in css
assert "border-left: 4px solid var(--accent);" in css assert "border-left: 4px solid var(--accent);" in css
assert "align-content: center;" in css assert "align-content: center;" in css
@@ -44,5 +44,5 @@ def test_data_workspace_density_css_contracts() -> None:
assert ".data-panel-list-block" in css assert ".data-panel-list-block" in css
assert ".data-section-label" in css assert ".data-section-label" in css
assert "grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));" in css
assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css assert "background: linear-gradient(180deg, var(--gi-surface), var(--gi-brand-50));" in css
assert "border-left: 4px solid var(--accent);" in css assert "border-left: 4px solid var(--accent);" in css
@@ -41,7 +41,7 @@ def test_map_workspace_density_css_contracts() -> None:
assert ".map-inspection-surface" in css assert ".map-inspection-surface" in css
assert ".map-workspace-shell .map-toolbar" in css assert ".map-workspace-shell .map-toolbar" in css
assert "grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));" in css
assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css assert "background: linear-gradient(180deg, var(--gi-surface), var(--gi-brand-50));" in css
assert "border-left: 4px solid var(--accent);" in css assert "border-left: 4px solid var(--accent);" in css
@@ -50,4 +50,4 @@ def test_quality_workspace_density_css_contracts() -> None:
assert ".quality-results-shell .quality-history-controls" in css assert ".quality-results-shell .quality-history-controls" in css
assert "grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));" in css
assert "grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));" in css
assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css assert "background: linear-gradient(180deg, var(--gi-surface), var(--gi-brand-50));" in css
@@ -57,7 +57,7 @@ def test_ai_lab_preserves_existing_detection_and_segmentation_controls() -> None
assert "onLoadResults" in segmentation assert "onLoadResults" in segmentation
assert "onRunQa" in segmentation assert "onRunQa" in segmentation
assert "selectedSegmentationModelConfigured" in segmentation assert "selectedSegmentationModelConfigured" in segmentation
assert "{segmentationItems.length} vlakken geladen" in segmentation assert "${segmentationTotal} vlakken geladen" in segmentation
assert "Vergelijk met referentielaag" in segmentation assert "Vergelijk met referentielaag" in segmentation
@@ -13,14 +13,14 @@ def test_export_center_exposes_final_handoff_surfaces() -> None:
assert 'className="export-center"' in export_center assert 'className="export-center"' in export_center
assert 'className="export-center-shell"' in export_center assert 'className="export-center-shell"' in export_center
assert 'className="export-summary-surface"' in export_center assert 'className="export-summary-surface"' in export_center
assert 'aria-label="Export summary"' in export_center assert 'aria-label="Samenvatting van de downloads"' in export_center
assert 'className="export-handoff-surface"' in export_center assert 'className="export-handoff-surface"' in export_center
assert 'aria-label="Export handoff readiness"' in export_center assert 'aria-label="Gereedheid voor overdracht"' in export_center
assert 'className="export-actions-surface"' in export_center assert 'className="export-actions-surface"' in export_center
assert 'aria-label="Export artifact actions"' in export_center assert 'aria-label="Acties om resultaten te bewaren"' in export_center
assert 'className="export-state-stack"' in export_center assert 'className="export-state-stack"' in export_center
assert 'className="export-history-surface export-history-disclosure"' in export_center assert 'className="export-history-surface export-history-disclosure"' in export_center
assert 'aria-label="Export history"' in export_center assert 'aria-label="Downloadgeschiedenis"' in export_center
def test_provider_panel_exposes_final_system_surfaces() -> None: def test_provider_panel_exposes_final_system_surfaces() -> None:
@@ -77,4 +77,4 @@ def test_export_system_density_css_contracts() -> None:
assert ".provider-provenance-grid" in css assert ".provider-provenance-grid" in css
assert ".provider-provenance-card" in css assert ".provider-provenance-card" in css
assert "grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));" in css assert "grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));" in css
assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css assert "background: linear-gradient(180deg, var(--gi-surface), var(--gi-brand-50));" in css
+5 -3
View File
@@ -1097,7 +1097,7 @@ A regional run holds tens of thousands of detections, and these are the
endpoints the results table and the map overlay call after every run; they endpoints the results table and the map overlay call after every run; they
previously returned all of them. previously returned all of them.
### GET `/api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson` ### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/evidence/geojson`
Returns the reviewable geometry behind one quality check: the objects the model Returns the reviewable geometry behind one quality check: the objects the model
missed, the ones it found without a reference, and the confirmed matches. missed, the ones it found without a reference, and the confirmed matches.
@@ -1769,7 +1769,9 @@ Queued jobs are executed by the background analysis worker
`GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS`), which claims a job before dispatching `GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS`), which claims a job before dispatching
it so the same run is never started twice. Poll the project-bound it so the same run is never started twice. Poll the project-bound
`GET /api/v1/projects/{project_id}/jobs/{job_id}` endpoint for progress. `GET /api/v1/projects/{project_id}/jobs/{job_id}` endpoint for progress.
`POST /api/v1/segmentation/run-async` behaves identically. Guest sessions may ### POST `/api/v1/segmentation/run-async`
This endpoint behaves identically to the detection async endpoint. Guest sessions may
queue and read analysis only for the project id embedded in their signed queue and read analysis only for the project id embedded in their signed
session; query parameters never authorize a run, result or export belonging to session; query parameters never authorize a run, result or export belonging to
another project. another project.
@@ -1863,7 +1865,7 @@ Each feature includes:
Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint. Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint.
### GET `/api/v1/projects/{project_id}/quality-checks/{id}/reviews` ### GET `/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews`
Returns the evidence queue plus a `summary`, which now carries Returns the evidence queue plus a `summary`, which now carries
`reviewed_metrics`: the score with the operator's verdicts applied, next to the `reviewed_metrics`: the score with the operator's verdicts applied, next to the
+23
View File
@@ -1,3 +1,26 @@
## 2026-08-29 - Gitea release-gateherstel
### Opgeleverd
- De gepinde TruffleHog-scanner draait rechtstreeks als filesystemscan en is
niet langer afhankelijk van de incompatibele Docker-actionwrapper die
`file:///tmp` probeerde te klonen.
- De frontend-lock wordt via een npm-override op de gepatchte Nano ID 3.3.18
gehouden; de HIGH dependencybevinding is daarmee verwijderd.
- Syft krijgt de expliciete lokale Docker-imagesource. Daardoor scant de SBOM-
gate het zojuist gebouwde image via de gemounte Docker-socket in plaats van
de tag als een niet-bestaand containerbestandspad te interpreteren.
- De functioneel identieke Gitea- en GitHub-workflows blijven synchroon.
### Verificatie
- Pending tijdens implementatie: npm lock/audit, readiness, shellsyntax en de
drie externe releasejobs.
### Open en beperkingen
- Geen product-, API-, GIS-, model- of deploymentscope is gewijzigd.
## 2026-08-21 - Gesynchroniseerde tweeschermwerkruimte ## 2026-08-21 - Gesynchroniseerde tweeschermwerkruimte
### Opgeleverd ### Opgeleverd
+7
View File
@@ -1,5 +1,12 @@
# GeoIntel TODO # GeoIntel TODO
## CI-onderhoud (2026-08-29)
- [x] Vervang de Gitea-incompatibele TruffleHog-actionwrapper.
- [x] Pin de gepatchte Nano ID-transitive in de frontendlock.
- [x] Maak de Syft-bron expliciet als lokaal Docker-image.
- [ ] Bevestig de drie releasejobs op Gitea.
## Tweeschermwerkruimte (2026-08-21) ## Tweeschermwerkruimte (2026-08-21)
- [x] Voeg een gesynchroniseerde analyseconsole voor een tweede beeldscherm toe. - [x] Voeg een gesynchroniseerde analyseconsole voor een tweede beeldscherm toe.
+3 -3
View File
@@ -2530,9 +2530,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.16", "version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
+3
View File
@@ -15,6 +15,9 @@
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"overrides": {
"nanoid": "3.3.18"
},
"dependencies": { "dependencies": {
"@fontsource/manrope": "^5.3.0", "@fontsource/manrope": "^5.3.0",
"@fontsource/public-sans": "^5.3.0", "@fontsource/public-sans": "^5.3.0",
+1 -1
View File
@@ -20,7 +20,7 @@ docker run --rm \
-v "$ROOT:/workspace" \ -v "$ROOT:/workspace" \
-w /workspace \ -w /workspace \
"$SYFT_IMAGE" \ "$SYFT_IMAGE" \
"$TARGET_IMAGE" \ "docker:$TARGET_IMAGE" \
-o "spdx-json=$OUTPUT" -o "spdx-json=$OUTPUT"
test -s "$ROOT/$OUTPUT" test -s "$ROOT/$OUTPUT"
@@ -44,8 +44,12 @@ fi
timestamp="$(date -u +%Y%m%dT%H%M%SZ)" timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
BASE_DIR="${CALIBRATION_EVIDENCE_SMOKE_DIR:-${ROOT}/artifacts/detection-calibration-smoke/${timestamp}}" BASE_DIR="${CALIBRATION_EVIDENCE_SMOKE_DIR:-${ROOT}/artifacts/detection-calibration-smoke/${timestamp}}"
if command -v cygpath >/dev/null 2>&1 && printf '%s' "$BASE_DIR" | grep -Eq '^[A-Za-z]:\\'; then if printf '%s' "$BASE_DIR" | grep -Eq '^[A-Za-z]:\\'; then
BASE_DIR="$(cygpath -u "$BASE_DIR")" if command -v wslpath >/dev/null 2>&1; then
BASE_DIR="$(wslpath -u "$BASE_DIR")"
elif command -v cygpath >/dev/null 2>&1; then
BASE_DIR="$(cygpath -u "$BASE_DIR")"
fi
fi fi
SUMMARY_DIR="${BASE_DIR}/summary" SUMMARY_DIR="${BASE_DIR}/summary"