Keep orthophoto release evidence immutable
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 02:23:46 +02:00
parent 06d05f03a6
commit 0c2ebc1ffc
5 changed files with 79 additions and 3 deletions
+3
View File
@@ -25,6 +25,9 @@
- Made official `YYYY.NN` orthophoto Datasets take precedence over rolling - Made official `YYYY.NN` orthophoto Datasets take precedence over rolling
acquisition markers in source-catalog comparison. No API, migration, acquisition markers in source-catalog comparison. No API, migration,
scheduler, browser fetch or automatic refresh was added. scheduler, browser fetch or automatic refresh was added.
- Made staged, reviewed and applied release evidence write-once. An idempotent
apply validates and reuses the existing evidence instead of changing its
timestamp or authorization history.
## Sprint 230 Governed orthophoto release preflight (2026-07-17) ## Sprint 230 Governed orthophoto release preflight (2026-07-17)
@@ -277,6 +277,9 @@ def test_stage_fetches_one_allowlisted_map_and_writes_reviewable_rgb_evidence(tm
assert manifest["normalized_geotiff"]["crs"] == "EPSG:31370" assert manifest["normalized_geotiff"]["crs"] == "EPSG:31370"
assert manifest["normalized_geotiff"]["band_count"] == 3 assert manifest["normalized_geotiff"]["band_count"] == 3
assert Path(manifest["review_preview"]["path"]).read_bytes().startswith(b"\x89PNG") assert Path(manifest["review_preview"]["path"]).read_bytes().startswith(b"\x89PNG")
with pytest.raises(RuntimeError, match="will not be overwritten"):
MANAGER.stage_artifacts(args, release_report, request, opener=opener)
assert len(calls) == 1
def test_getmap_redirect_and_response_limits_fail_closed(tmp_path: Path) -> None: def test_getmap_redirect_and_response_limits_fail_closed(tmp_path: Path) -> None:
@@ -397,6 +400,47 @@ def test_official_flight_dates_are_persisted_without_inventing_a_catalog_date()
MANAGER.parse_flight_dates(["spring 2025"]) MANAGER.parse_flight_dates(["spring 2025"])
def test_applied_evidence_is_immutable_on_idempotent_retry(tmp_path: Path) -> None:
args, plan_path, plan, staged = staged_plan(tmp_path)
manifest = MANAGER.validate_staged_artifacts(args, Path(staged["manifest_path"]))
args.approve = True
args.reviewer = "Jens"
review = MANAGER.build_review_evidence(args, plan_path, plan, manifest)
review_path = MANAGER.default_review_path(args, "2025.04")
dataset = {
"id": "dataset-1",
"checksum_sha256": manifest["normalized_geotiff"]["sha256"],
}
final_report = report(status="current", local="2025.04")
final_report["release"]["comparison_status"] = "same"
path, first = MANAGER.applied_evidence(
args,
plan_path,
plan,
review_path,
review,
dataset,
final_report,
reused=False,
)
original_bytes = path.read_bytes()
_, second = MANAGER.applied_evidence(
args,
plan_path,
plan,
review_path,
review,
dataset,
final_report,
reused=True,
)
assert second == first
assert path.read_bytes() == original_bytes
assert second["dataset_status"] == "imported"
def test_release_manager_is_packaged_and_compiled_by_readiness() -> None: def test_release_manager_is_packaged_and_compiled_by_readiness() -> None:
dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8") dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8")
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8") readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
+2
View File
@@ -9787,6 +9787,8 @@ Implemented:
- Made the latest official `YYYY.NN` Dataset authoritative for source-catalog - Made the latest official `YYYY.NN` Dataset authoritative for source-catalog
comparison even when a newer-imported rolling marker also exists. No API, comparison even when a newer-imported rolling marker also exists. No API,
migration, release table, Job type or frontend behavior changed. migration, release table, Job type or frontend behavior changed.
- Made stage, review and applied evidence write-once. Exact apply retries reuse
the original checksum-bound evidence and never rewrite approval history.
Validation so far: Validation so far:
- 38 focused Sprint 222/230/231 tests pass with deprecations treated as errors. - 38 focused Sprint 222/230/231 tests pass with deprecations treated as errors.
+3 -1
View File
@@ -168,7 +168,9 @@ flight-day preflight identities; review binds a named approval; applied
evidence binds both to the immutable Dataset id and checksum. Paths outside evidence binds both to the immutable Dataset id and checksum. Paths outside
this root, changed files and changed provider/local state fail closed. Only the this root, changed files and changed provider/local state fail closed. Only the
normalized GeoTIFF enters ordinary Dataset storage through DatasetService. normalized GeoTIFF enters ordinary Dataset storage through DatasetService.
Existing `most_recent_at_*` raster metadata is never rewritten. Existing release evidence and `most_recent_at_*` raster metadata are never
rewritten. An exact apply retry validates and returns the original applied
evidence rather than replacing it.
DHMV II DTM/DSM outputs are also normal raster Dataset files. The provider WCS DHMV II DTM/DSM outputs are also normal raster Dataset files. The provider WCS
returns multipart coverage data; GeoIntel retains response and extracted returns multipart coverage data; GeoIntel retains response and extracted
+27 -2
View File
@@ -446,6 +446,10 @@ def stage_artifacts(
geotiff_path = directory / f"orthophoto_{edition}_{selection_key(list(args.bbox))}.tif" geotiff_path = directory / f"orthophoto_{edition}_{selection_key(list(args.bbox))}.tif"
preview_path = directory / "review-preview.png" preview_path = directory / "review-preview.png"
manifest_path = directory / "staged-manifest.json" manifest_path = directory / "staged-manifest.json"
if directory.exists() and any(directory.iterdir()):
raise RuntimeError(
f"Governed orthophoto evidence already exists and will not be overwritten: {directory}"
)
max_bytes = args.max_response_mb * 1024 * 1024 max_bytes = args.max_response_mb * 1024 * 1024
content, content_type, final_url = fetch_map( content, content_type, final_url = fetch_map(
request_identity, request_identity,
@@ -888,6 +892,22 @@ def applied_evidence(
*, *,
reused: bool, reused: bool,
) -> tuple[Path, dict[str, Any]]: ) -> tuple[Path, dict[str, Any]]:
path = plan_path.with_name("applied-evidence.json")
if path.is_file():
existing = json.loads(path.read_text(encoding="utf-8"))
if existing.get("applied_evidence_sha256") != canonical_sha256(existing, "applied_evidence_sha256"):
raise RuntimeError("Existing orthophoto applied evidence checksum is invalid")
if (
existing.get("project_id") != args.project_id
or existing.get("scope") != args.scope
or existing.get("edition") != plan["edition"]
or existing.get("staged_plan_sha256") != plan["plan_sha256"]
or existing.get("review_sha256") != review["review_sha256"]
or existing.get("dataset_id") != dataset["id"]
or existing.get("dataset_checksum_sha256") != dataset["checksum_sha256"]
):
raise RuntimeError("Existing orthophoto applied evidence does not match this approved Dataset")
return path, existing
payload: dict[str, Any] = { payload: dict[str, Any] = {
"schema_version": 1, "schema_version": 1,
"status": "applied", "status": "applied",
@@ -908,7 +928,6 @@ def applied_evidence(
"existing_snapshots_retained": True, "existing_snapshots_retained": True,
} }
payload["applied_evidence_sha256"] = canonical_sha256(payload, "applied_evidence_sha256") payload["applied_evidence_sha256"] = canonical_sha256(payload, "applied_evidence_sha256")
path = plan_path.with_name("applied-evidence.json")
write_json(path, payload) write_json(path, payload)
return path, payload return path, payload
@@ -939,10 +958,12 @@ def main() -> int:
edition = require_edition(args, report) edition = require_edition(args, report)
if args.action == "stage": if args.action == "stage":
authorization = authorize_stage(args, report) authorization = authorize_stage(args, report)
plan_path = governed_path(args, args.plan_path or default_plan_path(args, edition))
if plan_path.exists():
raise RuntimeError(f"Governed orthophoto plan already exists and will not be overwritten: {plan_path}")
request_identity = map_request(report) request_identity = map_request(report)
staged = stage_artifacts(args, report, request_identity) staged = stage_artifacts(args, report, request_identity)
plan = build_staged_plan(args, report, authorization, staged) plan = build_staged_plan(args, report, authorization, staged)
plan_path = governed_path(args, args.plan_path or default_plan_path(args, edition))
write_json(plan_path, plan) write_json(plan_path, plan)
print( print(
json.dumps( json.dumps(
@@ -958,6 +979,10 @@ def main() -> int:
if args.action == "review": if args.action == "review":
review = build_review_evidence(args, plan_path, plan, manifest) review = build_review_evidence(args, plan_path, plan, manifest)
review_path = governed_path(args, args.review_path or default_review_path(args, edition)) review_path = governed_path(args, args.review_path or default_review_path(args, edition))
if review_path.exists():
raise RuntimeError(
f"Governed orthophoto review already exists and will not be overwritten: {review_path}"
)
write_json(review_path, review) write_json(review_path, review)
print(json.dumps({"status": "approved", "review_path": str(review_path), **review}, ensure_ascii=False, indent=2)) print(json.dumps({"status": "approved", "review_path": str(review_path), **review}, ensure_ascii=False, indent=2))
return 0 return 0