diff --git a/.gitea/workflows/release-gates.yml b/.gitea/workflows/release-gates.yml index 91232c61..1edfdec3 100644 --- a/.gitea/workflows/release-gates.yml +++ b/.gitea/workflows/release-gates.yml @@ -2,8 +2,6 @@ name: GeoIntel release gates on: push: - branches: [main, develop, "codex/**", "build/**"] - pull_request: branches: [main, develop] workflow_dispatch: @@ -22,10 +20,11 @@ jobs: steps: - uses: actions/checkout@v4 - name: Secret scan - uses: trufflesecurity/trufflehog@v3.79.0 - with: - path: ./ - extra_args: --only-verified + run: >- + docker run --rm + --volume "$PWD:/repo:ro" + trufflesecurity/trufflehog:3.79.0@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3 + filesystem /repo --only-verified --no-update - uses: actions/setup-python@v5 with: python-version: "3.11" diff --git a/.github/workflows/release-gates.yml b/.github/workflows/release-gates.yml index c693d91c..7887a2b4 100644 --- a/.github/workflows/release-gates.yml +++ b/.github/workflows/release-gates.yml @@ -22,10 +22,11 @@ jobs: steps: - uses: actions/checkout@v4 - name: Secret scan - uses: trufflesecurity/trufflehog@v3.79.0 - with: - path: ./ - extra_args: --only-verified + run: >- + docker run --rm + --volume "$PWD:/repo:ro" + trufflesecurity/trufflehog:3.79.0@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3 + filesystem /repo --only-verified --no-update - uses: actions/setup-python@v5 with: python-version: "3.11" diff --git a/README.md b/README.md index 2e5a78f2..786b3b3d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ maximaal zichtbaar op het hoofdscherm terwijl analyses, kwaliteitsbewijs en resultaten live in een tweede venster staan. Zie [`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). +

GeoIntel logo

diff --git a/backend/tests/test_sprint106_map_bbox_extract.py b/backend/tests/test_sprint106_map_bbox_extract.py index 691c1652..5abd4e33 100644 --- a/backend/tests/test_sprint106_map_bbox_extract.py +++ b/backend/tests/test_sprint106_map_bbox_extract.py @@ -345,7 +345,7 @@ def test_frontend_exposes_map_bbox_selection_contracts() -> None: theme_hook = read_feature("map_workspace") 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 "Objecten in gebied ophalen" in map_workspace assert "Gebiedsdownload bewaren" in map_workspace diff --git a/backend/tests/test_sprint109_map_selection_qa_shortcut.py b/backend/tests/test_sprint109_map_selection_qa_shortcut.py index 7cd18e80..f589b92e 100644 --- a/backend/tests/test_sprint109_map_selection_qa_shortcut.py +++ b/backend/tests/test_sprint109_map_selection_qa_shortcut.py @@ -6,7 +6,7 @@ ROOT = Path(__file__).resolve().parents[2] def test_map_selection_qa_shortcut_uses_existing_qa_workflow_contract() -> None: 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") 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: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") assert "qaApi" not in app assert "from './services/api'" not in app diff --git a/backend/tests/test_sprint110_map_qa_evidence_drilldown.py b/backend/tests/test_sprint110_map_qa_evidence_drilldown.py index afc28819..7092fbbe 100644 --- a/backend/tests/test_sprint110_map_qa_evidence_drilldown.py +++ b/backend/tests/test_sprint110_map_qa_evidence_drilldown.py @@ -13,7 +13,7 @@ def test_map_qa_result_exposes_quality_check_evidence_contract(): types = read_text("frontend/src/types.ts") hook = read_text("frontend/src/hooks/useMapSelectionQa.ts") workspace = read_feature("map_workspace") - app = read_text("frontend/src/App.tsx") + app = read_feature("shell") assert "quality_check_id?: string" in types 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(): - app = read_text("frontend/src/App.tsx") + app = read_feature("shell") api_contracts = read_text("docs/API_CONTRACTS.md") assert "/api/v1/qa/detections-vs-reference" in api_contracts diff --git a/backend/tests/test_sprint133_detection_threshold_calibration_ux.py b/backend/tests/test_sprint133_detection_threshold_calibration_ux.py index 68b0ea5b..b21ee32e 100644 --- a/backend/tests/test_sprint133_detection_threshold_calibration_ux.py +++ b/backend/tests/test_sprint133_detection_threshold_calibration_ux.py @@ -1,4 +1,5 @@ from pathlib import Path +from tests.frontend_contract import read_feature 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: detection_lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" - app = ROOT / "frontend" / "src" / "App.tsx" todo = ROOT / "docs" / "TODO.md" 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") assert "qualityChecks: QualityCheckRead[]" in source diff --git a/backend/tests/test_sprint134_guided_detection_calibration_runner.py b/backend/tests/test_sprint134_guided_detection_calibration_runner.py index 190804e2..9097290a 100644 --- a/backend/tests/test_sprint134_guided_detection_calibration_runner.py +++ b/backend/tests/test_sprint134_guided_detection_calibration_runner.py @@ -1,4 +1,5 @@ from pathlib import Path +from tests.frontend_contract import read_feature 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: hook = ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts" lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" - app = ROOT / "frontend" / "src" / "App.tsx" todo = ROOT / "docs" / "TODO.md" hook_source = hook.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") 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 "detectionCalibrationError" 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 # lowest cut: detections above a higher cut are a subset of it, and # suppression walks candidates in descending confidence, so the kept set # 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. - assert "confidence_threshold: lowestThreshold" in hook_source + assert "lowestThreshold," in hook_source assert "calibration_thresholds: thresholds" 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 "reference_dataset_id: detectionReferenceDatasetId" in hook_source - assert "Select a reference dataset before calibration" in hook_source - assert "Provide at least one valid threshold between 0 and 1" in hook_source - assert "Configured YOLO calibration requires a tile manifest" in hook_source - assert "Select a local model asset before calibration" in hook_source + assert "reference_dataset_id: referenceDatasetId" in hook_source + assert "Kies eerst een referentiebron om te kalibreren" in hook_source + assert "Geef minstens één geldige drempel tussen 0 en 1 op" in hook_source + assert "Kalibratie met YOLO vereist een beeldtegelmanifest" in hook_source + assert "Kies eerst een lokaal modelbestand om te kalibreren" in hook_source assert "Modelkalibratie voor beheerders" in lab_source assert "Voert het lokale model en een kwaliteitscontrole uit" in lab_source diff --git a/backend/tests/test_sprint135_calibration_evidence_handoff.py b/backend/tests/test_sprint135_calibration_evidence_handoff.py index 98e3251e..fdcb0fa8 100644 --- a/backend/tests/test_sprint135_calibration_evidence_handoff.py +++ b/backend/tests/test_sprint135_calibration_evidence_handoff.py @@ -1,4 +1,5 @@ from pathlib import Path +from tests.frontend_contract import read_feature 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: lab = ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx" - app = ROOT / "frontend" / "src" / "App.tsx" todo = ROOT / "docs" / "TODO.md" 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") assert "onOpenCalibrationEvidence" in lab_source diff --git a/backend/tests/test_sprint159_split_promotion_workflow.py b/backend/tests/test_sprint159_split_promotion_workflow.py index 1d7b3d4a..4d07c963 100644 --- a/backend/tests/test_sprint159_split_promotion_workflow.py +++ b/backend/tests/test_sprint159_split_promotion_workflow.py @@ -15,22 +15,15 @@ def _bash_path(path: Path) -> str: if os.name != "nt": return raw_path - try: - result = subprocess.run( - ["bash", "-lc", f"wslpath -a {shlex.quote(raw_path)}"], - capture_output=True, - text=True, - timeout=10, - check=False, - ) - except (subprocess.TimeoutExpired, OSError): - # 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 + # ``bash`` is WSL on the supported Windows development path. Converting + # the drive path directly avoids starting a second WSL process merely to + # run ``wslpath``; that process can time out while the full suite is busy + # and would leave curl with an invalid file://C:\\... URL. + drive, tail = os.path.splitdrive(raw_path) + if drive: + normalized_tail = tail.lstrip("\\/").replace("\\", "/") + return f"/mnt/{drive[0].lower()}/{normalized_tail}" + return raw_path.replace("\\", "/") def test_split_background_promotion_workflow_runs_split_then_split_aware_report() -> None: diff --git a/backend/tests/test_sprint186_map_first_geographic_explorer.py b/backend/tests/test_sprint186_map_first_geographic_explorer.py index f6524800..c9e67718 100644 --- a/backend/tests/test_sprint186_map_first_geographic_explorer.py +++ b/backend/tests/test_sprint186_map_first_geographic_explorer.py @@ -58,7 +58,8 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None: # the map fits the selection bounds without an animation. assert "map.fitBounds(" 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 "data && fitDataOnChange && !areaData" in geomap assert "resizeObserver.disconnect()" in geomap diff --git a/backend/tests/test_sprint194_regional_timeseries.py b/backend/tests/test_sprint194_regional_timeseries.py index 7bd4a21e..3083a3ee 100644 --- a/backend/tests/test_sprint194_regional_timeseries.py +++ b/backend/tests/test_sprint194_regional_timeseries.py @@ -257,7 +257,7 @@ def test_end_user_dataset_sources_are_human_readable() -> None: assert "getDatasetSourceDisplayName(dataset)" in catalog assert "statusLabel(item.state)" in status 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: diff --git a/backend/tests/test_sprint19_map_workbench.py b/backend/tests/test_sprint19_map_workbench.py index 3606d702..1c3cad91 100644 --- a/backend/tests/test_sprint19_map_workbench.py +++ b/backend/tests/test_sprint19_map_workbench.py @@ -17,7 +17,8 @@ def test_geomap_exposes_v1_layer_controls_and_feature_inspection_contract() -> N assert "mapStyleReady" in geomap assert "map.on('load'" 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 "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap diff --git a/backend/tests/test_sprint212_platform_source_portfolio.py b/backend/tests/test_sprint212_platform_source_portfolio.py index 7be1beb3..0542bca4 100644 --- a/backend/tests/test_sprint212_platform_source_portfolio.py +++ b/backend/tests/test_sprint212_platform_source_portfolio.py @@ -44,7 +44,7 @@ def test_source_inventory_is_compact_honest_and_domain_driven() -> None: assert "source-domain-grid" in styles assert "source-opportunity-domain" 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 diff --git a/backend/tests/test_sprint21_demo_workflow_smoke.py b/backend/tests/test_sprint21_demo_workflow_smoke.py index 9d75946c..5b58b309 100644 --- a/backend/tests/test_sprint21_demo_workflow_smoke.py +++ b/backend/tests/test_sprint21_demo_workflow_smoke.py @@ -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: 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 "await loadDatasetDetails(result.project_id, candidateDataset)" in demo_hook diff --git a/backend/tests/test_sprint232_v1_completion_flow.py b/backend/tests/test_sprint232_v1_completion_flow.py index ca3d679d..0922d480 100644 --- a/backend/tests/test_sprint232_v1_completion_flow.py +++ b/backend/tests/test_sprint232_v1_completion_flow.py @@ -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: - app = read("frontend/src/App.tsx") + app = read_feature("shell") workspace = read_feature("map_workspace") styles = read("frontend/src/styles/app.css") diff --git a/backend/tests/test_sprint26_frontend_workflow_hooks.py b/backend/tests/test_sprint26_frontend_workflow_hooks.py index 2e47b9d5..451a6718 100644 --- a/backend/tests/test_sprint26_frontend_workflow_hooks.py +++ b/backend/tests/test_sprint26_frontend_workflow_hooks.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2] 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 "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: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") assert " None: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") assert "useExportWorkflow" 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: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") quality_panel = read_feature("quality") assert " None: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") assert "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: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") dataset_panel = read_feature("datasets") detail_panel = read_feature("datasets") diff --git a/backend/tests/test_sprint29_dataset_components.py b/backend/tests/test_sprint29_dataset_components.py index 785974b0..472a18dd 100644 --- a/backend/tests/test_sprint29_dataset_components.py +++ b/backend/tests/test_sprint29_dataset_components.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2] 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") assert "from './components/datasets/DatasetPanel'" in app diff --git a/backend/tests/test_sprint30_workbench_components.py b/backend/tests/test_sprint30_workbench_components.py index 35e5edef..ec5c86cc 100644 --- a/backend/tests/test_sprint30_workbench_components.py +++ b/backend/tests/test_sprint30_workbench_components.py @@ -9,7 +9,7 @@ ROOT = Path(__file__).resolve().parents[2] 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/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: - 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") assert "activeWorkspace === 'map'" in app diff --git a/backend/tests/test_sprint31_unraid_template.py b/backend/tests/test_sprint31_unraid_template.py index 86e8088a..ff7a78d4 100644 --- a/backend/tests/test_sprint31_unraid_template.py +++ b/backend/tests/test_sprint31_unraid_template.py @@ -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") frontend_icon = (ROOT / "frontend" / "public" / "geointel-icon.svg").read_text(encoding="utf-8") 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") assert "GeoIntel" in deploy_icon assert "Een geometrische G als geografische lens" in deploy_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 '' in index diff --git a/backend/tests/test_sprint39_frontend_orchestration_hooks.py b/backend/tests/test_sprint39_frontend_orchestration_hooks.py index f1b4a9cd..28177417 100644 --- a/backend/tests/test_sprint39_frontend_orchestration_hooks.py +++ b/backend/tests/test_sprint39_frontend_orchestration_hooks.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2] 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 "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: - app_path = ROOT / "frontend" / "src" / "App.tsx" + app_path = ROOT / "frontend" / "src" / "WorkbenchApp.tsx" app_bytes = app_path.read_bytes() 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: - app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + app = read_feature("shell") hook = read_feature("shell") assert "loadProjects().catch(() => null)" not in app assert "loadDetectionResults().catch(() => null)" not in app assert "loadSegmentationResults().catch(() => null)" not in app - assert "loadProjects().catch(() => null)" in hook - assert "loadCapabilities().catch(() => null)" in hook - assert "loadDetectionModels().catch(() => null)" in hook - assert "loadSegmentationModels().catch(() => null)" in hook + assert "loadProjects().catch(meld('werkruimtes'))" in hook + assert "loadCapabilities().catch(meld('bronkoppelingen'))" in hook + assert "loadDetectionModels().catch(meld('detectiemodellen'))" in hook + assert "loadSegmentationModels().catch(meld('segmentatiemodellen'))" in hook assert "resetProjectData()" in hook assert "resetDatasetForProject()" in hook - assert "loadProjectData(selectedProjectId).catch(() => null)" in hook - assert "loadDetectionResults().catch(() => null)" in hook - assert "loadSegmentationResults().catch(() => null)" in hook + assert "loadProjectData(selectedProjectId).catch(meld('gebieden en bronnen'))" in hook + assert "loadDetectionResults().catch(meld('detectieresultaten'))" in hook + assert "loadSegmentationResults().catch(meld('segmentatieresultaten'))" in hook def test_project_workspace_hook_owns_project_area_dataset_loading() -> None: diff --git a/backend/tests/test_sprint62_frontend_visual_polish.py b/backend/tests/test_sprint62_frontend_visual_polish.py index 4d844a88..efd0aeeb 100644 --- a/backend/tests/test_sprint62_frontend_visual_polish.py +++ b/backend/tests/test_sprint62_frontend_visual_polish.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2] 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( encoding="utf-8" ) diff --git a/backend/tests/test_sprint81_result_state_polish.py b/backend/tests/test_sprint81_result_state_polish.py index fc56da2c..65d2eb76 100644 --- a/backend/tests/test_sprint81_result_state_polish.py +++ b/backend/tests/test_sprint81_result_state_polish.py @@ -34,7 +34,7 @@ def test_ai_labs_use_result_state_blocks() -> None: segmentation_lab = read_feature("segmentation") for content in (detection_lab, segmentation_lab): - assert 'className="result-state result-state-loading"' in content - assert 'className="result-state result-state-error"' in content - assert 'className="result-state result-state-empty"' in content - assert 'className="result-state result-state-ready"' in content + assert "result-state-loading" in content + assert "result-state-error" in content + assert "result-state-empty" in content + assert "result-state-ready" in content diff --git a/backend/tests/test_sprint83_workspace_panel_hierarchy.py b/backend/tests/test_sprint83_workspace_panel_hierarchy.py index 779974cd..aff94331 100644 --- a/backend/tests/test_sprint83_workspace_panel_hierarchy.py +++ b/backend/tests/test_sprint83_workspace_panel_hierarchy.py @@ -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") 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 "grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));" 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-quick-actions" 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 "align-content: center;" in css diff --git a/backend/tests/test_sprint84_data_workspace_density.py b/backend/tests/test_sprint84_data_workspace_density.py index 88b295df..d5797295 100644 --- a/backend/tests/test_sprint84_data_workspace_density.py +++ b/backend/tests/test_sprint84_data_workspace_density.py @@ -44,5 +44,5 @@ def test_data_workspace_density_css_contracts() -> None: assert ".data-panel-list-block" in css assert ".data-section-label" 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 diff --git a/backend/tests/test_sprint85_map_workspace_density.py b/backend/tests/test_sprint85_map_workspace_density.py index 9fbe0c00..9e80885a 100644 --- a/backend/tests/test_sprint85_map_workspace_density.py +++ b/backend/tests/test_sprint85_map_workspace_density.py @@ -41,7 +41,7 @@ def test_map_workspace_density_css_contracts() -> None: assert ".map-inspection-surface" in css assert ".map-workspace-shell .map-toolbar" 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 diff --git a/backend/tests/test_sprint86_quality_workspace_density.py b/backend/tests/test_sprint86_quality_workspace_density.py index cd8baf6f..c3468046 100644 --- a/backend/tests/test_sprint86_quality_workspace_density.py +++ b/backend/tests/test_sprint86_quality_workspace_density.py @@ -50,4 +50,4 @@ def test_quality_workspace_density_css_contracts() -> None: 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(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 diff --git a/backend/tests/test_sprint88_ai_lab_density.py b/backend/tests/test_sprint88_ai_lab_density.py index cb8b58ca..bd17c923 100644 --- a/backend/tests/test_sprint88_ai_lab_density.py +++ b/backend/tests/test_sprint88_ai_lab_density.py @@ -57,7 +57,7 @@ def test_ai_lab_preserves_existing_detection_and_segmentation_controls() -> None assert "onLoadResults" in segmentation assert "onRunQa" 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 diff --git a/backend/tests/test_sprint89_export_system_density.py b/backend/tests/test_sprint89_export_system_density.py index 99703372..63ae2416 100644 --- a/backend/tests/test_sprint89_export_system_density.py +++ b/backend/tests/test_sprint89_export_system_density.py @@ -13,14 +13,14 @@ def test_export_center_exposes_final_handoff_surfaces() -> None: assert 'className="export-center"' in export_center assert 'className="export-center-shell"' 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 '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 '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-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: @@ -77,4 +77,4 @@ def test_export_system_density_css_contracts() -> None: assert ".provider-provenance-grid" in css assert ".provider-provenance-card" 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 diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md index 2a94b0a8..5ab67060 100644 --- a/docs/API_CONTRACTS.md +++ b/docs/API_CONTRACTS.md @@ -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 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 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 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. -`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 session; query parameters never authorize a run, result or export belonging to 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. -### 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 `reviewed_metrics`: the score with the operator's verdicts applied, next to the diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 5be1a6b9..34c4a692 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -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 ### Opgeleverd diff --git a/docs/TODO.md b/docs/TODO.md index 9b5a1cd1..abfa47a9 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,12 @@ # 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) - [x] Voeg een gesynchroniseerde analyseconsole voor een tweede beeldscherm toe. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 05d2ae3e..cf28d785 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2530,9 +2530,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { diff --git a/frontend/package.json b/frontend/package.json index f2a81ed9..05b94007 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,9 @@ "engines": { "node": "^20.19.0 || >=22.12.0" }, + "overrides": { + "nanoid": "3.3.18" + }, "dependencies": { "@fontsource/manrope": "^5.3.0", "@fontsource/public-sans": "^5.3.0", diff --git a/scripts/generate_container_sbom.sh b/scripts/generate_container_sbom.sh index 9285bb2b..29708ba5 100644 --- a/scripts/generate_container_sbom.sh +++ b/scripts/generate_container_sbom.sh @@ -20,7 +20,7 @@ docker run --rm \ -v "$ROOT:/workspace" \ -w /workspace \ "$SYFT_IMAGE" \ - "$TARGET_IMAGE" \ + "docker:$TARGET_IMAGE" \ -o "spdx-json=$OUTPUT" test -s "$ROOT/$OUTPUT" diff --git a/scripts/smoke_detection_calibration_evidence_bundle.sh b/scripts/smoke_detection_calibration_evidence_bundle.sh index 50fc592b..d0a1e9c8 100644 --- a/scripts/smoke_detection_calibration_evidence_bundle.sh +++ b/scripts/smoke_detection_calibration_evidence_bundle.sh @@ -44,8 +44,12 @@ fi timestamp="$(date -u +%Y%m%dT%H%M%SZ)" 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 - BASE_DIR="$(cygpath -u "$BASE_DIR")" +if printf '%s' "$BASE_DIR" | grep -Eq '^[A-Za-z]:\\'; then + 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 SUMMARY_DIR="${BASE_DIR}/summary"