Wrap up cycle 5 verification + documentation: - Steps 10/11 wrap-up reports (implementation_completeness + implementation_report) for the AZ-503-foundation + AZ-504 batch. - Step 12 test-spec sync: AZ-503-foundation/AZ-504 ACs appended; AZ-505 deferred ACs recorded. - Step 13 update-docs: architecture, data-model, glossary, module- layout, uav-tile-upload contract (v1.1.0), DataAccess + Services + Tests module docs synced; new common_uuidv5.md module doc. - Step 14 security audit: PASS_WITH_WARNINGS; 0 new Critical/High; 2 new Low informational (F1 flightId provenance, F2 pgcrypto deploy gap). - Step 15 performance test: PASS_WITH_INFRA_WARNINGS; PT-08 passed twice (AZ-504 fix verified); PT-01/02 failed due to recurring local Docker/colima DNS cold-start (not an app regression). Cycle-3 perf-harness leftover stays OPEN with replay #5 documented. - Autodev state moved to Step 16 (Deploy). Co-authored-by: Cursor <cursoragent@cursor.com>
5.2 KiB
Module: Common/Utils/Uuidv5
Purpose
Deterministic UUIDv5 generator (RFC 9562 §5.5, SHA-1 namespace+name hashing) for tile identity. Pure C# implementation, ≤80 LoC, no third-party dependency. Owns the cross-repo TileNamespace constant that pins UUIDv5 outputs to be byte-identical between this workspace (C#) and the sibling gps-denied-onboard workspace (Python uuid.uuid5).
csproj: SatelliteProvider.Common/Utils/Uuidv5.cs
Introduced: AZ-503 (Cycle 5)
Public Interface
All members are static on Uuidv5:
TileNamespace(Guid, public const) —5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c. The shared namespace UUID used for every tile identity computation in this service and its onboard counterpart. MUST NOT be changed without coordinating a migration withgps-denied-onboard/components/c6_tile_cache/_uuid.py.Create(Guid namespaceId, string name) → Guid— produces a deterministic UUIDv5 by hashingnamespaceId.ToByteArrayBigEndian() || Encoding.UTF8.GetBytes(name)with SHA-1, then assembling the 16 bytes per RFC 9562:- bytes 0–3 are read as a big-endian uint32 (
time_low) - bytes 4–5 are read as a big-endian uint16 (
time_mid) - bytes 6–7 have their top 4 bits set to
0101(version 5) - byte 8 has its top 2 bits set to
10(variant RFC 4122 / 9562) - bytes 8–15 form the variant + clock_seq + node fields
- bytes 0–3 are read as a big-endian uint32 (
Create(Guid namespaceId, ReadOnlySpan<byte> name) → Guid— same as above but accepts a pre-encoded byte span; useful when the caller already has UTF-8 bytes or wants to avoid an intermediate string allocation.
Internal Logic
- The .NET 10
Guid.ToByteArray()method emits the first three fields in little-endian (Microsoft historical behavior); RFC 9562 requires big-endian. The module uses a localToBigEndianByteArray(Guid)helper that byte-swaps the first 4 bytes (time_low), the next 2 bytes (time_mid), and the next 2 bytes (time_hi_and_version) to produce the canonical big-endian layout before hashing. The same byte-swap is reversed when assembling the outputGuidfrom the hash digest, so the in-memoryGuidvalue still round-trips throughToString()to the expected hex form. - SHA-1 is invoked via
SHA1.HashData(buffer)(.NET 7+) which produces the 20-byte digest in one shot; only the first 16 bytes feed the resulting UUID (per RFC). - The function is allocation-light for typical tile-key sizes: the hash input buffer is stack-allocated via
Span<byte>when the namespace+name byte-length fits in 1024 bytes (always true for{z}/{x}/{y}and{z}/{x}/{y}/{source}/{flight_id}strings); larger payloads fall back to a pooledbyte[]. - The function is thread-safe (no shared mutable state).
Reference Vectors
SatelliteProvider.Tests/Uuidv5Tests.cs pins 10 reference vectors generated by Python (uuid.uuid5(TILE_NAMESPACE, name)). Each vector pairs an input name with the expected Guid string. The C# implementation must produce byte-identical output. Two representative pairs:
| Name | Expected UUIDv5 |
|---|---|
"18/12345/23456" |
38b26f49-a966-5121-aaf4-9cc476f57869 |
"18/12345/23456/google_maps/00000000-0000-0000-0000-000000000000" |
e228d1aa-25d4-556e-a72d-e0484756e165 |
The second value is observable end-to-end: a fresh GET /api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18 returns tileId = e228d1aa-25d4-556e-a72d-e0484756e165 because (47.461747, 37.647063) maps to slippy (z=18, x=158485, y=91707) — and the integration test asserts that exact value.
Dependencies
System.Security.Cryptography.SHA1System.Buffers.Binary.BinaryPrimitives(for big-endian byte-swaps)System.Buffers.ArrayPool<byte>(for the >1024-byte fallback path)
No third-party packages. No NuGet additions for AZ-503.
Consumers
SatelliteProvider.Services.TileDownloader.TileService.BuildTileEntity— computesIdandLocationHashfor every newly downloaded Google Maps tile.SatelliteProvider.Services.TileDownloader.UavTileUploadHandler.PersistAsync— computesIdandLocationHashfor every UAV upload.SatelliteProvider.IntegrationTests.UavUploadTests— seedslocation_hashvalues via raw SQL when bypassing the application code path.SatelliteProvider.IntegrationTests.MigrationTests— generates expected UUIDv5 outputs to validate migration 014'spg_temp.uuidv5PL/pgSQL backfill function.
Data Models
Operates only on Guid and string / Span<byte>. No persistence model.
Configuration
None. The namespace constant is pinned in source.
External Integrations
None (pure computation).
Security
The function is deterministic by design — it is NOT a cryptographic hash for security purposes. Two callers with the same (namespace, name) will always produce the same output. Treat the result as a content/location handle, not a secret. SHA-1 is used for RFC 9562 compatibility, not for collision resistance against an adversary.
Tests
SatelliteProvider.Tests/Uuidv5Tests.cs:
Create_MatchesPythonReferenceVectors_AC1— 10 reference vectors (AZ-503 AC-1).Create_IsDeterministic— re-running with the same inputs returns the sameGuid.Create_SetsVersionAndVariantBits— asserts the version nibble is5and the variant top-2-bits are10.