[AZ-226] Add generated tile staging

Keep generated tiles auditable and untrusted onboard while preserving
covariance, quality, and sidecar metadata for post-flight sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-03 18:10:25 +03:00
parent e86084da6b
commit 2db50bc124
8 changed files with 220 additions and 2 deletions
+8
View File
@@ -3,6 +3,10 @@
from .interfaces import LocalTileManager, TileManager
from .types import (
CacheValidationReport,
GeneratedTileCandidate,
GeneratedTileSidecar,
GeneratedTileSyncPackage,
TileGenerationRequest,
TileManifestEntry,
TileMetadataLookup,
TileValidationDecision,
@@ -11,8 +15,12 @@ from .types import (
__all__ = [
"CacheValidationReport",
"GeneratedTileCandidate",
"GeneratedTileSidecar",
"GeneratedTileSyncPackage",
"LocalTileManager",
"TileManager",
"TileGenerationRequest",
"TileManifestEntry",
"TileMetadataLookup",
"TileValidationDecision",
+53
View File
@@ -8,7 +8,11 @@ from shared.errors import ErrorEnvelope
from .types import (
CacheValidationReport,
GeneratedTileCandidate,
GeneratedTileSidecar,
GeneratedTileSyncPackage,
TileManifestEntry,
TileGenerationRequest,
TileMetadataLookup,
TileValidationDecision,
freshness_status,
@@ -40,6 +44,7 @@ class LocalTileManager:
self._trusted_by_tile_id: dict[str, CacheTileRecord] = {}
self._descriptor_by_tile_id: dict[str, str] = {}
self._tile_id_by_chunk_id: dict[str, str] = {}
self._generated_candidates: list[GeneratedTileCandidate] = []
def validate_cache(self, entries: list[TileManifestEntry]) -> CacheValidationReport:
if not self._postgis_available:
@@ -102,6 +107,54 @@ class LocalTileManager:
descriptor_ref=self._descriptor_by_tile_id[tile_id],
)
def orthorectify_frame(self, request: TileGenerationRequest) -> GeneratedTileCandidate:
if not request.frame_usable:
return GeneratedTileCandidate(accepted=False, rejection_reason="frame_not_usable")
if request.parent_covariance_m > 5.0:
return GeneratedTileCandidate(accepted=False, rejection_reason="covariance_too_high")
if request.quality_score < 0.25:
return GeneratedTileCandidate(accepted=False, rejection_reason="quality_too_low")
trust_level = "generated" if request.parent_covariance_m <= 3.0 else "candidate"
tile_id = f"generated-{request.mission_id}-{request.frame_id}"
candidate = GeneratedTileCandidate(
accepted=True,
tile_id=tile_id,
cog_ref=f"generated/{request.mission_id}/{tile_id}.cog.tif",
sidecar=GeneratedTileSidecar(
tile_id=tile_id,
parent_frame_id=request.frame_id,
parent_covariance_m=request.parent_covariance_m,
quality_score=request.quality_score,
trust_level=trust_level,
provenance=request.source_provenance,
),
)
self._generated_candidates.append(candidate)
return candidate
def package_sync(self, mission_id: str) -> GeneratedTileSyncPackage:
sidecars = tuple(
candidate.sidecar
for candidate in self._generated_candidates
if candidate.sidecar is not None
)
manifest_delta = tuple(
{
"tile_id": sidecar.tile_id,
"trust_level": sidecar.trust_level,
"parent_covariance_m": sidecar.parent_covariance_m,
"provenance": sidecar.provenance,
}
for sidecar in sidecars
)
return GeneratedTileSyncPackage(
package_ref=f"generated/{mission_id}/sync-package.json",
mission_id=mission_id,
manifest_delta=manifest_delta,
sidecars=sidecars,
)
def _validate_entry(self, entry: TileManifestEntry) -> TileValidationDecision:
if entry.signature_hash not in self._trusted_signature_hashes:
return TileValidationDecision(
+36
View File
@@ -53,6 +53,42 @@ class TileMetadataLookup(TileManagerModel):
error: ErrorEnvelope | None = None
class TileGenerationRequest(TileManagerModel):
mission_id: str = Field(min_length=1)
frame_id: str = Field(min_length=1)
image_ref: str = Field(min_length=1)
timestamp_ns: int = Field(ge=0)
parent_covariance_m: float = Field(ge=0.0)
frame_usable: bool
quality_score: float = Field(ge=0.0, le=1.0)
footprint: dict[str, float]
source_provenance: str = Field(min_length=1)
class GeneratedTileSidecar(TileManagerModel):
tile_id: str = Field(min_length=1)
parent_frame_id: str = Field(min_length=1)
parent_covariance_m: float = Field(ge=0.0)
quality_score: float = Field(ge=0.0, le=1.0)
trust_level: Literal["generated", "candidate"]
provenance: str = Field(min_length=1)
class GeneratedTileCandidate(TileManagerModel):
accepted: bool
tile_id: str | None = None
cog_ref: str | None = None
sidecar: GeneratedTileSidecar | None = None
rejection_reason: str | None = None
class GeneratedTileSyncPackage(TileManagerModel):
package_ref: str = Field(min_length=1)
mission_id: str = Field(min_length=1)
manifest_delta: tuple[dict[str, object], ...]
sidecars: tuple[GeneratedTileSidecar, ...]
def freshness_status(expires_at: datetime, now: datetime) -> Literal["fresh", "stale"]:
normalized_expiry = expires_at
if normalized_expiry.tzinfo is None: