Co-authored-by: Cursor <cursoragent@cursor.com>
5.1 KiB
Migrate UAV upload capturedAt to DateTimeOffset
Task: AZ-1126_captured_at_datetimeoffset
Name: Migrate UavTileMetadata.capturedAt to DateTimeOffset (F-AZ810-2)
Description: Close security carry-over F-AZ810-2 by typing UavTileMetadata.CapturedAt as DateTimeOffset instead of DateTime, eliminating ambiguous DateTimeKind.Unspecified handling on the UAV upload metadata input path.
Complexity: 2 points
Dependencies: AZ-810 (HARD — metadata validation layer); AZ-488 (original upload endpoint)
Component: SatelliteProvider.Common (UavTileMetadata) + SatelliteProvider.Api (validators) + SatelliteProvider.Services.TileDownloader (quality gate + upload handler)
Tracker: AZ-1126
Epic: AZ-795
Problem
Security finding F-AZ810-2 (cycle 8, open through cycle 12) flags that UavTileMetadata.CapturedAt is typed DateTime rather than DateTimeOffset. DateTime accepts DateTimeKind.Unspecified values that deserialize from offset-less ISO-8601 strings, forcing manual Kind normalization in the upload handler and quality gate. This is a time-handling correctness gap that can skew freshness-window checks in non-UTC dev environments.
Outcome
capturedAton the UAV upload metadata input is unambiguously UTC-aware at the type level- Offset-less or
Unspecifiedtimestamps are rejected before persistence - F-AZ810-2 is marked resolved in the next security audit cycle
- Wire compatibility preserved for clients sending ISO-8601 UTC with explicit offset (
Zor+00:00)
Scope
Included
- Change
UavTileMetadata.CapturedAtfromDateTimetoDateTimeOffset - Update FluentValidation rules, quality gate, and upload handler to compare via UTC without manual
Kindbranching - Reject offset-less / ambiguous
capturedAtvalues with HTTP 400 - Unit tests for the new rejection path and existing freshness-window rules
- Integration test proving offset-less
capturedAtis rejected - Patch
_docs/02_document/contracts/api/uav-tile-upload.md1.2.0 → 1.2.1 (change log + clarify offset requirement)
Excluded
TileInventoryEntry.CapturedAtresponse field (remainsDateTime?— DB read path)TileEntity.CapturedAtpersistence layer type- Changes to gRPC tile delivery or other non-UAV-upload surfaces
- MAJOR contract version bump (wire JSON shape unchanged for compliant clients)
Acceptance Criteria
AC-1: Type migration
Given the UAV upload metadata DTO
When deserialized from JSON
Then CapturedAt is DateTimeOffset and freshness comparisons use UTC without manual DateTimeKind normalization
AC-2: Reject ambiguous timestamps
Given a UAV upload batch with capturedAt lacking an explicit UTC offset (offset-less ISO string)
When POST /api/satellite/upload
Then HTTP 400 with a validation or deserialization error referencing capturedAt
AC-3: Backward-compatible UTC clients
Given a UAV upload batch with capturedAt as ISO-8601 UTC (...Z or ...+00:00)
When POST /api/satellite/upload with otherwise valid payload
Then HTTP 200 (or the same non-timestamp rejection as before timestamp validation)
AC-4: Contract patch
Given the implementation is complete
When uav-tile-upload.md is reviewed
Then version is 1.2.1 with a change-log entry documenting the offset requirement and F-AZ810-2 closure
Non-Functional Requirements
Compatibility
- Compliant clients already sending
Z-suffixed timestamps must not break
Security
- Closes F-AZ810-2 (Low / informational time-handling finding)
Unit Tests
| AC Ref | What to Test | Required Outcome |
|---|---|---|
| AC-1 | UavTileMetadataValidator freshness window with DateTimeOffset |
Future/too-old rules still fire |
| AC-2 | Deserializer or validator with offset-less capturedAt |
Rejected |
| AC-1 | UavTileQualityGate captured-at rules |
Still accept/reject correctly |
Blackbox Tests
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|---|---|---|---|---|
| AC-2 | Valid JPEG + metadata with capturedAt: "2026-06-26T12:00:00" (no offset) |
POST /api/satellite/upload |
HTTP 400 mentioning capturedAt |
— |
| AC-3 | Valid JPEG + metadata with capturedAt as DateTime.UtcNow.ToString("o") |
POST /api/satellite/upload |
HTTP 200 (happy path unchanged) | Compatibility |
Constraints
- Must remain a child of epic AZ-795 strict-validation theme
- No breaking wire change for offset-aware clients
Risks & Mitigation
Risk 1: Client sends offset-less timestamps
- Risk: Legitimate clients using
"2026-06-26T12:00:00"withoutZstart failing - Mitigation: Contract patch documents requirement; rejection is intentional per F-AZ810-2
Contract
This task patches the producer contract at _docs/02_document/contracts/api/uav-tile-upload.md (1.2.0 → 1.2.1).
Consumers: gps-denied-onboard, mission planner UI, any UAV upload client.
Document Dependencies
_docs/02_document/contracts/api/uav-tile-upload.mdv1.2.0 (patch to 1.2.1)_docs/02_document/contracts/api/error-shape.mdv1.0.0 (unchanged)