feat(lab): add lazy E40 evidence review
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -41,6 +42,93 @@ def test_advanced_catalogs_are_empty_when_not_configured() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_advanced_index_is_empty_when_not_configured() -> None:
|
||||
router = build_advanced_laboratory_router()
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route() == { # type: ignore[operator]
|
||||
"schema_version": "missioncore.laboratory-advanced-index/v1",
|
||||
"items": [],
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def test_advanced_index_reads_only_bounded_identity_documents(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
e31 = tmp_path / "e31"
|
||||
invalid = e31 / f"e31-source-qualification-{'1' * 64}"
|
||||
valid = e31 / f"e31-source-qualification-{'2' * 64}"
|
||||
invalid.mkdir(parents=True)
|
||||
valid.mkdir()
|
||||
(invalid / "manifest.json").write_text("{}", encoding="utf-8")
|
||||
(valid / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.e31-source-qualification/v1",
|
||||
"result_id": valid.name,
|
||||
"created_at_utc": "2026-07-27T10:00:00Z",
|
||||
"identity_sha256": "2" * 64,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
e31_root_provider=lambda: e31,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route() == { # type: ignore[operator]
|
||||
"schema_version": "missioncore.laboratory-advanced-index/v1",
|
||||
"items": [
|
||||
{
|
||||
"work_id": "e31-source-binding",
|
||||
"result_id": valid.name,
|
||||
"created_at_utc": "2026-07-27T10:00:00Z",
|
||||
"access": "read-only",
|
||||
}
|
||||
],
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def test_advanced_index_rejects_authority_escalation(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "e40"
|
||||
candidate = root / f"e40-perception-product-gate-{'a' * 64}"
|
||||
candidate.mkdir(parents=True)
|
||||
(candidate / "manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.e40-perception-product-gate/v1",
|
||||
"result_id": candidate.name,
|
||||
"created_at_utc": "2026-07-28T11:00:00Z",
|
||||
"identity_sha256": "a" * 64,
|
||||
"identity": {
|
||||
"authority": {
|
||||
"commands_enabled": True,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
router = build_advanced_laboratory_router(
|
||||
e40_root_provider=lambda: root,
|
||||
)
|
||||
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
|
||||
|
||||
assert route()["items"] == [] # type: ignore[index,operator]
|
||||
|
||||
|
||||
def test_advanced_catalogs_fail_closed_on_incomplete_results(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.routing import APIRoute
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import k1link.web.e40_case_review_api as case_api
|
||||
from k1link.web.e40_case_review_api import build_e40_case_review_router
|
||||
|
||||
|
||||
def _endpoint(router: APIRouter, path: str) -> object:
|
||||
for route in router.routes:
|
||||
if (
|
||||
isinstance(route, APIRoute)
|
||||
and route.path == path
|
||||
and route.methods is not None
|
||||
and "GET" in route.methods
|
||||
):
|
||||
return route.endpoint
|
||||
raise AssertionError(f"GET {path} route is missing")
|
||||
|
||||
|
||||
def _prediction(
|
||||
*,
|
||||
sequence: int,
|
||||
severity: str,
|
||||
split: str = "validation",
|
||||
presence: str = "object-present",
|
||||
predicted_presence: str = "occupied-environment",
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "missioncore.e40-perception-prediction/v1",
|
||||
"sequence": sequence,
|
||||
"item_id": f"e30-review-item-{sequence:064x}",
|
||||
"review_key": f"geometry:{sequence}:0",
|
||||
"source_frame_index": 100 + sequence,
|
||||
"source_stratum": "geometry-only",
|
||||
"severity": severity,
|
||||
"split": split,
|
||||
"prediction": {
|
||||
"presence": predicted_presence,
|
||||
"geometry_association": "independent-occupied",
|
||||
"freshness": "current",
|
||||
},
|
||||
"presence_confidence": 1.0,
|
||||
"reference": {
|
||||
"presence": presence,
|
||||
"geometry_association": "object-associated",
|
||||
"freshness": "current",
|
||||
},
|
||||
"scored": split == "validation",
|
||||
"authority": {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_e40_case_review_is_bounded_sorted_and_source_bound(
|
||||
tmp_path: Path,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
result_id = f"e40-perception-product-gate-{'a' * 64}"
|
||||
materialization_id = f"e30-materialization-{'b' * 64}"
|
||||
candidate = tmp_path / result_id
|
||||
candidate.mkdir()
|
||||
rows = [
|
||||
_prediction(sequence=7, severity="medium"),
|
||||
_prediction(
|
||||
sequence=1,
|
||||
severity="standard",
|
||||
presence="object-present",
|
||||
predicted_presence="object-present",
|
||||
),
|
||||
_prediction(sequence=4, severity="high"),
|
||||
_prediction(sequence=2, severity="high"),
|
||||
_prediction(sequence=9, severity="standard", split="development"),
|
||||
]
|
||||
rows[1]["prediction"]["geometry_association"] = "object-associated"
|
||||
predictions = candidate / "predictions.jsonl"
|
||||
predictions.write_text(
|
||||
"".join(json.dumps(row) + "\n" for row in rows),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(candidate / "manifest.json").write_text("{}", encoding="utf-8")
|
||||
result = SimpleNamespace(
|
||||
result_id=result_id,
|
||||
result_root=candidate,
|
||||
manifest={
|
||||
"identity": {
|
||||
"source": {"materialization_id": materialization_id},
|
||||
},
|
||||
},
|
||||
report={"metrics": {"validation_items": 4}},
|
||||
)
|
||||
|
||||
def fake_read(
|
||||
root_text: str,
|
||||
signature: tuple[int, ...],
|
||||
) -> SimpleNamespace:
|
||||
assert root_text == str(candidate.resolve())
|
||||
assert signature
|
||||
return result
|
||||
|
||||
monkeypatch.setattr(case_api, "_read_result_cached", fake_read)
|
||||
router = build_e40_case_review_router(
|
||||
e40_root_provider=lambda: tmp_path,
|
||||
)
|
||||
route = _endpoint(
|
||||
router,
|
||||
"/api/v1/laboratory/e40/results/{result_id}/cases",
|
||||
)
|
||||
catalog = route(result_id=result_id, limit=2) # type: ignore[operator]
|
||||
|
||||
assert catalog == {
|
||||
"schema_version": "missioncore.laboratory-e40-case-catalog/v1",
|
||||
"result_id": result_id,
|
||||
"materialization_id": materialization_id,
|
||||
"items": [
|
||||
{
|
||||
"item_id": f"e30-review-item-{2:064x}",
|
||||
"sequence": 2,
|
||||
"source_frame_index": 102,
|
||||
"source_stratum": "geometry-only",
|
||||
"severity": "high",
|
||||
"presence_confidence": 1.0,
|
||||
"prediction_basis": "fixed-stratum-policy",
|
||||
"reference": {
|
||||
"presence": "object-present",
|
||||
"geometry_association": "object-associated",
|
||||
"freshness": "current",
|
||||
},
|
||||
"prediction": {
|
||||
"presence": "occupied-environment",
|
||||
"geometry_association": "independent-occupied",
|
||||
"freshness": "current",
|
||||
},
|
||||
"mismatched_dimensions": [
|
||||
"presence",
|
||||
"geometry_association",
|
||||
],
|
||||
"access": "read-only",
|
||||
},
|
||||
{
|
||||
"item_id": f"e30-review-item-{4:064x}",
|
||||
"sequence": 4,
|
||||
"source_frame_index": 104,
|
||||
"source_stratum": "geometry-only",
|
||||
"severity": "high",
|
||||
"presence_confidence": 1.0,
|
||||
"prediction_basis": "fixed-stratum-policy",
|
||||
"reference": {
|
||||
"presence": "object-present",
|
||||
"geometry_association": "object-associated",
|
||||
"freshness": "current",
|
||||
},
|
||||
"prediction": {
|
||||
"presence": "occupied-environment",
|
||||
"geometry_association": "independent-occupied",
|
||||
"freshness": "current",
|
||||
},
|
||||
"mismatched_dimensions": [
|
||||
"presence",
|
||||
"geometry_association",
|
||||
],
|
||||
"access": "read-only",
|
||||
},
|
||||
],
|
||||
"total": 3,
|
||||
"truncated": True,
|
||||
"access": "read-only",
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.compute.e40_operator_review import (
|
||||
E40OperatorReviewConflictError,
|
||||
E40OperatorReviewStore,
|
||||
E40OperatorReviewSubject,
|
||||
E40OperatorReviewSubstrate,
|
||||
)
|
||||
|
||||
|
||||
def _substrate() -> E40OperatorReviewSubstrate:
|
||||
return E40OperatorReviewSubstrate(
|
||||
result_id=f"e40-perception-product-gate-{'a' * 64}",
|
||||
materialization_id=f"e30-materialization-{'b' * 64}",
|
||||
case_catalog_sha256="c" * 64,
|
||||
subjects=(
|
||||
E40OperatorReviewSubject(
|
||||
item_id=f"e30-review-item-{'d' * 64}",
|
||||
sequence=2,
|
||||
),
|
||||
E40OperatorReviewSubject(
|
||||
item_id=f"e30-review-item-{'e' * 64}",
|
||||
sequence=9,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_e40_operator_verdict_is_atomic_resumable_and_revisioned(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = E40OperatorReviewStore(root=tmp_path)
|
||||
substrate = _substrate()
|
||||
first_item = substrate.subjects[0].item_id
|
||||
|
||||
empty = store.get(substrate=substrate, reviewer_id="DC")
|
||||
assert empty["revision"] == 0
|
||||
assert empty["reviewed_item_count"] == 0
|
||||
assert empty["remaining_item_count"] == 2
|
||||
|
||||
saved = store.record_verdict(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
item_id=first_item,
|
||||
expected_revision=0,
|
||||
idempotency_key="ui-first",
|
||||
verdict="confirmed-error",
|
||||
)
|
||||
assert saved["revision"] == 1
|
||||
assert saved["reviewed_item_count"] == 1
|
||||
assert saved["decisions"][0]["verdict"] == "confirmed-error" # type: ignore[index]
|
||||
|
||||
resumed = E40OperatorReviewStore(root=tmp_path).get(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
)
|
||||
assert resumed == saved
|
||||
|
||||
replay = store.record_verdict(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
item_id=first_item,
|
||||
expected_revision=0,
|
||||
idempotency_key="ui-first",
|
||||
verdict="confirmed-error",
|
||||
)
|
||||
assert replay == saved
|
||||
|
||||
revised = store.record_verdict(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
item_id=first_item,
|
||||
expected_revision=1,
|
||||
idempotency_key="ui-revise",
|
||||
verdict="rejected-error",
|
||||
)
|
||||
assert revised["revision"] == 2
|
||||
assert revised["reviewed_item_count"] == 1
|
||||
assert revised["decisions"][0]["verdict"] == "rejected-error" # type: ignore[index]
|
||||
|
||||
with pytest.raises(E40OperatorReviewConflictError):
|
||||
store.record_verdict(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
item_id=substrate.subjects[1].item_id,
|
||||
expected_revision=1,
|
||||
idempotency_key="ui-stale",
|
||||
verdict="confirmed-error",
|
||||
)
|
||||
|
||||
|
||||
def test_e40_operator_review_is_bound_to_exact_case_catalog(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = E40OperatorReviewStore(root=tmp_path)
|
||||
substrate = _substrate()
|
||||
store.record_verdict(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
item_id=substrate.subjects[0].item_id,
|
||||
expected_revision=0,
|
||||
idempotency_key="ui-bound",
|
||||
verdict="confirmed-error",
|
||||
)
|
||||
|
||||
changed = E40OperatorReviewSubstrate(
|
||||
result_id=substrate.result_id,
|
||||
materialization_id=substrate.materialization_id,
|
||||
case_catalog_sha256="f" * 64,
|
||||
subjects=substrate.subjects,
|
||||
)
|
||||
changed_review = store.get(substrate=changed, reviewer_id="DC")
|
||||
|
||||
assert changed_review["revision"] == 0
|
||||
assert changed_review["review_id"] != store.get(
|
||||
substrate=substrate,
|
||||
reviewer_id="DC",
|
||||
)["review_id"]
|
||||
Reference in New Issue
Block a user