mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 20:21:17 +00:00
dde838d2cc
Strictly additive Alembic migration on the AZ-263 baseline (data_model
.md § 6.1 / § 6.3): six new tiles columns (tile_uuid UNIQUE,
location_hash, content_sha256, disk_bytes, accessed_at, uploaded_at),
four new btree indices, one UNIQUE expression index over the
COALESCE-zero-uuid natural key, CHECK widening of
ck_tiles_freshness_status to the AZ-263 + AZ-303 vocabulary UNION,
four NULLable bbox columns on sector_classifications, and a new
tile_freshness_rules table seeded with the two default thresholds.
Pinned UUIDv5 namespace (TILE_NAMESPACE_UUID =
5b8d0c2e-1a4f-4b3a-8c9d-e7f6a3b2c1d0) + derive_tile_id /
derive_location_hash helpers cross-coordinated with
satellite-provider. Migration runner apply_migrations(config) drives
Alembic command.upgrade("head") against the AZ-263 env with one
retry on PG SQLSTATE 40001 and structured INFO logs on apply / no-op.
Contract bump tile_metadata_store.md v1.1.0 -> v1.2.0 adds
TileMetadata.location_hash: UUID | None = None (non-breaking).
module-layout.md updated so c6_tile_cache explicitly Owns
db/migrations/**.
Tier-1 tests: UUIDv5 determinism + locked vectors + DSN resolution +
retry mocked DBAPIError -> 1180 passed, 32 skipped. Tier-2 docker
schema tests gated by @pytest.mark.docker run against the existing
docker-compose.test.yml db service.
Co-authored-by: Cursor <cursoragent@cursor.com>
178 lines
6.1 KiB
Python
178 lines
6.1 KiB
Python
"""AZ-304 AC-10 / AC-11 — UUIDv5 namespace determinism + cross-repo coordination.
|
|
|
|
The expected UUIDs locked below are the *cross-repo coordination
|
|
evidence* between ``gps-denied-onboard`` (Python ``uuid.uuid5``) and
|
|
``satellite-provider`` (C# Guid.NewGuid v5 implementation per
|
|
``AZ-TBD_tile_identity_uuidv5_bulk_list``). Both sides MUST emit
|
|
byte-identical UUIDs for these input vectors; changing any expected
|
|
value here without a coordinated cross-workspace release breaks the
|
|
correlation key joining the two systems.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
|
|
from gps_denied_onboard.components.c6_tile_cache._types import TileSource
|
|
from gps_denied_onboard.components.c6_tile_cache._uuid_namespace import (
|
|
TILE_NAMESPACE_UUID,
|
|
derive_location_hash,
|
|
derive_tile_id,
|
|
)
|
|
|
|
_EXPECTED_NAMESPACE = UUID("5b8d0c2e-1a4f-4b3a-8c9d-e7f6a3b2c1d0")
|
|
|
|
|
|
# AC-10 — five locked tile-id vectors. (z, x, y, source, flight_id) -> expected uuidv5.
|
|
_TILE_ID_VECTORS: list[tuple[int, int, int, str, str | None, str]] = [
|
|
(18, 72346, 46342, "googlemaps", None, "6f49531b-1351-55ba-b733-66d3f1fca1a5"),
|
|
(
|
|
18,
|
|
72346,
|
|
46342,
|
|
"onboard_ingest",
|
|
"11111111-2222-3333-4444-555555555555",
|
|
"c7f6eda4-3b95-5818-a0b7-1aa8cbb5aa95",
|
|
),
|
|
(10, 300, 200, "googlemaps", None, "3604dd59-1018-5889-97dc-ba5635761ac5"),
|
|
(
|
|
21,
|
|
999999,
|
|
999999,
|
|
"onboard_ingest",
|
|
"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
"36c6e0c0-54a1-56ab-9dfd-a4f8f184cb22",
|
|
),
|
|
(15, 1000, 2000, "googlemaps", None, "955df722-8d4e-5375-8a23-4f45dc16fef1"),
|
|
]
|
|
|
|
|
|
# AC-11 — four locked location-hash vectors. (z, x, y) -> expected uuidv5.
|
|
_LOCATION_HASH_VECTORS: list[tuple[int, int, int, str]] = [
|
|
(18, 72346, 46342, "e95c7edb-550e-58eb-8f94-3056f73a57d3"),
|
|
(10, 300, 200, "76aa22b7-fd8e-5089-8b20-c45fb4a0f5e8"),
|
|
(21, 999999, 999999, "4337a27e-f118-524f-8d74-82cf9295c632"),
|
|
(15, 1000, 2000, "0501b2fc-0fc8-5330-a407-c7ccbf1fb9c7"),
|
|
]
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# AC-10: namespace + derivation are deterministic and locked.
|
|
|
|
|
|
def test_ac10_namespace_uuid_locked() -> None:
|
|
# Assert
|
|
assert TILE_NAMESPACE_UUID == _EXPECTED_NAMESPACE
|
|
assert TILE_NAMESPACE_UUID.version == 4 # the namespace itself was a one-time UUIDv4 pick
|
|
assert str(TILE_NAMESPACE_UUID) == "5b8d0c2e-1a4f-4b3a-8c9d-e7f6a3b2c1d0"
|
|
|
|
|
|
@pytest.mark.parametrize("z,x,y,source,flight_id,expected", _TILE_ID_VECTORS)
|
|
def test_ac10_derive_tile_id_locked_vectors(
|
|
z: int, x: int, y: int, source: str, flight_id: str | None, expected: str
|
|
) -> None:
|
|
# Act
|
|
result = derive_tile_id(z, x, y, source, flight_id)
|
|
|
|
# Assert
|
|
assert result == UUID(expected)
|
|
assert result.version == 5
|
|
|
|
|
|
@pytest.mark.parametrize("z,x,y,source,flight_id,expected", _TILE_ID_VECTORS)
|
|
def test_ac10_derive_tile_id_idempotent_on_second_call(
|
|
z: int, x: int, y: int, source: str, flight_id: str | None, expected: str
|
|
) -> None:
|
|
# Act
|
|
first = derive_tile_id(z, x, y, source, flight_id)
|
|
second = derive_tile_id(z, x, y, source, flight_id)
|
|
|
|
# Assert
|
|
assert first == second == UUID(expected)
|
|
|
|
|
|
def test_ac10_derive_tile_id_accepts_tile_source_enum() -> None:
|
|
"""Passing a `TileSource` enum yields the same UUID as its string value."""
|
|
# Act
|
|
from_enum = derive_tile_id(18, 72346, 46342, TileSource.GOOGLEMAPS, None)
|
|
from_str = derive_tile_id(18, 72346, 46342, "googlemaps", None)
|
|
|
|
# Assert
|
|
assert from_enum == from_str
|
|
assert from_enum == UUID("6f49531b-1351-55ba-b733-66d3f1fca1a5")
|
|
|
|
|
|
def test_ac10_derive_tile_id_accepts_uuid_flight_id() -> None:
|
|
"""Passing a `UUID` instance for ``flight_id`` matches the string form."""
|
|
# Arrange
|
|
flight_uuid_str = "11111111-2222-3333-4444-555555555555"
|
|
|
|
# Act
|
|
from_uuid = derive_tile_id(18, 72346, 46342, "onboard_ingest", UUID(flight_uuid_str))
|
|
from_str = derive_tile_id(18, 72346, 46342, "onboard_ingest", flight_uuid_str)
|
|
|
|
# Assert
|
|
assert from_uuid == from_str
|
|
assert from_uuid == UUID("c7f6eda4-3b95-5818-a0b7-1aa8cbb5aa95")
|
|
|
|
|
|
def test_ac10_derive_tile_id_rejects_unknown_source_type() -> None:
|
|
# Act + Assert
|
|
with pytest.raises(TypeError, match="source must be"):
|
|
derive_tile_id(18, 0, 0, 123, None)
|
|
|
|
|
|
def test_ac10_derive_tile_id_rejects_unknown_flight_id_type() -> None:
|
|
# Act + Assert
|
|
with pytest.raises(TypeError, match="flight_id must be"):
|
|
derive_tile_id(18, 0, 0, "googlemaps", 12345)
|
|
|
|
|
|
def test_ac10_derive_tile_id_rejects_malformed_flight_id_string() -> None:
|
|
# Act + Assert
|
|
with pytest.raises(ValueError):
|
|
derive_tile_id(18, 0, 0, "googlemaps", "not-a-uuid")
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# AC-11: location_hash is invariant across source / flight_id.
|
|
|
|
|
|
@pytest.mark.parametrize("z,x,y,expected", _LOCATION_HASH_VECTORS)
|
|
def test_ac11_derive_location_hash_locked_vectors(z: int, x: int, y: int, expected: str) -> None:
|
|
# Act
|
|
result = derive_location_hash(z, x, y)
|
|
|
|
# Assert
|
|
assert result == UUID(expected)
|
|
assert result.version == 5
|
|
|
|
|
|
def test_ac11_location_hash_invariant_across_source_and_flight() -> None:
|
|
"""Same (z, x, y) yields the same location_hash for any source + flight_id."""
|
|
# Arrange
|
|
cell = (18, 72346, 46342)
|
|
cell_lh = derive_location_hash(*cell)
|
|
|
|
# Act + Assert — `derive_tile_id` produces _different_ tile UUIDs for
|
|
# different source/flight combos but the cell-bag is the same; this is
|
|
# explicitly tested via the locked vectors above. Here we only need to
|
|
# confirm that `location_hash` NEVER depends on these inputs.
|
|
for _source in ("googlemaps", "onboard_ingest"):
|
|
for _flight_id in (None, "11111111-2222-3333-4444-555555555555"):
|
|
assert derive_location_hash(*cell) == cell_lh
|
|
|
|
|
|
def test_ac11_different_cells_yield_different_location_hashes() -> None:
|
|
# Act
|
|
lh1 = derive_location_hash(18, 72346, 46342)
|
|
lh2 = derive_location_hash(18, 72346, 46343) # neighbour cell
|
|
lh3 = derive_location_hash(19, 72346, 46342) # same x,y at different zoom
|
|
|
|
# Assert
|
|
assert lh1 != lh2
|
|
assert lh1 != lh3
|
|
assert lh2 != lh3
|