mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-23 18:51:14 +00:00
Add TileProvision configuration and gRPC service for tile delivery
- Introduced new TileProvision settings in appsettings.json, including MaxTilesPerBatch and ProgressEmitIntervalSeconds. - Configured TileProvisionConfig in Program.cs to bind the new settings. - Added gRPC service for RouteTileDelivery in Program.cs to handle tile delivery requests. - Updated SatelliteProvider.Api.csproj to include Grpc.AspNetCore package and added protobuf file for tile provision. - Enhanced AuthenticationServiceCollectionExtensions to handle JWT token extraction from the Authorization header. - Registered additional services in RouteManagementServiceCollectionExtensions for tile processing. These changes enhance the API's capability to manage tile provisioning and delivery efficiently.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package satellite.v1;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option csharp_namespace = "Satellite.V1";
|
||||
|
||||
service RouteTileDelivery {
|
||||
rpc DeliverRouteTiles(DeliverRouteTilesRequest) returns (stream RouteTileEvent);
|
||||
}
|
||||
|
||||
message DeliverRouteTilesRequest {
|
||||
RouteSpec route = 1;
|
||||
repeated ClientTileRecord client_tiles = 2;
|
||||
}
|
||||
|
||||
message RouteSpec {
|
||||
string route_id = 1;
|
||||
repeated Waypoint waypoints = 2;
|
||||
double region_size_meters = 3;
|
||||
int32 zoom = 4;
|
||||
repeated GeofencePolygon geofences = 5;
|
||||
bool include_geofence_tiles = 6;
|
||||
}
|
||||
|
||||
message Waypoint {
|
||||
double lat = 1;
|
||||
double lon = 2;
|
||||
}
|
||||
|
||||
message GeofencePolygon {
|
||||
repeated Waypoint vertices = 1;
|
||||
}
|
||||
|
||||
message ClientTileRecord {
|
||||
int32 z = 1;
|
||||
int32 x = 2;
|
||||
int32 y = 3;
|
||||
double resolution_m_per_px = 4;
|
||||
google.protobuf.Timestamp captured_at = 5;
|
||||
optional string source = 6;
|
||||
bytes content_sha256 = 7;
|
||||
}
|
||||
|
||||
message RouteTileEvent {
|
||||
oneof payload {
|
||||
RouteManifest manifest = 1;
|
||||
TileBatch batch = 2;
|
||||
ProgressUpdate progress = 3;
|
||||
DeliveryComplete complete = 4;
|
||||
DeliveryError error = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message RouteManifest {
|
||||
uint32 total_candidates = 1;
|
||||
uint32 skipped_by_client = 2;
|
||||
uint32 to_deliver = 3;
|
||||
}
|
||||
|
||||
message TileBatch {
|
||||
uint32 batch_seq = 1;
|
||||
repeated TilePayload tiles = 2;
|
||||
}
|
||||
|
||||
message TilePayload {
|
||||
int32 z = 1;
|
||||
int32 x = 2;
|
||||
int32 y = 3;
|
||||
double resolution_m_per_px = 4;
|
||||
google.protobuf.Timestamp captured_at = 5;
|
||||
string source = 6;
|
||||
bytes jpeg = 7;
|
||||
bytes content_sha256 = 8;
|
||||
uint32 route_priority = 9;
|
||||
}
|
||||
|
||||
message ProgressUpdate {
|
||||
uint32 delivered = 1;
|
||||
uint32 total = 2;
|
||||
uint32 downloading = 3;
|
||||
}
|
||||
|
||||
message DeliveryComplete {
|
||||
uint32 delivered = 1;
|
||||
uint32 skipped_client = 2;
|
||||
uint32 skipped_server_filter = 3;
|
||||
}
|
||||
|
||||
message DeliveryError {
|
||||
string code = 1;
|
||||
string message = 2;
|
||||
bool retryable = 3;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
# Contract: RouteTileDelivery (gRPC)
|
||||
|
||||
**Component**: c11_tilemanager (consumer), satellite-provider (producer)
|
||||
**Epic**: AZ-976
|
||||
**ADR**: ADR-013 (architecture.md)
|
||||
**Proto**: `tile_provision.proto` — `package satellite.v1`
|
||||
**Version**: 0.3.0
|
||||
**Status**: proposed
|
||||
**Last Updated**: 2026-06-19
|
||||
|
||||
## Purpose
|
||||
|
||||
Operator-side **pre-flight cache provisioning**. Client sends route + onboard tile catalog once; server streams `RouteTileEvent` messages until `DeliveryComplete` or `DeliveryError`.
|
||||
|
||||
satellite-provider does **not** receive `flight_id` — that is a C6 bookkeeping concern on the gps-denied side only (`route_id` is the wire correlation id).
|
||||
|
||||
C11/C12 on the **operator workstation** only. ADR-004: airborne image must not import stubs or open this channel.
|
||||
|
||||
## RPC
|
||||
|
||||
```protobuf
|
||||
service RouteTileDelivery {
|
||||
rpc DeliverRouteTiles(DeliverRouteTilesRequest) returns (stream RouteTileEvent);
|
||||
}
|
||||
```
|
||||
|
||||
| Concern | Rule |
|
||||
|---------|------|
|
||||
| Auth | gRPC metadata `authorization: Bearer <JWT>` |
|
||||
| TLS | Required in production; `SATELLITE_PROVIDER_TLS_INSECURE=1` dev knob |
|
||||
| Idempotency | `RouteSpec.route_id` (UUID string) |
|
||||
| Resume | Client persists last acked `batch_seq` per `route_id` locally (not on wire) |
|
||||
|
||||
## Request
|
||||
|
||||
### `DeliverRouteTilesRequest`
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `route` | Corridor geometry + single zoom |
|
||||
| `client_tiles` | Onboard inventory snapshot (route intersection only) |
|
||||
|
||||
### `RouteSpec`
|
||||
|
||||
| Field | Maps from gps-denied |
|
||||
|-------|----------------------|
|
||||
| `route_id` | Client-generated UUID per provision job |
|
||||
| `waypoints` | `replay_input.tlog_route.RouteSpec.waypoints` |
|
||||
| `region_size_meters` | `RouteSpec.suggested_region_size_meters` |
|
||||
| `zoom` | Single slippy zoom level (confirmed sufficient) |
|
||||
| `geofences` | Optional inclusion polygons |
|
||||
| `include_geofence_tiles` | Union geofence tiles with corridor grid |
|
||||
|
||||
### `ClientTileRecord`
|
||||
|
||||
Canonical key: **`(z, x, y)`**. `source` is informational only — **not** used in skip logic.
|
||||
|
||||
| Field | C6 mapping |
|
||||
|-------|------------|
|
||||
| `resolution_m_per_px` | RESTRICT-SAT-4 (lower = better) |
|
||||
| `captured_at` | `TileMetadata.capture_timestamp` |
|
||||
| `content_sha256` | `TileMetadata.content_sha256_hex` (raw 32 bytes) |
|
||||
|
||||
## Server skip rule (client catalog)
|
||||
|
||||
For each server candidate tile, **omit from stream** when `client_tiles` has matching `(z,x,y)` and **any** of:
|
||||
|
||||
1. `client.content_sha256` is non-empty and **equals** server payload hash → skip (byte-identical)
|
||||
2. `client.resolution_m_per_px <= server.resolution_m_per_px` **and** `client.captured_at >= server.captured_at` → skip (metadata-sufficient)
|
||||
|
||||
`source` is **not** compared.
|
||||
|
||||
`RouteManifest.skipped_by_client` counts tiles removed by this rule.
|
||||
|
||||
## Sector — not on this wire
|
||||
|
||||
**Sector** (`active_conflict` vs `stable_rear`) controls **how stale a tile may be before C6 rejects it on write** (AC-NEW-6 freshness). It is an operator decision about the geographic area, not something satellite-provider needs to deliver tiles.
|
||||
|
||||
| Layer | Who applies sector |
|
||||
|-------|-------------------|
|
||||
| satellite-provider | Does not need sector — streams tiles by route geometry |
|
||||
| C11 client write | Reads sector from **C11/C12 config** (same as today) when calling C6 freshness gate |
|
||||
|
||||
No `SectorClass` field on the gRPC request.
|
||||
|
||||
## Response stream: `RouteTileEvent`
|
||||
|
||||
Typical sequence:
|
||||
|
||||
1. **`RouteManifest`** — `total_candidates`, `skipped_by_client`, `to_deliver`
|
||||
2. **`TileBatch`** — monotonic `batch_seq`; on-disk hits first, then freshly fetched
|
||||
3. **`ProgressUpdate`** — optional
|
||||
4. **`DeliveryComplete`** or **`DeliveryError`**
|
||||
|
||||
### `DeliveryComplete` counters
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `delivered` | Tiles actually sent in `TileBatch` streams |
|
||||
| `skipped_client` | Same as manifest `skipped_by_client` (echo for client verify) |
|
||||
| `skipped_server_filter` | Tiles SP required but **did not send** after client dedup — see below |
|
||||
|
||||
#### `skipped_server_filter` — what counts
|
||||
|
||||
Tiles that entered the post-client-dedup work queue but never appeared in a batch:
|
||||
|
||||
| Reason | Example |
|
||||
|--------|---------|
|
||||
| **Fetch failed** | External imagery provider 404/timeout after retries |
|
||||
| **Below SP min resolution** | SP refuses to store/serve below its configured floor |
|
||||
| **Geometry clip** | Tile dropped after server-side corridor/geofence validation |
|
||||
| **Operational cap** | Job hit max-tiles / rate limit (if SP enforces) |
|
||||
|
||||
Tiles skipped by the **client catalog rule** are **not** included here (they are `skipped_client`).
|
||||
|
||||
If SP has no server-side filters in v1, `skipped_server_filter` may be **0**; the field is reserved for observability.
|
||||
|
||||
### `TilePayload`
|
||||
|
||||
| Field | Notes |
|
||||
|-------|-------|
|
||||
| `content_sha256` | 32-byte SHA-256 of `jpeg`; matches C6 DB invariant |
|
||||
| `route_priority` | Lower = earlier along route |
|
||||
|
||||
## Client write path (gps-denied)
|
||||
|
||||
`RouteTileDeliveryClient` (C11):
|
||||
|
||||
- Assigns C6 `flight_id` from operator context locally (not from SP)
|
||||
- Applies RESTRICT-SAT-4, **sector-based freshness**, AZ-308 budget, download journal
|
||||
- Resumes via persisted `route_id` + `batch_seq`
|
||||
|
||||
## Migration
|
||||
|
||||
REST `route_client` + `HttpTileDownloader` remain fallback until AZ-979 benchmark.
|
||||
|
||||
## Change log
|
||||
|
||||
| Version | Date | Change |
|
||||
|---------|------|--------|
|
||||
| 0.3.0 | 2026-06-19 | `ClientTileRecord.content_sha256`; sequential field nums on `TilePayload`; sector/flight_id off wire; skip rule + `skipped_server_filter` defined |
|
||||
| 0.2.0 | 2026-06-19 | `satellite.v1.RouteTileDelivery` + `RouteTileEvent` oneof |
|
||||
| 0.1.0 | 2026-06-19 | Initial draft (superseded) |
|
||||
Reference in New Issue
Block a user