From 0bc3b2f2e336188dfba2099c6116b62aeaf82434 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 24 Jun 2026 23:59:25 +0200 Subject: [PATCH] Add AI lab action guardrails --- CHANGELOG.md | 8 ++++ ...test_sprint104_ai_lab_action_guardrails.py | 40 +++++++++++++++++++ docs/CODEX_EXECUTION_LOG.md | 27 +++++++++++++ docs/TODO.md | 1 + .../src/components/detection/DetectionLab.tsx | 22 +++++++++- .../segmentation/SegmentationLab.tsx | 19 ++++++++- frontend/src/styles/app.css | 34 ++++++++++++++++ 7 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 backend/tests/test_sprint104_ai_lab_action_guardrails.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 115c0f01..dfaf8b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1008,3 +1008,11 @@ Added: - Segmentation readiness now shows raster dataset, model availability and tile manifest provenance state before submitting a run. - Added regression coverage for the AI Lab readiness UI contract and styling. - No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced. + +## Sprint 104 AI Lab action guardrails (2026-06-24) + +- Added explicit action guardrails below Detection and Segmentation run-readiness panels. +- Detection now distinguishes configured model state from UI-runnable state and blocks the explicit test/demo-only fixture detector in the normal run form. +- Segmentation now distinguishes configured model state from UI-runnable state and blocks the explicit test/demo-only fixture segmenter in the normal run form. +- Added regression coverage for AI Lab action guardrails and compact guardrail styling. +- No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced. diff --git a/backend/tests/test_sprint104_ai_lab_action_guardrails.py b/backend/tests/test_sprint104_ai_lab_action_guardrails.py new file mode 100644 index 00000000..6c25115c --- /dev/null +++ b/backend/tests/test_sprint104_ai_lab_action_guardrails.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_detection_lab_distinguishes_configured_model_from_ui_runnable_action() -> None: + lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text( + encoding="utf-8" + ) + + assert "detectionModelUiRunnable" in lab + assert "selectedDetectionModelId !== 'manual-fixture-detector'" in lab + assert "detectionRunBlockedReason" in lab + assert "Fixture model is explicit test/demo-only" in lab + assert "Run action" in lab + assert "disabled={runningDetection || !detectionRunReady}" in lab + + +def test_segmentation_lab_distinguishes_configured_model_from_ui_runnable_action() -> None: + lab = (ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx").read_text( + encoding="utf-8" + ) + + assert "segmentationModelUiRunnable" in lab + assert "selectedSegmentationModelId !== 'fixture-segmenter'" in lab + assert "segmentationRunBlockedReason" in lab + assert "Fixture segmenter is explicit test/demo-only" in lab + assert "Run action" in lab + assert "disabled={runningSegmentation || !segmentationRunReady}" in lab + + +def test_ai_lab_guardrail_styles_remain_compact() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".lab-action-guardrail" in css + assert ".lab-action-guardrail-ready" in css + assert "overflow-wrap: anywhere;" in css diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index f75aac0f..f3af82f9 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -3760,3 +3760,30 @@ Limitations: Next recommended pass: - Continue with V1 usability work that reduces operator confusion without expanding frozen product scope. + +## Sprint 104 AI Lab action guardrails (2026-06-24) + +Changed: +- Added explicit action guardrails below the Detection Lab and Segmentation Lab run-readiness panels. +- Detection now distinguishes `configured` model registry state from UI-runnable action state, blocking the explicit test/demo-only `manual-fixture-detector` in the normal workbench run form. +- Segmentation now distinguishes `configured` model registry state from UI-runnable action state, blocking the explicit test/demo-only `fixture-segmenter` in the normal workbench run form. +- Updated run button disabled conditions to use the new readiness/action state. +- Added compact guardrail styling and regression coverage in `backend/tests/test_sprint104_ai_lab_action_guardrails.py`. +- Updated `CHANGELOG.md` and `docs/TODO.md`. + +Tested: +- Red step: `python -m pytest backend\tests\test_sprint104_ai_lab_action_guardrails.py -q` failed while the action guardrails and CSS contracts were absent. +- `python -m pytest backend\tests\test_sprint104_ai_lab_action_guardrails.py -q` (`3 passed`) +- `python -m pytest backend\tests\test_sprint103_ai_lab_run_readiness.py backend\tests\test_sprint104_ai_lab_action_guardrails.py -q` (`6 passed`) +- `python -m compileall backend/app` +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` + +Open: +- Full repository validation, commit, deploy and live browser verification are still pending for this pass. + +Limitations: +- Frontend action-guardrail guidance only; no backend API, persistence, migration, provider fetching, AI dependency or model execution behavior changed. + +Next recommended pass: +- After deploy validation, continue with V1 usability work that reduces operator confusion without expanding frozen product scope. diff --git a/docs/TODO.md b/docs/TODO.md index ff346433..d1675744 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -370,3 +370,4 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add useful default dataset context so Data, Map and Exports are immediately usable after project/demo load. - [x] Make raster tile handoff to Detection Lab auto-select the configured YOLO run form. - [x] Add AI Lab run-readiness checks for Detection and Segmentation before job submission. +- [x] Add AI Lab action guardrails so explicit fixture models are not exposed as normal operator runs. diff --git a/frontend/src/components/detection/DetectionLab.tsx b/frontend/src/components/detection/DetectionLab.tsx index 98c6075d..cdf7ba56 100644 --- a/frontend/src/components/detection/DetectionLab.tsx +++ b/frontend/src/components/detection/DetectionLab.tsx @@ -89,10 +89,24 @@ export function DetectionLab({ const detectionHasDataset = selectedDetectionDatasetId.length > 0 const detectionHasModel = selectedDetectionModel !== null const detectionModelReady = Boolean(selectedDetectionModel?.configured) + const detectionModelUiRunnable = detectionModelReady && selectedDetectionModelId !== 'manual-fixture-detector' const detectionHasTileManifest = !detectionRequiresTileManifest || detectionTileManifestPath.trim().length > 0 const detectionRunReady = - Boolean(selectedProjectId) && detectionHasDataset && detectionHasModel && detectionModelReady && detectionHasTileManifest + Boolean(selectedProjectId) && detectionHasDataset && detectionHasModel && detectionModelUiRunnable && detectionHasTileManifest + const detectionRunBlockedReason = !selectedProjectId + ? 'Select or create a project first' + : !detectionHasDataset + ? 'Select a raster dataset' + : !detectionHasModel + ? 'Select a detection model' + : selectedDetectionModelId === 'manual-fixture-detector' + ? 'Fixture model is explicit test/demo-only' + : !detectionModelReady + ? selectedDetectionModel?.limitation_message ?? 'Selected model is not configured' + : !detectionHasTileManifest + ? 'Provide a raster tile manifest for configured YOLO' + : null return (
@@ -191,6 +205,10 @@ export function DetectionLab({ +
+ Run action + {detectionRunReady ? 'Ready to submit a detection job' : detectionRunBlockedReason} +
{rasterDatasets.length === 0 ? (
No raster datasets available for detection. @@ -242,7 +260,7 @@ export function DetectionLab({ /> ) : null} -
diff --git a/frontend/src/components/segmentation/SegmentationLab.tsx b/frontend/src/components/segmentation/SegmentationLab.tsx index 1b71d419..e92d4fd4 100644 --- a/frontend/src/components/segmentation/SegmentationLab.tsx +++ b/frontend/src/components/segmentation/SegmentationLab.tsx @@ -90,8 +90,19 @@ export function SegmentationLab({ }: SegmentationLabProps): JSX.Element { const segmentationHasDataset = selectedSegmentationDatasetId.length > 0 const segmentationHasTileManifest = segmentationTileManifestPath.trim().length > 0 + const segmentationModelUiRunnable = + selectedSegmentationModelConfigured && selectedSegmentationModelId !== 'fixture-segmenter' const segmentationRunReady = - Boolean(selectedProjectId) && segmentationHasDataset && selectedSegmentationModelConfigured + Boolean(selectedProjectId) && segmentationHasDataset && segmentationModelUiRunnable + const segmentationRunBlockedReason = !selectedProjectId + ? 'Select or create a project first' + : !segmentationHasDataset + ? 'Select a raster dataset' + : selectedSegmentationModelId === 'fixture-segmenter' + ? 'Fixture segmenter is explicit test/demo-only' + : !selectedSegmentationModelConfigured + ? selectedSegmentationModelLimitation ?? 'Selected segmentation model is not configured' + : null return (
@@ -184,6 +195,10 @@ export function SegmentationLab({ +
+ Run action + {segmentationRunReady ? 'Ready to submit a segmentation job' : segmentationRunBlockedReason} +
{rasterDatasets.length === 0 ? (
No raster datasets available for segmentation. @@ -237,7 +252,7 @@ export function SegmentationLab({ className="primary-action" type="button" onClick={onRunSegmentation} - disabled={runningSegmentation || !selectedProjectId || rasterDatasets.length === 0 || !selectedSegmentationModelConfigured} + disabled={runningSegmentation || !segmentationRunReady} > Run segmentation diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index b18af5ab..8508b5ab 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2719,6 +2719,40 @@ button.entity-card { overflow-wrap: anywhere; } +.lab-action-guardrail { + display: flex; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 0.6rem; + border: 1px solid #d8e3de; + border-radius: 8px; + padding: 0.52rem 0.62rem; + background: #ffffff; +} + +.lab-action-guardrail-ready { + border-color: #b8dcc9; + background: #f8fff9; +} + +.lab-action-guardrail span { + flex: 0 0 auto; + color: var(--muted); + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; +} + +.lab-action-guardrail strong { + min-width: 0; + color: var(--text); + font-size: 0.88rem; + line-height: 1.28; + text-align: right; + overflow-wrap: anywhere; +} + .ai-lab-shell .model-list { grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); gap: 0.55rem;