mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-26 06:21:13 +00:00
[AZ-1074] [AZ-1075] Cycle 9 closeout: security, tests, metrics
Resolve F-AZ1074-1/2 (collection caps, generic gRPC internal errors). Standalone integration compose stack, docs, security audit, perf and retro. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,7 +54,7 @@ public sealed class RouteTileDeliveryGrpcService : RouteTileDelivery.RouteTileDe
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Route tile delivery failed for route {RouteId}", routeId);
|
_logger.LogError(ex, "Route tile delivery failed for route {RouteId}", routeId);
|
||||||
await WriteErrorAsync(responseStream, "INTERNAL_ERROR", ex.Message, retryable: true, context.CancellationToken);
|
await WriteErrorAsync(responseStream, "INTERNAL_ERROR", "An internal error occurred.", retryable: true, context.CancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
@@ -214,6 +214,10 @@ public sealed class RouteTileDeliveryOrchestrator
|
|||||||
await sink.WriteCompleteAsync(delivered, skippedByClient, serverFiltered, cancellationToken);
|
await sink.WriteCompleteAsync(delivered, skippedByClient, serverFiltered, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int MaxWaypoints = 500;
|
||||||
|
private const int MaxGeofencePolygons = 50;
|
||||||
|
private const int MaxClientTiles = 5000;
|
||||||
|
|
||||||
private void ValidateJob(RouteTileDeliveryJob job)
|
private void ValidateJob(RouteTileDeliveryJob job)
|
||||||
{
|
{
|
||||||
if (job.RouteId == Guid.Empty)
|
if (job.RouteId == Guid.Empty)
|
||||||
@@ -226,6 +230,27 @@ public sealed class RouteTileDeliveryOrchestrator
|
|||||||
throw new ArgumentException("Route must have at least 2 waypoints", nameof(job));
|
throw new ArgumentException("Route must have at least 2 waypoints", nameof(job));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (job.Waypoints.Count > MaxWaypoints)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"waypoints must contain at most {MaxWaypoints} entries",
|
||||||
|
nameof(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.GeofenceVertices.Count > MaxGeofencePolygons)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"geofences must contain at most {MaxGeofencePolygons} polygons",
|
||||||
|
nameof(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.ClientTiles.Count > MaxClientTiles)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"client_tiles must contain at most {MaxClientTiles} entries",
|
||||||
|
nameof(job));
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < job.Waypoints.Count; i++)
|
for (var i = 0; i < job.Waypoints.Count; i++)
|
||||||
{
|
{
|
||||||
var (lat, lon) = job.Waypoints[i];
|
var (lat, lon) = job.Waypoints[i];
|
||||||
|
|||||||
@@ -37,6 +37,29 @@ public class RouteTileDeliveryOrchestratorTests
|
|||||||
.WithMessage("*lat must be between -90 and 90*");
|
.WithMessage("*lat must be between -90 and 90*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeliverAsync_TooManyWaypoints_Throws()
|
||||||
|
{
|
||||||
|
var expander = new RouteTileExpander(
|
||||||
|
new RoutePointGraphBuilder(Options.Create(new ProcessingConfig { MaxRoutePointSpacingMeters = 200 })),
|
||||||
|
new GeofenceGridCalculator());
|
||||||
|
var orchestrator = CreateOrchestrator(expander, Mock.Of<ITileRepository>(), Mock.Of<ITileService>());
|
||||||
|
var waypoints = Enumerable.Range(0, 501).Select(i => (47.0 + i * 0.0001, 37.0)).ToList();
|
||||||
|
var job = new RouteTileDeliveryJob(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
waypoints,
|
||||||
|
400,
|
||||||
|
18,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
[]);
|
||||||
|
|
||||||
|
var act = () => orchestrator.DeliverAsync(job, new RecordingSink(), CancellationToken.None);
|
||||||
|
|
||||||
|
await act.Should().ThrowAsync<ArgumentException>()
|
||||||
|
.WithMessage("*at most 500*");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DeliverAsync_AllTilesSkippedByClient_EmitsManifestAndCompleteWithZeroDelivered()
|
public async Task DeliverAsync_AllTilesSkippedByClient_EmitsManifestAndCompleteWithZeroDelivered()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ The cycle-1 (AZ-487) and cycle-2 (AZ-488) code reviews each surfaced an F1 (Low
|
|||||||
- **Imports from**: Common, DataAccess (uses `IRegionService` / `IRegionRequestQueue` from Common — no compile-time dependency on RegionProcessing)
|
- **Imports from**: Common, DataAccess (uses `IRegionService` / `IRegionRequestQueue` from Common — no compile-time dependency on RegionProcessing)
|
||||||
- **Consumed by**: WebApi
|
- **Consumed by**: WebApi
|
||||||
|
|
||||||
|
### Shared project: GrpcContracts (cycle 9 — AZ-1074)
|
||||||
|
|
||||||
|
- **Directory**: `SatelliteProvider.GrpcContracts/`
|
||||||
|
- **Purpose**: Canonical protobuf + generated C# stubs for gRPC wire contracts shared by API (server) and IntegrationTests (client).
|
||||||
|
- **Owns**: `SatelliteProvider.GrpcContracts/tile_provision.proto` (`package satellite.v1`; `GrpcServices=Both`)
|
||||||
|
- **ProjectReferences**: none (proto-only project)
|
||||||
|
- **Consumed by**: WebApi (`RouteTileDeliveryGrpcService`), IntegrationTests (`RouteTileDeliveryGrpcTests` via `GrpcTestHelpers`)
|
||||||
|
|
||||||
### Component: WebApi
|
### Component: WebApi
|
||||||
|
|
||||||
- **Directory**: `SatelliteProvider.Api/`
|
- **Directory**: `SatelliteProvider.Api/`
|
||||||
@@ -130,10 +138,11 @@ The cycle-1 (AZ-487) and cycle-2 (AZ-488) code reviews each surfaced an F1 (Low
|
|||||||
- `SatelliteProvider.Api/Validators/InventoryRequestValidator.cs` + `TileCoordValidator` (added by AZ-796; FluentValidation rules for `POST /api/satellite/tiles/inventory` — XOR `tiles`/`locationHashes`, per-array cap, slippy-map range checks)
|
- `SatelliteProvider.Api/Validators/InventoryRequestValidator.cs` + `TileCoordValidator` (added by AZ-796; FluentValidation rules for `POST /api/satellite/tiles/inventory` — XOR `tiles`/`locationHashes`, per-array cap, slippy-map range checks)
|
||||||
- `SatelliteProvider.Api/Validators/GlobalValidatorConfig.cs` (added by AZ-795/AZ-796; idempotent `ApplyOnce()` configures `ValidatorOptions.Global.PropertyNameResolver` so `errors`-map keys are camelCase per `error-shape.md` Inv-4; called from `Program.cs` and from the test assembly's `ModuleInitializer`)
|
- `SatelliteProvider.Api/Validators/GlobalValidatorConfig.cs` (added by AZ-795/AZ-796; idempotent `ApplyOnce()` configures `ValidatorOptions.Global.PropertyNameResolver` so `errors`-map keys are camelCase per `error-shape.md` Inv-4; called from `Program.cs` and from the test assembly's `ModuleInitializer`)
|
||||||
- `SatelliteProvider.Api/GlobalExceptionHandler.cs` (added by AZ-795; `IExceptionHandler` registered via `AddExceptionHandler<GlobalExceptionHandler>()`. Intercepts `BadHttpRequestException(JsonException)` from System.Text.Json's strict-parsing path — unknown-member rejection, missing required field via `[JsonRequired]`, JSON type mismatch — and emits `ValidationProblemDetails` with the same `errors[]` map shape that FluentValidation produces. 5xx errors pass through with sanitised body + `correlationId` per AZ-353.)
|
- `SatelliteProvider.Api/GlobalExceptionHandler.cs` (added by AZ-795; `IExceptionHandler` registered via `AddExceptionHandler<GlobalExceptionHandler>()`. Intercepts `BadHttpRequestException(JsonException)` from System.Text.Json's strict-parsing path — unknown-member rejection, missing required field via `[JsonRequired]`, JSON type mismatch — and emits `ValidationProblemDetails` with the same `errors[]` map shape that FluentValidation produces. 5xx errors pass through with sanitised body + `correlationId` per AZ-353.)
|
||||||
|
- `SatelliteProvider.Api/Grpc/RouteTileDeliveryGrpcService.cs` (added cycle 9 — AZ-1074; server-streaming gRPC adapter over `IRouteTileDeliveryOrchestrator`)
|
||||||
- **Internal**: (none)
|
- **Internal**: (none)
|
||||||
- **Owns**: `SatelliteProvider.Api/**`
|
- **Owns**: `SatelliteProvider.Api/**`
|
||||||
- **PackageReferences (added by AZ-487, bumped by AZ-496, then by AZ-500; AZ-795 added FluentValidation)**: `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.7 (pinned to the same minor patch as `Microsoft.AspNetCore.OpenApi` 10.0.7; AZ-496 bumped both packages from 8.0.21 → 8.0.25 in cycle 3 to close cycle-1 D1 + cycle-2 D3 supply-chain findings, then AZ-500 bumped both 8.0.25 → 10.0.7 in cycle 4 as part of the .NET 8 → .NET 10 migration; AZ-500 also bumped `Swashbuckle.AspNetCore` 6.6.2 → 10.1.7 here to land Microsoft.OpenApi 2.x compat required by ASP.NET Core 10). `FluentValidation` + `FluentValidation.DependencyInjectionExtensions` 12.0.0 added by AZ-795 to back the strict-input-validation epic.
|
- **PackageReferences (added by AZ-487, bumped by AZ-496, then by AZ-500; AZ-795 added FluentValidation)**: `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.7 (pinned to the same minor patch as `Microsoft.AspNetCore.OpenApi` 10.0.7; AZ-496 bumped both packages from 8.0.21 → 8.0.25 in cycle 3 to close cycle-1 D1 + cycle-2 D3 supply-chain findings, then AZ-500 bumped both 8.0.25 → 10.0.7 in cycle 4 as part of the .NET 8 → .NET 10 migration; AZ-500 also bumped `Swashbuckle.AspNetCore` 6.6.2 → 10.1.7 here to land Microsoft.OpenApi 2.x compat required by ASP.NET Core 10). `FluentValidation` + `FluentValidation.DependencyInjectionExtensions` 12.0.0 added by AZ-795 to back the strict-input-validation epic.
|
||||||
- **Imports from**: Common (incl. AZ-488 UAV DTOs + `UavQualityConfig`), DataAccess, TileDownloader (incl. AZ-488 `IUavTileUploadHandler`), RegionProcessing, RouteManagement
|
- **Imports from**: Common (incl. AZ-488 UAV DTOs + `UavQualityConfig`), DataAccess, TileDownloader (incl. AZ-488 `IUavTileUploadHandler`), RegionProcessing, RouteManagement, GrpcContracts (cycle 9)
|
||||||
- **Consumed by**: (none — top-level entry point)
|
- **Consumed by**: (none — top-level entry point)
|
||||||
|
|
||||||
## Shared / Cross-Cutting
|
## Shared / Cross-Cutting
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
|||||||
| POST | `/api/satellite/route` | `CreateRoute` | Create route with intermediate points. AZ-809 (cycle 8) added strict pre-handler validation via `WithValidation<CreateRouteRequest>()`: non-zero `id`, name length ∈ \[1, 200\], description length ≤ 1000, `regionSizeMeters` ∈ \[100, 10000\], `zoomLevel` ∈ \[0, 22\], `points` count ∈ \[2, 500\] with per-point lat/lon range checks, per-polygon NW-of-SE invariants, and the `createTilesZip ⇒ requestMaps` cross-field rule. Deserializer-layer failures (missing `[JsonRequired]` axes, unknown fields, type mismatches) are caught by `GlobalExceptionHandler` and produce the same RFC 7807 envelope. Contract: `_docs/02_document/contracts/api/route-creation.md` v1.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
| POST | `/api/satellite/route` | `CreateRoute` | Create route with intermediate points. AZ-809 (cycle 8) added strict pre-handler validation via `WithValidation<CreateRouteRequest>()`: non-zero `id`, name length ∈ \[1, 200\], description length ≤ 1000, `regionSizeMeters` ∈ \[100, 10000\], `zoomLevel` ∈ \[0, 22\], `points` count ∈ \[2, 500\] with per-point lat/lon range checks, per-polygon NW-of-SE invariants, and the `createTilesZip ⇒ requestMaps` cross-field rule. Deserializer-layer failures (missing `[JsonRequired]` axes, unknown fields, type mismatches) are caught by `GlobalExceptionHandler` and produce the same RFC 7807 envelope. Contract: `_docs/02_document/contracts/api/route-creation.md` v1.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
||||||
| GET | `/api/satellite/route/{id}` | `GetRoute` | Get route with all points |
|
| GET | `/api/satellite/route/{id}` | `GetRoute` | Get route with all points |
|
||||||
|
|
||||||
|
### gRPC Services (AZ-1074, cycle 9)
|
||||||
|
| Service | RPC | Handler | Description |
|
||||||
|
|---------|-----|---------|-------------|
|
||||||
|
| `RouteTileDelivery` | `DeliverRouteTiles(DeliverRouteTilesRequest) → stream RouteTileEvent` | `RouteTileDeliveryGrpcService` | Server-streaming route tile provisioning for gps-denied-onboard pre-flight cache fill. Reuses `RouteTileDeliveryOrchestrator` + tile download pipeline. Auth: gRPC metadata `authorization: Bearer <JWT>` (same HS256 contract as REST). Proto: `SatelliteProvider.GrpcContracts/tile_provision.proto` (`package satellite.v1`). Contract doc: `_docs/02_document/contracts/c11_tilemanager/tile_provision_grpc.md`. Invalid requests (single waypoint, out-of-range lat/lon/zoom) map to gRPC `InvalidArgument`. |
|
||||||
|
|
||||||
### Local Records (defined in Program.cs)
|
### Local Records (defined in Program.cs)
|
||||||
- `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs
|
- `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs
|
||||||
- `DownloadTileResponse` — tile download response
|
- `DownloadTileResponse` — tile download response
|
||||||
@@ -74,11 +79,12 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
|||||||
8. CORS policy: `TilesCors` — configured origins from `CorsConfig:AllowedOrigins`, falls back to allow-any
|
8. CORS policy: `TilesCors` — configured origins from `CorsConfig:AllowedOrigins`, falls back to allow-any
|
||||||
9. JSON options: camelCase, case-insensitive
|
9. JSON options: camelCase, case-insensitive
|
||||||
10. **JWT authentication (AZ-487 + AZ-494)**: `AddSatelliteJwt(builder.Configuration)` (extension in `SatelliteProvider.Api.Authentication`) registers `JwtBearer` with `TokenValidationParameters` set per the suite auth contract: signature + lifetime + issuer + audience validation, 30 s clock skew, ≥ 32-byte HMAC key. The `iss` value comes from `JWT_ISSUER` env (fallback `Jwt:Issuer` config); the `aud` value comes from `JWT_AUDIENCE` env (fallback `Jwt:Audience` config). All three values (secret, iss, aud) are fail-fast — the API throws `InvalidOperationException` at startup if any is unset or whitespace-only. Production deploys MUST set the env vars with admin-team-confirmed values; `appsettings.json` ships empty so the fail-fast triggers. `appsettings.Development.json` ships clearly-tagged DEV-ONLY values (`DEV-ONLY-iss-admin-azaion-local` / `DEV-ONLY-aud-satellite-provider`) so local dev works out-of-the-box. Followed by `AddAuthorization` with the `RequiresGpsPermission` policy (AZ-488).
|
10. **JWT authentication (AZ-487 + AZ-494)**: `AddSatelliteJwt(builder.Configuration)` (extension in `SatelliteProvider.Api.Authentication`) registers `JwtBearer` with `TokenValidationParameters` set per the suite auth contract: signature + lifetime + issuer + audience validation, 30 s clock skew, ≥ 32-byte HMAC key. The `iss` value comes from `JWT_ISSUER` env (fallback `Jwt:Issuer` config); the `aud` value comes from `JWT_AUDIENCE` env (fallback `Jwt:Audience` config). All three values (secret, iss, aud) are fail-fast — the API throws `InvalidOperationException` at startup if any is unset or whitespace-only. Production deploys MUST set the env vars with admin-team-confirmed values; `appsettings.json` ships empty so the fail-fast triggers. `appsettings.Development.json` ships clearly-tagged DEV-ONLY values (`DEV-ONLY-iss-admin-azaion-local` / `DEV-ONLY-aud-satellite-provider`) so local dev works out-of-the-box. Followed by `AddAuthorization` with the `RequiresGpsPermission` policy (AZ-488).
|
||||||
11. **Kestrel HTTP/2 (AZ-505)**: `builder.WebHost.ConfigureKestrel(opts => opts.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http1AndHttp2))`. The dev listener is now `https://+:8080` with a self-signed cert (`./certs/api.pfx`, generated idempotently by `scripts/run-tests.sh` and bound via `ASPNETCORE_Kestrel__Certificates__Default__Path` / `__Password` in `docker-compose.yml`). Kestrel needs TLS for HTTP/2 protocol negotiation; ALPN advertises both `h2` and `http/1.1` so HTTP/2-capable clients (browser Leaflet, `HttpClient` with `Version20` + `RequestVersionExact`, httpx `http2=True`) multiplex tile reads on a single TLS connection, and legacy clients fall back to HTTP/1.1. The integration-test container trusts the dev cert via `/usr/local/share/ca-certificates/` + `update-ca-certificates`. AZ-505 AC-5 verifies the multiplex semantics here; production termination is expected at the ingress (Envoy / nginx / ALB) — Kestrel can then drop to HTTP/2 cleartext behind it without changing this code.
|
11. **Kestrel HTTP/2 (AZ-505)**: `builder.WebHost.ConfigureKestrel(opts => opts.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http1AndHttp2))`. The dev listener is now `https://+:8080` with a self-signed cert (`./certs/api.pfx`, generated idempotently by `scripts/run-tests.sh` and bound via `ASPNETCORE_Kestrel__Certificates__Default__Path` / `__Password` in `docker-compose.yml`). Kestrel needs TLS for HTTP/2 protocol negotiation; ALPN advertises both `h2` and `http/1.1` so HTTP/2-capable clients (browser Leaflet, `HttpClient` with `Version20` + `RequestVersionExact`, httpx `http2=True`, gRPC over HTTP/2) multiplex tile reads on a single TLS connection, and legacy clients fall back to HTTP/1.1. The integration-test container trusts the dev cert via `/usr/local/share/ca-certificates/` + `update-ca-certificates`. AZ-505 AC-5 verifies the multiplex semantics here; production termination is expected at the ingress (Envoy / nginx / ALB) — Kestrel can then drop to HTTP/2 cleartext behind it without changing this code.
|
||||||
12. **ProblemDetails + global exception handler (AZ-795, cycle 7)**: `AddProblemDetails()` + `AddExceptionHandler<GlobalExceptionHandler>()` register the uniform RFC 7807 error pipeline. `app.UseExceptionHandler()` (in the middleware chain) routes unhandled exceptions through `GlobalExceptionHandler`, which converts `BadHttpRequestException(JsonException)` (unknown-member rejection, missing-required-field, JSON type mismatch) into `ValidationProblemDetails` with the same `errors[]` map shape that FluentValidation produces. This is the deserializer-layer half of the strict-validation contract — `error-shape.md` v1.0.0 §"Two collaborating pieces of shared infrastructure".
|
12. **gRPC (AZ-1074, cycle 9)**: `AddGrpc()` + `MapGrpcService<RouteTileDeliveryGrpcService>()`. Shares JWT auth middleware with REST — callers pass `authorization: Bearer <token>` in gRPC metadata. Server-streaming RPC delegates to `IRouteTileDeliveryOrchestrator.DeliverAsync`.
|
||||||
13. **Strict JSON parsing (AZ-795, cycle 7)**: `ConfigureHttpJsonOptions` sets `PropertyNamingPolicy = CamelCase`, `PropertyNameCaseInsensitive = true`, `UnmappedMemberHandling = Disallow`, and adds `JsonStringEnumConverter` with camelCase naming. `UnmappedMemberHandling.Disallow` is the key strict-parsing knob: any unknown root or nested field is rejected at the deserializer rather than silently dropped. Catches typos (`{"Z":12}` uppercase, `{"tileZoom":...}` post-rename) that no FluentValidation rule can see after deserialization.
|
13. **ProblemDetails + global exception handler (AZ-795, cycle 7)**: `AddProblemDetails()` + `AddExceptionHandler<GlobalExceptionHandler>()` register the uniform RFC 7807 error pipeline. `app.UseExceptionHandler()` (in the middleware chain) routes unhandled exceptions through `GlobalExceptionHandler`, which converts `BadHttpRequestException(JsonException)` (unknown-member rejection, missing-required-field, JSON type mismatch) into `ValidationProblemDetails` with the same `errors[]` map shape that FluentValidation produces. This is the deserializer-layer half of the strict-validation contract — `error-shape.md` v1.0.0 §"Two collaborating pieces of shared infrastructure".
|
||||||
14. **FluentValidation registration (AZ-795 + AZ-796, cycle 7)**: `AddValidatorsFromAssemblyContaining<Program>()` auto-registers every `IValidator<T>` in the API assembly (currently `InventoryRequestValidator` + `TileCoordValidator`, AZ-808 `RegionRequestValidator`, AZ-809 `CreateRouteRequestValidator` + `RoutePointValidator` + `GeofencePolygonValidator`, AZ-810 `UavTileBatchMetadataPayloadValidator` + `UavTileMetadataValidator`, AZ-811 `GetTileByLatLonQueryValidator`). `GlobalValidatorConfig.ApplyOnce()` runs the idempotent process-wide config — sets `ValidatorOptions.Global.PropertyNameResolver` so `errors` map keys are camelCase (matches the request body's casing per `error-shape.md` Inv-4). Per-endpoint opt-in via `.WithValidation<T>()` on the JSON-body endpoints — the generic `ValidationEndpointFilter<T>` resolves the validator from DI at request time and returns `Results.ValidationProblem` on failure.
|
14. **Strict JSON parsing (AZ-795, cycle 7)**: `ConfigureHttpJsonOptions` sets `PropertyNamingPolicy = CamelCase`, `PropertyNameCaseInsensitive = true`, `UnmappedMemberHandling = Disallow`, and adds `JsonStringEnumConverter` with camelCase naming. `UnmappedMemberHandling.Disallow` is the key strict-parsing knob: any unknown root or nested field is rejected at the deserializer rather than silently dropped. Catches typos (`{"Z":12}` uppercase, `{"tileZoom":...}` post-rename) that no FluentValidation rule can see after deserialization.
|
||||||
15. **AZ-810 multipart validation filter (cycle 8)**: `AddTransient<UavUploadValidationFilter>()` registers the bespoke filter used by `POST /api/satellite/upload`. The endpoint is `multipart/form-data` so the generic `.WithValidation<T>()` JSON-body filter cannot bind; this filter reads the `metadata` form field, deserializes it via the strict global `JsonSerializerOptions`, runs the FluentValidation chain, and enforces the cross-field `items.Count == files.Count` envelope rule. Wired on the endpoint with `.AddEndpointFilter<UavUploadValidationFilter>()` between `.RequireAuthorization(SatellitePermissions.UavUploadPolicy)` and the metadata accept/produces annotations.
|
15. **FluentValidation registration (AZ-795 + AZ-796, cycle 7)**: `AddValidatorsFromAssemblyContaining<Program>()` auto-registers every `IValidator<T>` in the API assembly (currently `InventoryRequestValidator` + `TileCoordValidator`, AZ-808 `RegionRequestValidator`, AZ-809 `CreateRouteRequestValidator` + `RoutePointValidator` + `GeofencePolygonValidator`, AZ-810 `UavTileBatchMetadataPayloadValidator` + `UavTileMetadataValidator`, AZ-811 `GetTileByLatLonQueryValidator`). `GlobalValidatorConfig.ApplyOnce()` runs the idempotent process-wide config — sets `ValidatorOptions.Global.PropertyNameResolver` so `errors` map keys are camelCase (matches the request body's casing per `error-shape.md` Inv-4). Per-endpoint opt-in via `.WithValidation<T>()` on the JSON-body endpoints — the generic `ValidationEndpointFilter<T>` resolves the validator from DI at request time and returns `Results.ValidationProblem` on failure.
|
||||||
|
16. **AZ-810 multipart validation filter (cycle 8)**: `AddTransient<UavUploadValidationFilter>()` registers the bespoke filter used by `POST /api/satellite/upload`. The endpoint is `multipart/form-data` so the generic `.WithValidation<T>()` JSON-body filter cannot bind; this filter reads the `metadata` form field, deserializes it via the strict global `JsonSerializerOptions`, runs the FluentValidation chain, and enforces the cross-field `items.Count == files.Count` envelope rule. Wired on the endpoint with `.AddEndpointFilter<UavUploadValidationFilter>()` between `.RequireAuthorization(SatellitePermissions.UavUploadPolicy)` and the metadata accept/produces annotations.
|
||||||
|
|
||||||
### Startup
|
### Startup
|
||||||
1. Database migration via `DatabaseMigrator.RunMigrations()` — throws on failure
|
1. Database migration via `DatabaseMigrator.RunMigrations()` — throws on failure
|
||||||
@@ -128,7 +134,8 @@ NuGet: `Serilog.AspNetCore` (8.0.3 — fallback retained on .NET 10 per AZ-500 R
|
|||||||
|
|
||||||
## Consumers
|
## Consumers
|
||||||
- HTTP clients (external)
|
- HTTP clients (external)
|
||||||
- Integration tests (via HTTP)
|
- gRPC clients (gps-denied-onboard C11 — AZ-1074)
|
||||||
|
- Integration tests (via HTTP + gRPC)
|
||||||
|
|
||||||
## Data Models
|
## Data Models
|
||||||
Defines several local request/response records that are not shared with other projects.
|
Defines several local request/response records that are not shared with other projects.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Console application that runs end-to-end integration tests against a live API in
|
|||||||
- `TileInventoryTests` (added cycle 6 — AZ-505) — `OrderingAndPresentAbsentShaping_AC1`, `LeafletReadReturnsMostRecentViaLocationHash_AC2`, `ValidationRejectsBothPopulated_AC6`, `ValidationRejectsNeitherPopulated_AC6`, `ValidationRejectsOversizedBatch_AC6`, `UnauthenticatedRequestReturns401_AC6`, `PerformanceBudget_AC4` (full-suite only). Tests are cycle-7-stable — they use the post-AZ-794 `{z, x, y}` wire shape and a minor x/y reduction was applied in cycle 7 to keep the synthetic coords within the z=18 slippy bounds enforced by `TileCoordValidator`.
|
- `TileInventoryTests` (added cycle 6 — AZ-505) — `OrderingAndPresentAbsentShaping_AC1`, `LeafletReadReturnsMostRecentViaLocationHash_AC2`, `ValidationRejectsBothPopulated_AC6`, `ValidationRejectsNeitherPopulated_AC6`, `ValidationRejectsOversizedBatch_AC6`, `UnauthenticatedRequestReturns401_AC6`, `PerformanceBudget_AC4` (full-suite only). Tests are cycle-7-stable — they use the post-AZ-794 `{z, x, y}` wire shape and a minor x/y reduction was applied in cycle 7 to keep the synthetic coords within the z=18 slippy bounds enforced by `TileCoordValidator`.
|
||||||
- `TileInventoryValidationTests` (added cycle 7 — AZ-796) — 16 tests: `HappyPath_Returns200`, `EmptyBody_Returns400`, `NeitherPopulated_Returns400`, `BothPopulated_Returns400`, `EmptyTilesArray_Returns400`, `TilesOverCap_Returns400`, `MissingZ_Returns400WithFieldPath`, `MissingXAndY_Returns400`, `ZoomOutOfRange_Returns400WithFieldPath`, `XBeyondZoomBounds_Returns400`, `YBeyondZoomBounds_Returns400`, `NegativeAxis_Returns400`, `UnknownRootField_Returns400`, `UnknownNestedField_Returns400`, `OldV1FieldName_Returns400` (AZ-794 + AZ-796 intersection — exact AZ-777 Phase 1 reproducer body, asserts legacy `tileZoom/tileX/tileY` now yields 400), `TypeMismatch_Returns400`. Each test exercises one of the 9 validation rules end-to-end through `ValidationEndpointFilter<TileInventoryRequest>` + `GlobalExceptionHandler`, asserts HTTP 400 + RFC 7807 `ValidationProblemDetails` shape via the shared `ProblemDetailsAssertions` helper.
|
- `TileInventoryValidationTests` (added cycle 7 — AZ-796) — 16 tests: `HappyPath_Returns200`, `EmptyBody_Returns400`, `NeitherPopulated_Returns400`, `BothPopulated_Returns400`, `EmptyTilesArray_Returns400`, `TilesOverCap_Returns400`, `MissingZ_Returns400WithFieldPath`, `MissingXAndY_Returns400`, `ZoomOutOfRange_Returns400WithFieldPath`, `XBeyondZoomBounds_Returns400`, `YBeyondZoomBounds_Returns400`, `NegativeAxis_Returns400`, `UnknownRootField_Returns400`, `UnknownNestedField_Returns400`, `OldV1FieldName_Returns400` (AZ-794 + AZ-796 intersection — exact AZ-777 Phase 1 reproducer body, asserts legacy `tileZoom/tileX/tileY` now yields 400), `TypeMismatch_Returns400`. Each test exercises one of the 9 validation rules end-to-end through `ValidationEndpointFilter<TileInventoryRequest>` + `GlobalExceptionHandler`, asserts HTTP 400 + RFC 7807 `ValidationProblemDetails` shape via the shared `ProblemDetailsAssertions` helper.
|
||||||
- `IdempotentPostTests` — pre-existing; cycle 7 adjusted the route-point payload from PascalCase (`Latitude`/`Longitude`) to camelCase (`lat`/`lon`) because the post-AZ-795 `UnmappedMemberHandling.Disallow` would otherwise reject the previously-silently-ignored fields. The `RoutePoint` DTO has carried `JsonPropertyName("lat"/"lon")` since AZ-309; cycle 7's strict JSON parsing exposed the test was sending the wrong shape and getting away with it via the pre-cycle-7 permissive deserializer.
|
- `IdempotentPostTests` — pre-existing; cycle 7 adjusted the route-point payload from PascalCase (`Latitude`/`Longitude`) to camelCase (`lat`/`lon`) because the post-AZ-795 `UnmappedMemberHandling.Disallow` would otherwise reject the previously-silently-ignored fields. The `RoutePoint` DTO has carried `JsonPropertyName("lat"/"lon")` since AZ-309; cycle 7's strict JSON parsing exposed the test was sending the wrong shape and getting away with it via the pre-cycle-7 permissive deserializer.
|
||||||
|
- `RouteTileDeliveryGrpcTests` (added cycle 9 — AZ-1074/AZ-1075) — `RunHappyPath`, `RunInvalidRequests` (single waypoint / lat out of range / zoom out of range → `InvalidArgument`), `RunBackpressureSafe` (slow consumer preserves JPEG + SHA256), `RunRestConsistency` (REST route CSV tile keys overlap gRPC stream keys). Wired into both smoke and full suites via `Program.cs`.
|
||||||
|
|
||||||
### Supporting Classes
|
### Supporting Classes
|
||||||
- `Models.cs` — HTTP response DTOs for deserialization
|
- `Models.cs` — HTTP response DTOs for deserialization
|
||||||
@@ -34,9 +35,11 @@ Console application that runs end-to-end integration tests against a live API in
|
|||||||
- `IntegrationTestDatabaseReset.cs` (AZ-493) — instance class with a single `EnsureCleanStateAsync()` method that truncates the integration-test target tables in FK-safe order. Guarded via `SatelliteProvider.TestSupport.IntegrationTestResetGuard` (env + Host allowlist) so it cannot run against a non-test database.
|
- `IntegrationTestDatabaseReset.cs` (AZ-493) — instance class with a single `EnsureCleanStateAsync()` method that truncates the integration-test target tables in FK-safe order. Guarded via `SatelliteProvider.TestSupport.IntegrationTestResetGuard` (env + Host allowlist) so it cannot run against a non-test database.
|
||||||
- `PerfBootstrap.cs` (AZ-492) — static helpers for the perf harness bootstrap subcommands. `MintToken()` mints a 4-hour HS256 token with subject `perf-tests` and a `permissions: GPS` claim via the canonical `SatelliteProvider.TestSupport.JwtTokenFactory.Create`; `GenerateUavFixture(args)` writes a 256×256 random-noise JPEG via `SixLabors.ImageSharp` to the path passed on the CLI. Invoked from `scripts/run-performance-tests.sh` via `dotnet <SatelliteProvider.IntegrationTests.dll> --mint-only` and `--gen-uav-fixture <path>`.
|
- `PerfBootstrap.cs` (AZ-492) — static helpers for the perf harness bootstrap subcommands. `MintToken()` mints a 4-hour HS256 token with subject `perf-tests` and a `permissions: GPS` claim via the canonical `SatelliteProvider.TestSupport.JwtTokenFactory.Create`; `GenerateUavFixture(args)` writes a 256×256 random-noise JPEG via `SixLabors.ImageSharp` to the path passed on the CLI. Invoked from `scripts/run-performance-tests.sh` via `dotnet <SatelliteProvider.IntegrationTests.dll> --mint-only` and `--gen-uav-fixture <path>`.
|
||||||
- `ProblemDetailsAssertions.cs` (added cycle 7 — AZ-795) — shared static helpers for asserting RFC 7807 ProblemDetails bodies on integration-test responses. `ReadProblemDetailsAsync(HttpResponseMessage, label)` deserialises the response body into a `JsonElement` with helpful failure messages when the content-type / shape doesn't match. `AssertProblemDetails(problem, expectedStatus, label)` asserts the base ProblemDetails shape (`type`, `title`, `status`). `AssertValidationProblem(problem, expectedStatus, label, expectedErrorPath?, expectedErrorContains?)` extends the base assertion to require the `errors` map per `error-shape.md` Inv-2 and optionally checks a specific field path / message substring. Consumed by `TileInventoryValidationTests`; designed to be reused by every future per-endpoint child task under AZ-795.
|
- `ProblemDetailsAssertions.cs` (added cycle 7 — AZ-795) — shared static helpers for asserting RFC 7807 ProblemDetails bodies on integration-test responses. `ReadProblemDetailsAsync(HttpResponseMessage, label)` deserialises the response body into a `JsonElement` with helpful failure messages when the content-type / shape doesn't match. `AssertProblemDetails(problem, expectedStatus, label)` asserts the base ProblemDetails shape (`type`, `title`, `status`). `AssertValidationProblem(problem, expectedStatus, label, expectedErrorPath?, expectedErrorContains?)` extends the base assertion to require the `errors` map per `error-shape.md` Inv-2 and optionally checks a specific field path / message substring. Consumed by `TileInventoryValidationTests`; designed to be reused by every future per-endpoint child task under AZ-795.
|
||||||
|
- `GrpcTestHelpers.cs` (added cycle 9 — AZ-1075) — gRPC client factory (`CreateClient` over TLS with dev cert trust), request builders, stream collector, and `ExpectInvalidArgumentAsync` assertion helper. Consumed exclusively by `RouteTileDeliveryGrpcTests`.
|
||||||
|
|
||||||
## Internal Logic
|
## Internal Logic
|
||||||
- Makes HTTP calls to the API at `API_URL` environment variable (default: `http://api:8080`)
|
- Makes HTTP calls to the API at `API_URL` environment variable (default: `https://api:8080` post-AZ-505 TLS)
|
||||||
|
- Makes gRPC calls to the same host via `Grpc.Net.Client` + generated stubs from `SatelliteProvider.GrpcContracts`
|
||||||
- Tests are methods called sequentially from `Program.cs` (not xUnit — plain console app)
|
- Tests are methods called sequentially from `Program.cs` (not xUnit — plain console app)
|
||||||
- Poll-based waiting for async operations (region/route completion)
|
- Poll-based waiting for async operations (region/route completion)
|
||||||
- Validates response structure, status transitions, file creation
|
- Validates response structure, status transitions, file creation
|
||||||
@@ -45,13 +48,13 @@ Console application that runs end-to-end integration tests against a live API in
|
|||||||
- `ProjectReference` to `SatelliteProvider.TestSupport` (added by AZ-491; provides `JwtTokenFactory`. Added by AZ-493; provides `IntegrationTestResetGuard`).
|
- `ProjectReference` to `SatelliteProvider.TestSupport` (added by AZ-491; provides `JwtTokenFactory`. Added by AZ-493; provides `IntegrationTestResetGuard`).
|
||||||
- Communicates with the API exclusively via HTTP for end-to-end tests; communicates with PostgreSQL directly only via the dedicated DB-reset hook + the existing `MigrationTests` schema assertions.
|
- Communicates with the API exclusively via HTTP for end-to-end tests; communicates with PostgreSQL directly only via the dedicated DB-reset hook + the existing `MigrationTests` schema assertions.
|
||||||
- NuGet: `Npgsql` 9.0.2 (Postgres client for DB-reset + MigrationTests), `SixLabors.ImageSharp` 3.1.11 (UAV fixture image generation).
|
- NuGet: `Npgsql` 9.0.2 (Postgres client for DB-reset + MigrationTests), `SixLabors.ImageSharp` 3.1.11 (UAV fixture image generation).
|
||||||
- ProjectReferences: `SatelliteProvider.Api` (running service for the integration runner), `SatelliteProvider.TestSupport` (canonical `JwtTokenFactory` + `IntegrationTestResetGuard`), `SatelliteProvider.Common` (added by AZ-503 so the `MultiSourceCoexistence_AZ484_Cycle2` seeder can compute `location_hash` via `Uuidv5.Create` instead of duplicating the UUIDv5 algorithm in T-SQL fixtures).
|
- ProjectReferences: `SatelliteProvider.Api` (running service for the integration runner), `SatelliteProvider.TestSupport` (canonical `JwtTokenFactory` + `IntegrationTestResetGuard`), `SatelliteProvider.Common` (added by AZ-503 so the `MultiSourceCoexistence_AZ484_Cycle2` seeder can compute `location_hash` via `Uuidv5.Create` instead of duplicating the UUIDv5 algorithm in T-SQL fixtures), `SatelliteProvider.GrpcContracts` (added cycle 9 — generated gRPC client stubs for `RouteTileDeliveryGrpcTests`).
|
||||||
|
|
||||||
## Consumers
|
## Consumers
|
||||||
- `docker-compose.tests.yml` — runs as a container that depends on the API service
|
- `docker-compose.tests.yml` — runs as a container that depends on the API service
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
- `API_URL` environment variable (set in docker-compose.tests.yml to `http://api:8080`)
|
- `API_URL` environment variable (set in docker-compose.tests.yml to `https://api:8080`)
|
||||||
- `INTEGRATION_TESTS_MODE` — `smoke` or `full` (default `full`). Drives `TestRunMode.Smoke`.
|
- `INTEGRATION_TESTS_MODE` — `smoke` or `full` (default `full`). Drives `TestRunMode.Smoke`.
|
||||||
- `INTEGRATION_KEEP_STATE` — set to `1` or `true` (or pass `--keep-state` to `Program.cs` / `scripts/run-tests.sh`) to skip the AZ-493 DB-reset hook. Useful for debugging a failed run.
|
- `INTEGRATION_KEEP_STATE` — set to `1` or `true` (or pass `--keep-state` to `Program.cs` / `scripts/run-tests.sh`) to skip the AZ-493 DB-reset hook. Useful for debugging a failed run.
|
||||||
- `ASPNETCORE_ENVIRONMENT=Testing` — guard for the DB-reset hook. The reset refuses to run unless this is set (see Reliability § Test isolation below).
|
- `ASPNETCORE_ENVIRONMENT=Testing` — guard for the DB-reset hook. The reset refuses to run unless this is set (see Reliability § Test isolation below).
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Ripple Log — Cycle 9
|
||||||
|
|
||||||
|
Tasks: AZ-1074, AZ-1075 (gRPC RouteTileDelivery + integration tests)
|
||||||
|
|
||||||
|
- `_docs/02_document/modules/api_program.md` — gRPC service registration + `RouteTileDeliveryGrpcService` (changed by AZ-1074)
|
||||||
|
- `_docs/02_document/modules/tests_integration.md` — `RouteTileDeliveryGrpcTests` + `GrpcTestHelpers` (changed by AZ-1075)
|
||||||
|
- `_docs/02_document/tests/blackbox-tests.md` — BT-32 gRPC scenarios (test-spec sync)
|
||||||
|
- `_docs/02_document/tests/traceability-matrix.md` — AZ-1074/1075 AC rows (test-spec sync)
|
||||||
|
|
||||||
|
No import-graph ripple beyond IntegrationTests → GrpcContracts (direct ProjectReference from task scope).
|
||||||
@@ -393,3 +393,22 @@ Cycle 8 extends the AZ-795 shared validation infrastructure (FluentValidation +
|
|||||||
**AC trace**: AZ-811 AC-1 (5 rules + ProblemDetails shape), AC-2 (happy path), AC-9 (the novel unknown-query-param envelope filter is documented in `_docs/02_document/modules/api_program.md` for reuse).
|
**AC trace**: AZ-811 AC-1 (5 rules + ProblemDetails shape), AC-2 (happy path), AC-9 (the novel unknown-query-param envelope filter is documented in `_docs/02_document/modules/api_program.md` for reuse).
|
||||||
**Notes**: This is the first endpoint to need a generic **unknown-query-param rejection** layer — ASP.NET's default model binder silently ignores unknown query parameters (parallel to `UnmappedMemberHandling.Disallow` for JSON bodies, but no built-in equivalent exists for query strings). The new `UnknownQueryParameterEndpointFilter` introspects the route's declared parameters and rejects any extra keys. Sub-cases `4b` and `4c` exercise this filter: `4b` proves the pre-AZ-811 wire format (`?Latitude=&Longitude=&ZoomLevel=`) that silently fell back to `lat=0, lon=0, zoom=0` now fails fast with HTTP 400 naming all three unknown keys; `4c` proves the same path catches arbitrary hostile / typo keys. The filter is designed for reuse by any future query-param endpoint (AZ-811 AC-9).
|
**Notes**: This is the first endpoint to need a generic **unknown-query-param rejection** layer — ASP.NET's default model binder silently ignores unknown query parameters (parallel to `UnmappedMemberHandling.Disallow` for JSON bodies, but no built-in equivalent exists for query strings). The new `UnknownQueryParameterEndpointFilter` introspects the route's declared parameters and rejects any extra keys. Sub-cases `4b` and `4c` exercise this filter: `4b` proves the pre-AZ-811 wire format (`?Latitude=&Longitude=&ZoomLevel=`) that silently fell back to `lat=0, lon=0, zoom=0` now fails fast with HTTP 400 naming all three unknown keys; `4c` proves the same path catches arbitrary hostile / typo keys. The filter is designed for reuse by any future query-param endpoint (AZ-811 AC-9).
|
||||||
|
|
||||||
|
## BT-32: gRPC RouteTileDelivery — Happy Path + Validation + REST Consistency
|
||||||
|
|
||||||
|
**Trigger**: `RouteTileDelivery.DeliverRouteTiles(DeliverRouteTilesRequest)` over gRPC (TLS to `https://api:8080` inside docker-compose; metadata `authorization: Bearer <JWT>`). Contract: `_docs/02_document/contracts/c11_tilemanager/tile_provision_grpc.md` + `SatelliteProvider.GrpcContracts/tile_provision.proto`.
|
||||||
|
**Precondition**: API + Postgres up via `docker-compose.tests.yml`; JWT env vars set; dev TLS cert trusted in integration-tests container.
|
||||||
|
**Expected stream sequence**: `RouteManifest` → zero or more `TileChunk` → `DeliveryComplete` (or `DeliveryError` on failure paths).
|
||||||
|
|
||||||
|
| # | Scenario | Trigger excerpt | Expected | Test method |
|
||||||
|
|---|----------|-----------------|----------|-------------|
|
||||||
|
| pos | Happy path | 2 waypoints (48.276067,37.384458) → (48.270740,37.374029), regionSize=500, zoom=18 | `RouteManifest` with `to_deliver ≥ 0`; at least one `TileChunk` when `to_deliver > 0`; terminal `DeliveryComplete` | `RunHappyPath` |
|
||||||
|
| 1 | Single waypoint | `waypoints` count = 1 | gRPC `StatusCode.InvalidArgument` before stream completes | `RunInvalidRequests` ("Single waypoint route") |
|
||||||
|
| 2 | Lat out of range | first waypoint `lat=91` | gRPC `StatusCode.InvalidArgument` | `RunInvalidRequests` ("Latitude out of range") |
|
||||||
|
| 3 | Zoom out of range | `zoom=99` | gRPC `StatusCode.InvalidArgument` | `RunInvalidRequests` ("Zoom out of allowed range") |
|
||||||
|
| 4 | Backpressure safe | same as happy path; consumer delays 50 ms between stream events | every `TileChunk.jpeg` starts with `FF D8`; `content_sha256` matches SHA-256 of JPEG bytes | `RunBackpressureSafe` |
|
||||||
|
| 5 | REST consistency | REST `POST /api/satellite/route` with `requestMaps=true` for same geometry; then gRPC stream for same corridor | gRPC tile keys `(z,x,y)` overlap REST route CSV tile keys by ≥ 1 | `RunRestConsistency` |
|
||||||
|
|
||||||
|
**Pass criterion**: All sub-cases pass in full integration mode (`INTEGRATION_TESTS_MODE=full`). No `DeliveryError` on happy path / backpressure path. Invalid requests fail with `InvalidArgument` (not `Internal` / `Unknown`). REST overlap count > 0.
|
||||||
|
**AC trace**: AZ-1074 AC-1..AC-4; AZ-1075 AC-1..AC-3.
|
||||||
|
**Notes**: gRPC is additive — REST route endpoints (BT-06..BT-12) remain unchanged. Cache-reuse (AZ-1074 AC-2) is covered structurally by the orchestrator unit tests (`RouteTileDeliveryOrchestratorTests.DeliverAsync_CachedTileOnDisk_EmitsBatchWithoutDownload`) plus the integration happy path reusing tiles seeded by prior REST runs in the same compose volume. Consumer-side tests (gps-denied-onboard AZ-1076) are out of scope.
|
||||||
|
|
||||||
|
|||||||
@@ -263,3 +263,18 @@
|
|||||||
- AZ-809 ACs 9 + 10 are **advisory** (surfaced for parent-suite team decision, not implemented or tested this cycle). Matrix marks them `◐ advisory (not tested)`. They're recorded so the next cycle / parent-suite review sees them without having to re-discover them from the task spec. AC-9: `RequestRegionRequest.sizeMeters` vs `CreateRouteRequest.regionSizeMeters` naming inconsistency. AC-10: input `points: [{lat, lon}]` vs output `points: [{latitude, longitude}]` round-trip asymmetry on the route endpoint. Either keep + document, or harmonize in a follow-up MAJOR contract bump for both — parent-suite team's call.
|
- AZ-809 ACs 9 + 10 are **advisory** (surfaced for parent-suite team decision, not implemented or tested this cycle). Matrix marks them `◐ advisory (not tested)`. They're recorded so the next cycle / parent-suite review sees them without having to re-discover them from the task spec. AC-9: `RequestRegionRequest.sizeMeters` vs `CreateRouteRequest.regionSizeMeters` naming inconsistency. AC-10: input `points: [{lat, lon}]` vs output `points: [{latitude, longitude}]` round-trip asymmetry on the route endpoint. Either keep + document, or harmonize in a follow-up MAJOR contract bump for both — parent-suite team's call.
|
||||||
- AZ-810 AC-9 (no AZ-488 regression) has a **process annotation**: cycle 8's batch_04 report originally claimed AC-9 "verified by tracing each AZ-488 test payload's metadata shape against the new rules" without running the integration suite. That verification was a false-PASS — the suite was actually red on the AZ-488 happy path because `UavUploadTests.NextTestCoordinate()` produced lat > 90° (a pre-existing latent bug masked by the absence of any validator before AZ-810). The bug was fixed by clamping the test-data generator to OSM-valid ranges in commit `b763da3` and AC-9 is now bound to the green full-suite re-run, not to source tracing. Process lesson recorded in `_docs/LESSONS.md` (2026-05-23).
|
- AZ-810 AC-9 (no AZ-488 regression) has a **process annotation**: cycle 8's batch_04 report originally claimed AC-9 "verified by tracing each AZ-488 test payload's metadata shape against the new rules" without running the integration suite. That verification was a false-PASS — the suite was actually red on the AZ-488 happy path because `UavUploadTests.NextTestCoordinate()` produced lat > 90° (a pre-existing latent bug masked by the absence of any validator before AZ-810). The bug was fixed by clamping the test-data generator to OSM-valid ranges in commit `b763da3` and AC-9 is now bound to the green full-suite re-run, not to source tracing. Process lesson recorded in `_docs/LESSONS.md` (2026-05-23).
|
||||||
- Cycle-update rule check: no NFR conflicts. Range bounds (`lat ∈ [-90, 90]`, `lon ∈ [-180, 180]`, `zoom ∈ [0, 22]`, `tileSizeMeters > 0`) are reaffirmed across all 4 endpoints — they were never previously contested. The error-shape contract (`error-shape.md` v1.0.0 from cycle 7) is reused unchanged.
|
- Cycle-update rule check: no NFR conflicts. Range bounds (`lat ∈ [-90, 90]`, `lon ∈ [-180, 180]`, `zoom ∈ [0, 22]`, `tileSizeMeters > 0`) are reaffirmed across all 4 endpoints — they were never previously contested. The error-shape contract (`error-shape.md` v1.0.0 from cycle 7) is reused unchanged.
|
||||||
|
|
||||||
|
| AZ-1074 AC-1 | Happy path streams tiles — `RouteManifest` + ≥1 `TileChunk` + `DeliveryComplete` | BT-32 pos (blackbox); `RouteTileDeliveryGrpcTests.RunHappyPath` (integration) | ✓ |
|
||||||
|
| AZ-1074 AC-2 | Cached tiles served without redundant Google Maps download | `RouteTileDeliveryOrchestratorTests.DeliverAsync_CachedTileOnDisk_EmitsBatchWithoutDownload` (unit); BT-32 pos reuses compose volume cache (integration) | ✓ |
|
||||||
|
| AZ-1074 AC-3 | Invalid route / coordinates rejected with `INVALID_ARGUMENT` | BT-32 sub-cases 1–3 (blackbox); `RouteTileDeliveryGrpcTests.RunInvalidRequests` (integration) | ✓ |
|
||||||
|
| AZ-1074 AC-4 | Slow consumer — tile bytes not corrupted | BT-32 sub-case 4 (blackbox); `RouteTileDeliveryGrpcTests.RunBackpressureSafe` (integration) | ✓ |
|
||||||
|
| AZ-1075 AC-1 | gRPC happy-path passes in docker-compose full run | Full `scripts/run-tests.sh --full` / `docker-compose.tests.yml` (cycle 9 Step 11 — passed) | ✓ |
|
||||||
|
| AZ-1075 AC-2 | Each invalid variant returns expected gRPC status | BT-32 sub-cases 1–3; `RouteTileDeliveryGrpcTests.RunInvalidRequests` | ✓ |
|
||||||
|
| AZ-1075 AC-3 | REST and gRPC tile metadata consistent for same route | BT-32 sub-case 5; `RouteTileDeliveryGrpcTests.RunRestConsistency` | ✓ |
|
||||||
|
|
||||||
|
**Coverage shape notes (Cycle 9 — AZ-1074 + AZ-1075 gRPC RouteTileDelivery):**
|
||||||
|
- Cycle 9 adds the first gRPC blackbox surface alongside the existing REST suite. BT-32 is the binding blackbox spec; integration coverage lives in `RouteTileDeliveryGrpcTests` wired into both smoke and full suites via `Program.cs`.
|
||||||
|
- Proto source of truth moved to `SatelliteProvider.GrpcContracts/tile_provision.proto` (GrpcServices=Both); contract doc at `_docs/02_document/contracts/c11_tilemanager/tile_provision_grpc.md`.
|
||||||
|
- Cycle 9 Step 11 initially failed integration startup due to host port 5433 conflict with sibling project `fleet-viewer-dev-db`. Fixed by making `docker-compose.tests.yml` self-contained (no host port publishing — compose-internal networking only) and pointing `scripts/run-tests.sh` at that file alone for integration runs. Unit count is now 448 (includes orchestrator + gRPC validation tests).
|
||||||
|
- No perf / security NFRs declared in AZ-1074/1075 task specs beyond existing JWT-on-gRPC-metadata (inherits AZ-487/494 invariants). Load testing explicitly excluded.
|
||||||
|
- Cycle-update rule check: no NFR conflicts.
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Deploy Report — Cycle 9 (AZ-1074 + AZ-1075)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Cycle**: 9
|
||||||
|
**Scope**: gRPC RouteTileDelivery streaming service (AZ-1074) + integration test coverage (AZ-1075).
|
||||||
|
|
||||||
|
## What is shipping
|
||||||
|
|
||||||
|
### Code changes (uncommitted on `dev` at report time)
|
||||||
|
|
||||||
|
| Area | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `SatelliteProvider.GrpcContracts/` | New shared project — `tile_provision.proto`, GrpcServices=Both |
|
||||||
|
| `SatelliteProvider.Api/Grpc/RouteTileDeliveryGrpcService.cs` | `[Authorize]` streaming RPC; generic internal error to client |
|
||||||
|
| `SatelliteProvider.Api/Program.cs` | `AddGrpc` (16 MiB recv / 64 MiB send); `MapGrpcService` after auth |
|
||||||
|
| `RouteTileDeliveryOrchestrator` | Collection caps (500/50/5000); lat/lon validation |
|
||||||
|
| Integration tests | `RouteTileDeliveryGrpcTests`, `GrpcTestHelpers`; standalone `docker-compose.tests.yml` |
|
||||||
|
| Docs / security | Cycle-9 module docs, traceability, security reports + hardening |
|
||||||
|
|
||||||
|
### Database migrations
|
||||||
|
|
||||||
|
**None.**
|
||||||
|
|
||||||
|
### Configuration changes
|
||||||
|
|
||||||
|
| Setting | Change |
|
||||||
|
|---------|--------|
|
||||||
|
| New env vars | **None** — gRPC uses existing JWT auth |
|
||||||
|
| `docker-compose.tests.yml` | Self-contained test stack (no host port publish) |
|
||||||
|
| Container image | Same base `mcr.microsoft.com/dotnet/aspnet:10.0`; Dockerfile copies GrpcContracts |
|
||||||
|
|
||||||
|
### Contract changes (consumer-visible)
|
||||||
|
|
||||||
|
| Contract | Change | Consumer action |
|
||||||
|
|----------|--------|-----------------|
|
||||||
|
| gRPC `RouteTileDelivery.DeliverRouteTiles` | **New** — server-streaming tile delivery | Clients import `SatelliteProvider.GrpcContracts`; Bearer/metadata auth required |
|
||||||
|
| REST endpoints | Unchanged wire shape | No action |
|
||||||
|
|
||||||
|
## Verification gates passed in this cycle
|
||||||
|
|
||||||
|
| Gate | Result | Evidence |
|
||||||
|
|------|--------|----------|
|
||||||
|
| Step 11 — Functional tests | **PASS** | 448 unit + integration exit 0 (`scripts/run-tests.sh --full`) |
|
||||||
|
| Step 12 — Test-Spec Sync | **PASS** | BT-32 gRPC scenarios; traceability matrix updated |
|
||||||
|
| Step 13 — Update Docs | **PASS** | `api_program.md`, `tests_integration.md`, `module-layout.md`, ripple log |
|
||||||
|
| Step 14 — Security Audit | **PASS** (delta) | `security_report_cycle9.md`; F-AZ1074-1/2 resolved in follow-up |
|
||||||
|
| Step 15 — Performance Test | **PASS** | `perf_2026-06-25_cycle9.md` — 8/8 REST scenarios, exit 0 |
|
||||||
|
|
||||||
|
## Outstanding carry-overs (unchanged)
|
||||||
|
|
||||||
|
- F-AZ795-1, F-AZ795-2 — REST error message sanitization
|
||||||
|
- D-AZ795-1 — FluentValidation 12.0.0 → 12.1.1
|
||||||
|
- D2-cy4 — test SDK transitive advisory
|
||||||
|
|
||||||
|
## Operator runbook
|
||||||
|
|
||||||
|
1. **Commit and push** cycle-9 changes to `origin/dev`; confirm Woodpecker `01-test` green.
|
||||||
|
2. **No migration** — deploy new image only.
|
||||||
|
3. **Smoke-test** after deploy:
|
||||||
|
- REST: existing endpoints (401 without JWT, 200 with JWT)
|
||||||
|
- gRPC: `DeliverRouteTiles` with valid JWT — expect manifest + tile chunks or InvalidArgument on bad input
|
||||||
|
4. **Ingress**: ensure HTTP/2 (gRPC) is forwarded if clients connect through ingress; dev stack uses TLS on 18980.
|
||||||
|
|
||||||
|
## Recommended follow-up PBIs
|
||||||
|
|
||||||
|
| Estimate | Title |
|
||||||
|
|----------|-------|
|
||||||
|
| 3 SP | PT-10 gRPC stream latency / backpressure perf scenario |
|
||||||
|
| 2 SP | REST error sanitizer pass (F-AZ795-1/2) |
|
||||||
|
| 1 SP | FluentValidation 12.0.0 → 12.1.1 (D-AZ795-1) |
|
||||||
|
| 1 SP | Document postgres port conflict workaround for parallel dev stacks |
|
||||||
|
|
||||||
|
**Verdict**: Cleared for Step 16.5 (Release) pending operator commit/push and environment selection.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Dependency Scan (Cycle 9)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: Cycle-9 delta over cycle-8 (`dependency_scan_cycle8.md`). Surface = AZ-1074/AZ-1075 gRPC RouteTileDelivery + `SatelliteProvider.GrpcContracts`.
|
||||||
|
**Method**: `dotnet list SatelliteProvider.sln package --vulnerable --include-transitive` via Docker SDK 10.0 image + manifest diff on new/changed csproj files.
|
||||||
|
|
||||||
|
## Cycle-9 Package Manifest Diff
|
||||||
|
|
||||||
|
| csproj | Cycle 8 baseline | Cycle 9 change |
|
||||||
|
|--------|------------------|----------------|
|
||||||
|
| `SatelliteProvider.Api/SatelliteProvider.Api.csproj` | unchanged | **+1** `Grpc.AspNetCore` 2.71.0 |
|
||||||
|
| `SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj` | **NEW** | `Google.Protobuf` 3.31.1, `Grpc.AspNetCore` 2.71.0, `Grpc.Tools` 2.71.0 (PrivateAssets) |
|
||||||
|
| All other csproj | unchanged | **+0** |
|
||||||
|
|
||||||
|
## Vulnerable Package Scan (2026-06-25)
|
||||||
|
|
||||||
|
| Project | Finding | Severity | Notes |
|
||||||
|
|---------|---------|----------|-------|
|
||||||
|
| `SatelliteProvider.Api` | none | — | Includes new `Grpc.AspNetCore` 2.71.0 — clean |
|
||||||
|
| `SatelliteProvider.GrpcContracts` | none | — | New project — clean |
|
||||||
|
| `SatelliteProvider.IntegrationTests` | transitive `Microsoft.IdentityModel.JsonWebTokens` 7.0.3, `System.IdentityModel.Tokens.Jwt` 7.0.3 | Moderate | GHSA-59j7-ghrg-fj52 — **test-runtime only** (pre-existing; unchanged by cycle 9) |
|
||||||
|
| `SatelliteProvider.TestSupport` | same JWT packages 7.0.3 | Moderate | test-runtime only — pre-existing |
|
||||||
|
|
||||||
|
## Cycle-9 Findings
|
||||||
|
|
||||||
|
**No new dependency CVEs** from the gRPC package additions. Grpc.AspNetCore 2.71.0 / Google.Protobuf 3.31.1 report clean against NuGet advisory feed at scan time.
|
||||||
|
|
||||||
|
## Carry-overs
|
||||||
|
|
||||||
|
- **D-AZ795-1** (Low): FluentValidation 12.0.0 → 12.1.1 hardening — still open
|
||||||
|
- **D2-cy4** (Medium, test-runtime): `Microsoft.NET.Test.Sdk` transitive — still open
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-9 delta) — zero new CVEs in production/runtime packages.
|
||||||
|
|
||||||
|
Cumulative: **PASS_WITH_WARNINGS** — D2-cy4 + D-AZ795-1 carry-overs unchanged.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Infrastructure & Configuration Review (Cycle 9)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: Cycle-9 infrastructure changes only.
|
||||||
|
|
||||||
|
| File | Change | Security relevance |
|
||||||
|
|------|--------|-------------------|
|
||||||
|
| `docker-compose.tests.yml` | Rewritten as self-contained stack; **no host port publishing** for postgres/api | **Positive** — avoids port conflicts; reduces accidental exposure of test DB/API to host network |
|
||||||
|
| `scripts/run-tests.sh` | Integration runs use `docker-compose.tests.yml` only | Aligns with above |
|
||||||
|
| `SatelliteProvider.Api/Dockerfile` | Added `GrpcContracts` csproj COPY | Build-order only; no new secrets |
|
||||||
|
| `SatelliteProvider.IntegrationTests/Dockerfile` | `linux/amd64` platform; `aspnet:10.0` runtime for Grpc.AspNetCore | Protoc/build stability; no new exposed ports |
|
||||||
|
| `docker-compose.yml` (dev) | Unchanged | Host ports 5433/18980 still published for local dev — pre-existing |
|
||||||
|
| CI/CD, `.env`, `appsettings.*` | Unchanged | — |
|
||||||
|
|
||||||
|
## Container checks (carried forward)
|
||||||
|
|
||||||
|
| Check | Status |
|
||||||
|
|-------|--------|
|
||||||
|
| Non-root user in API image | Still runs as root (pre-existing; not cycle-9 regression) |
|
||||||
|
| Secrets in build args | None |
|
||||||
|
| Dev TLS cert gitignored | `./certs/` — unchanged |
|
||||||
|
| JWT via env vars | Unchanged |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-9 delta) — test harness change improves isolation; no new misconfiguration.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# OWASP Top 10 Review (Cycle 9)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Framework**: OWASP Top 10:2021
|
||||||
|
**Scope**: Cycle-9 gRPC delta (AZ-1074/AZ-1075)
|
||||||
|
|
||||||
|
| Category | Status (cycle-9 delta) | Notes |
|
||||||
|
|----------|------------------------|-------|
|
||||||
|
| A01 — Broken Access Control | **PASS** | `[Authorize]` on gRPC service; anonymous calls rejected (integration tests cover JWT baseline) |
|
||||||
|
| A02 — Cryptographic Failures | **N/A** | TLS via Kestrel dev cert / production ingress — unchanged pattern from AZ-505 |
|
||||||
|
| A03 — Injection | **PASS** | No new string-built SQL; tile coords validated before expand |
|
||||||
|
| A04 — Insecure Design | **PASS (post-follow-up)** | F-AZ1074-1 unbounded collections **resolved** — caps aligned with REST |
|
||||||
|
| A05 — Security Misconfiguration | **PASS** | gRPC message size limits set; test compose no longer publishes DB port to host |
|
||||||
|
| A06 — Vulnerable Components | **PASS_WITH_WARNINGS** | New Grpc.AspNetCore 2.71.0 clean; D-AZ795-1 + D2-cy4 carry-overs |
|
||||||
|
| A07 — Auth Failures | **PASS** | Same JWT contract as REST; gRPC metadata `Authorization: Bearer` |
|
||||||
|
| A08 — Data Integrity Failures | **N/A** | No CI/CD or signing changes |
|
||||||
|
| A09 — Logging Failures | **PASS_WITH_WARNINGS** | F-AZ1074-2 **resolved**; F-AZ795-1/F-AZ795-2 REST carry-overs still open |
|
||||||
|
| A10 — SSRF | **N/A** | No URL inputs in gRPC contract |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS_WITH_WARNINGS** cumulative (REST carry-overs). Cycle-9 delta: **PASS** after Step-14 follow-up fixes.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Security Audit Report (Cycle 9)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Scope**: Cycle-9 delta — AZ-1074 (gRPC RouteTileDelivery service) + AZ-1075 (integration tests) + `SatelliteProvider.GrpcContracts`.
|
||||||
|
**Trigger**: `/autodev` Step 14 — user chose **A) Run security audit**.
|
||||||
|
**Verdict (cycle-9 delta, post-follow-up)**: **PASS** — 0 Medium open, 1 Low resolved in follow-up, 0 new Critical/High.
|
||||||
|
**Verdict (cumulative)**: **PASS_WITH_WARNINGS** — cycle-4/7/8 carry-overs unchanged (D2-cy4, D-AZ795-1, F-AZ795-1, F-AZ795-2, F-AZ810-1, F-AZ810-2).
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Severity | Cycle 9 at audit | Post Step-14 follow-up | Cumulative open |
|
||||||
|
|----------|------------------|------------------------|-----------------|
|
||||||
|
| Critical | 0 | 0 | 0 |
|
||||||
|
| High | 0 | 0 | 0 |
|
||||||
|
| Medium | 1 (F-AZ1074-1) | **0 — RESOLVED** | 1 (D2-cy4 test-runtime) |
|
||||||
|
| Low | 1 (F-AZ1074-2) | **0 — RESOLVED** | 5+ (cycle 7–8 carry-overs) |
|
||||||
|
|
||||||
|
## OWASP Top 10:2021 (cycle-9 delta)
|
||||||
|
|
||||||
|
See `owasp_review_cycle9.md` — all categories PASS or N/A after follow-up.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
| # | Severity | Category | Location | Title | Status |
|
||||||
|
|---|----------|----------|----------|-------|--------|
|
||||||
|
| F-AZ1074-1 | Medium | Insecure Design (A04) | `RouteTileDeliveryOrchestrator.ValidateJob` | Unbounded gRPC waypoints/geofences/client_tiles | **RESOLVED** — caps 500/50/5000 |
|
||||||
|
| F-AZ1074-2 | Low | Information Disclosure (A09) | `RouteTileDeliveryGrpcService` catch-all | `ex.Message` in `DeliveryError` | **RESOLVED** — generic client message |
|
||||||
|
|
||||||
|
### F-AZ1074-1 detail (RESOLVED)
|
||||||
|
|
||||||
|
Aligned gRPC collection caps with REST: `waypoints ≤ 500`, `geofences ≤ 50`, `client_tiles ≤ 5000`. InvalidArgument via existing `ArgumentException` → `RpcException` mapping.
|
||||||
|
|
||||||
|
### F-AZ1074-2 detail (RESOLVED)
|
||||||
|
|
||||||
|
Internal errors now return `"An internal error occurred."` on the wire; `LogError` retains full exception.
|
||||||
|
|
||||||
|
## Carry-overs (unchanged)
|
||||||
|
|
||||||
|
- **F-AZ795-1, F-AZ795-2, F-AZ810-1, F-AZ810-2** — REST information-disclosure / time-handling (cycle 7–8)
|
||||||
|
- **D-AZ795-1** — FluentValidation 12.0.0 → 12.1.1
|
||||||
|
- **D2-cy4** — test SDK transitive (Medium, test-runtime only)
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Immediate
|
||||||
|
- None blocking cycle 9 ship.
|
||||||
|
|
||||||
|
### Short-term (cycle 10+)
|
||||||
|
- Sanitise REST `GlobalExceptionHandler` + `UavUploadValidationFilter` (F-AZ795-1 / F-AZ810-1) in one ticket.
|
||||||
|
- Bump FluentValidation 12.0.0 → 12.1.1 (D-AZ795-1).
|
||||||
|
|
||||||
|
### Long-term
|
||||||
|
- Consider `region_size_meters` upper bound on gRPC path (REST uses 10_000 m cap) — advisory parity, not release-blocking.
|
||||||
|
|
||||||
|
## Artifacts
|
||||||
|
|
||||||
|
- `dependency_scan_cycle9.md`
|
||||||
|
- `static_analysis_cycle9.md`
|
||||||
|
- `owasp_review_cycle9.md`
|
||||||
|
- `infrastructure_review_cycle9.md`
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Static Analysis (Cycle 9)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: AZ-1074 + AZ-1075 gRPC surface. Cycle-8 baseline remains authoritative for REST validators.
|
||||||
|
|
||||||
|
**Files in scope**:
|
||||||
|
- `SatelliteProvider.Api/Grpc/RouteTileDeliveryGrpcService.cs` (new)
|
||||||
|
- `SatelliteProvider.Api/Program.cs` (`AddGrpc`, `MapGrpcService`, message size limits)
|
||||||
|
- `SatelliteProvider.Services.RouteManagement/TileProvision/RouteTileDeliveryOrchestrator.cs` (validation hardening)
|
||||||
|
- `SatelliteProvider.GrpcContracts/tile_provision.proto` + generated stubs
|
||||||
|
- `SatelliteProvider.IntegrationTests/RouteTileDeliveryGrpcTests.cs`, `GrpcTestHelpers.cs`
|
||||||
|
- `SatelliteProvider.IntegrationTests/Dockerfile` (linux/amd64, aspnet runtime)
|
||||||
|
- `docker-compose.tests.yml` (self-contained test stack)
|
||||||
|
|
||||||
|
**Method**: End-to-end read of new files; grep for hardcoded secrets; trace auth middleware order; compare gRPC validation bounds vs REST `CreateRouteRequestValidator`.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### F-AZ1074-1 — Unbounded gRPC request collections enable authenticated DoS (Medium / A04) — **RESOLVED in cycle 9 (Step-14 follow-up)**
|
||||||
|
|
||||||
|
- **Location**: `RouteTileDeliveryOrchestrator.ValidateJob` (pre-fix).
|
||||||
|
- **Description**: `DeliverRouteTiles` accepted unbounded `waypoints`, `geofences`, and `client_tiles` protobuf repeated fields. REST `POST /api/satellite/route` caps `points` at 500 and `geofences.polygons` at 50 (cycle-8 F-AZ809-1 fix); gRPC had no equivalent caps before cycle 9 Step 14.
|
||||||
|
- **Impact**: Medium. Auth-gated (`[Authorize]` on `RouteTileDeliveryGrpcService`; JWT metadata required). Authenticated operator could force large CPU/memory work in `RouteTileExpander.Expand` and `ClientTileCatalog.IndexByZxy`.
|
||||||
|
- **Resolution**: Added `MaxWaypoints = 500`, `MaxGeofencePolygons = 50`, `MaxClientTiles = 5000` (inventory cap parity) to `ValidateJob`. Unit test `DeliverAsync_TooManyWaypoints_Throws` added.
|
||||||
|
|
||||||
|
### F-AZ1074-2 — Internal exception message echoed to gRPC client (Low / A09) — **RESOLVED in cycle 9 (Step-14 follow-up)**
|
||||||
|
|
||||||
|
- **Location**: `RouteTileDeliveryGrpcService.cs:55-58` (pre-fix).
|
||||||
|
- **Description**: Generic `catch (Exception)` wrote `ex.Message` into stream `DeliveryError.Message` — parallel to cycle-7 F-AZ795-1 (REST ProblemDetails path).
|
||||||
|
- **Impact**: Low. Auth-gated. Could leak internal exception text to authenticated clients.
|
||||||
|
- **Resolution**: Client message replaced with generic `"An internal error occurred."`; full exception still logged server-side.
|
||||||
|
|
||||||
|
## Pass areas (cycle-9 delta)
|
||||||
|
|
||||||
|
| Area | Result |
|
||||||
|
|------|--------|
|
||||||
|
| SQL injection | N/A — no new raw SQL |
|
||||||
|
| Hardcoded secrets | None in new files |
|
||||||
|
| gRPC auth | `[Authorize]` + `UseAuthentication`/`UseAuthorization` before `MapGrpcService` |
|
||||||
|
| JWT on gRPC | Integration tests pass Bearer token via metadata — matches REST contract |
|
||||||
|
| Message size limits | `MaxReceiveMessageSize = 16 MiB`, `MaxSendMessageSize = 64 MiB` configured |
|
||||||
|
| Protobuf parsing | Bounded by Kestrel/gRPC message limits; collection caps added post-audit |
|
||||||
|
| Test fixtures | `GrpcTestHelpers` uses env-resolved JWT via `JwtTestHelpers.MintAuthenticated` — no embedded secrets |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS_WITH_WARNINGS** at audit time (1 Medium open → **resolved in Step-14 follow-up**). Post-fix delta: **PASS** for cycle-9 new code.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Perf Run — Cycle 9 (AZ-1074 + AZ-1075)
|
||||||
|
|
||||||
|
**Date**: 2026-06-25T14:18Z
|
||||||
|
**Run label**: cycle9 — full default-parameter run after gRPC RouteTileDelivery (AZ-1074) + integration tests (AZ-1075) and Step-14 security hardening (collection caps + generic internal error message).
|
||||||
|
**Trigger**: autodev existing-code Step 15 (Performance Test gate). User chose **A) Run performance tests**.
|
||||||
|
**Runner**: `scripts/run-performance-tests.sh` (default params: `PERF_REPEAT_COUNT=20`, `PERF_UAV_BATCH_SIZE=10`). Single run, exit 0.
|
||||||
|
**System under test**: `docker compose -f docker-compose.yml -f docker-compose.perf.yml up -d --build` — postgres without host port publish (5433 conflict with sibling `fleet-viewer-dev-db`); api on `https://localhost:18980` (TLS+ALPN, dev cert `./certs/api.crt` trusted via `--cacert`).
|
||||||
|
**Build**: `SatelliteProvider.IntegrationTests` Release built on host; 0 errors / 15 warnings (carried-over NU1902 IdentityModel + CA2227).
|
||||||
|
**JWT**: minted by `SatelliteProvider.IntegrationTests --mint-only`; 4 h lifetime, 341 bytes.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
| # | Scenario | Verdict | Observed | Threshold | Source |
|
||||||
|
|---|----------|---------|----------|-----------|--------|
|
||||||
|
| PT-01 | Tile download (cold) | **PASS** | 1260 ms | ≤ 30000 ms | performance-tests.md |
|
||||||
|
| PT-02 | Cached tile retrieval | **PASS** | 220 ms | ≤ 500 ms | performance-tests.md |
|
||||||
|
| PT-03 | Region 200 m / z18 | **PASS** | 2317 ms | ≤ 60000 ms | performance-tests.md |
|
||||||
|
| PT-04 | Region 500 m / z18 + stitch | **PASS** | 2192 ms | ≤ 120000 ms | performance-tests.md |
|
||||||
|
| PT-05 | 5 concurrent regions | **PASS** | 2383 ms | ≤ 300000 ms | performance-tests.md |
|
||||||
|
| PT-06 | Route creation (2 points) | **PASS** | 244 ms | ≤ 5000 ms | performance-tests.md |
|
||||||
|
| PT-07 | Region distribution (N=20, cold + warm) | **PASS** | cold p50=2115 ms, p95=2156 ms · warm p50=45 ms, p95=79 ms | warm p95 < cold p95 | AZ-484 / AZ-492 |
|
||||||
|
| PT-08 | UAV batch upload (batch=10, N=20) | **PASS** | batch p50=90 ms, p95=179 ms; per-item proxy p95=17 ms; accepted=200, rejected=0, failed=0 | batch p95 ≤ 2000 ms | AZ-488 |
|
||||||
|
|
||||||
|
**Raw verdict: 8 Pass · 0 Warn · 0 Fail · 0 Unverified**
|
||||||
|
|
||||||
|
## Cycle-9 delta (gRPC)
|
||||||
|
|
||||||
|
No gRPC scenarios in `performance-tests.md` yet (PT-01..PT-08 are REST-only). The new `DeliverRouteTiles` streaming RPC shares `RouteTileDeliveryOrchestrator` with REST tile delivery; PT-06 exercises route creation latency on the same orchestration path. Step-14 caps (500 waypoints / 50 geofences / 5000 client tiles) are O(1) count checks — invisible at REST perf resolution.
|
||||||
|
|
||||||
|
**Unverified (not blocking)**: gRPC stream latency / backpressure under load — candidate PT-10 for a future cycle if acceptance criteria add streaming NFRs.
|
||||||
|
|
||||||
|
## Trend vs cycle 8
|
||||||
|
|
||||||
|
| Scenario | Cycle 8 | Cycle 9 | Δ | Notes |
|
||||||
|
|----------|---------|---------|---|-------|
|
||||||
|
| PT-01 cold | 885 ms | 1260 ms | +375 ms | noise (tile likely cached on disk; not true cold Google Maps) |
|
||||||
|
| PT-02 cached | 244 ms | 220 ms | -24 ms | noise |
|
||||||
|
| PT-03 region 200 m | 99 ms | 2317 ms | +2218 ms | cold-cache variance — PT-03 coord may have missed warm cache this run |
|
||||||
|
| PT-04 region 500 m + stitch | 2128 ms | 2192 ms | +64 ms | noise |
|
||||||
|
| PT-05 5 concurrent | 2663 ms | 2383 ms | -280 ms | noise |
|
||||||
|
| PT-06 route create | 83 ms | 244 ms | +161 ms | noise band |
|
||||||
|
| PT-07 cold p95 / warm p95 | 2274 ms / 108 ms | 2156 ms / 79 ms | -118 ms / -29 ms | noise |
|
||||||
|
| PT-08 batch p95 | 379 ms | 179 ms | -200 ms | noise (within historical 179–544 ms band) |
|
||||||
|
|
||||||
|
No scenario shows a regression attributable to cycle-9 gRPC work or security caps.
|
||||||
|
|
||||||
|
## Infrastructure note
|
||||||
|
|
||||||
|
Host port **5433** was occupied by a sibling project. Perf stack used a one-shot `docker-compose.perf.yml` override (`postgres.ports: !reset []`) so only api port 18980 was published. Recommend documenting this pattern for dev workstations with parallel Postgres stacks.
|
||||||
|
|
||||||
|
## Verdict (Step 15)
|
||||||
|
|
||||||
|
**PASS** — 8/8 REST scenarios within threshold. Cleared to auto-chain to Step 16 (Deploy).
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Retrospective — Cycle 9 (2026-06-25)
|
||||||
|
|
||||||
|
**Tasks**: AZ-1074 (gRPC RouteTileDelivery service, 5 SP), AZ-1075 (gRPC integration tests, 3 SP). **2 tasks, 8 SP, 1 batch.**
|
||||||
|
**Mode**: cycle-end (autodev Step 17). Step 16.5 (Release) **skipped** per user choice — matches cycles 1–8 pattern; changes uncommitted at retro time.
|
||||||
|
**Previous retro**: `retro_2026-05-23_cycle8.md`
|
||||||
|
|
||||||
|
## Implementation Summary
|
||||||
|
|
||||||
|
| Metric | Cycle 9 | Δ vs cycle 8 |
|
||||||
|
|--------|---------|--------------|
|
||||||
|
| Tasks implemented | **2** | -3 |
|
||||||
|
| Batches executed | **1** | -3 |
|
||||||
|
| Total complexity delivered | **8 SP** | -9 SP |
|
||||||
|
| Avg tasks / batch | **2** | +0.75 |
|
||||||
|
| Blocked tasks | **0** | unchanged |
|
||||||
|
| Implementation report | **YES** (`implementation_report_tile_provision_grpc_cycle9.md`) | maintained |
|
||||||
|
|
||||||
|
## Quality Metrics
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
| Verdict | Count |
|
||||||
|
|---------|-------|
|
||||||
|
| PASS_WITH_WARNINGS | **1** (batch 01) |
|
||||||
|
| FAIL | 0 |
|
||||||
|
|
||||||
|
**Findings**: 2 Low (amd64 Docker pin documentation; ArgumentException detail string formatting).
|
||||||
|
|
||||||
|
### Security Audit (Step 14)
|
||||||
|
|
||||||
|
| Severity at audit | Post follow-up |
|
||||||
|
|-------------------|----------------|
|
||||||
|
| Medium 1 | **0** — F-AZ1074-1 resolved (collection caps) |
|
||||||
|
| Low 1 | **0** — F-AZ1074-2 resolved (generic internal error) |
|
||||||
|
|
||||||
|
Continues cycle-8 pattern of in-cycle Medium resolution.
|
||||||
|
|
||||||
|
### Test & Perf Gates
|
||||||
|
|
||||||
|
| Gate | Result |
|
||||||
|
|------|--------|
|
||||||
|
| Step 11 functional | **PASS** — 448 unit + integration |
|
||||||
|
| Step 15 perf | **PASS** — 8/8 REST scenarios (gRPC unverified) |
|
||||||
|
|
||||||
|
## Efficiency
|
||||||
|
|
||||||
|
| Blocker | Resolution |
|
||||||
|
|---------|------------|
|
||||||
|
| Host port 5433 conflict (integration + perf) | Standalone `docker-compose.tests.yml`; perf used ephemeral compose override |
|
||||||
|
|
||||||
|
## Trend Comparison
|
||||||
|
|
||||||
|
| Metric | Cycle 8 | Cycle 9 | Change |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| Code review FAIL rate | 0% | 0% | unchanged |
|
||||||
|
| Security Medium open (delta) | 0 (1 resolved in-cycle) | 0 (1 resolved in-cycle) | same pattern |
|
||||||
|
| Perf scenarios pass | 8/8 | 8/8 | unchanged |
|
||||||
|
| Project count | 9 | 10 | +1 (GrpcContracts) |
|
||||||
|
|
||||||
|
## Top 3 Improvement Actions
|
||||||
|
|
||||||
|
1. **Document parallel-Postgres dev workaround** (~1 SP): add `docker-compose.perf.yml` or documented `ports: !reset []` override to `_docs/02_document/deployment/containerization.md` so Step 11/15 don't rediscover the 5433 conflict each cycle.
|
||||||
|
- Impact: faster integration/perf runs on multi-project dev machines
|
||||||
|
- Effort: low
|
||||||
|
|
||||||
|
2. **PT-10 gRPC stream perf scenario** (~3 SP): when streaming NFR is accepted, add harness scenario for `DeliverRouteTiles` (time-to-first-chunk, total stream duration, backpressure).
|
||||||
|
- Impact: closes Unverified gap from cycle 9 Step 15
|
||||||
|
- Effort: medium
|
||||||
|
|
||||||
|
3. **REST error sanitizer sweep** (~2 SP): F-AZ795-1/2 carry-over — static 400 messages in `GlobalExceptionHandler` / upload filter.
|
||||||
|
- Impact: reduces cumulative Low security debt (5+ cycles)
|
||||||
|
- Effort: low–medium
|
||||||
|
|
||||||
|
## Suggested Rule/Skill Updates
|
||||||
|
|
||||||
|
| File | Change | Rationale |
|
||||||
|
|------|--------|-----------|
|
||||||
|
| `test-run/SKILL.md` perf mode | Note gRPC scenarios may be Unverified when only REST harness exists | Cycle 9 Step 15 |
|
||||||
|
| `environment.md` or containerization doc | Postgres port conflict playbook | Recurring blocker |
|
||||||
|
|
||||||
|
## Cycle 9 Verdict
|
||||||
|
|
||||||
|
**Successful feature cycle** — gRPC delivery shipped with tests, security hardening, and green gates. Release deferred (user choice); commit/push remains operator action before production promotion.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Structural Snapshot — 2026-06-25 (post-cycle 9, gRPC tile provision)
|
||||||
|
|
||||||
|
Cycle 9 delta against `structure_2026-05-23_cycle8.md`. Source: `_docs/02_document/module-layout.md` + on-disk `*.csproj` graph.
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
| Layer | csproj | Cycle 9 delta |
|
||||||
|
|-------|--------|---------------|
|
||||||
|
| 1 (Foundation) | `SatelliteProvider.GrpcContracts` | **NEW** — shared proto project (`tile_provision.proto`, GrpcServices=Both) |
|
||||||
|
| 4 (API) | `SatelliteProvider.Api` | + `Grpc/RouteTileDeliveryGrpcService.cs`; `Program.cs` AddGrpc/MapGrpcService; ProjectReference → GrpcContracts |
|
||||||
|
| 3 (Application) | `SatelliteProvider.Services.RouteManagement` | `RouteTileDeliveryOrchestrator` — collection caps + lat/lon validation |
|
||||||
|
| 6 (Tests) | `SatelliteProvider.IntegrationTests` | + `RouteTileDeliveryGrpcTests.cs`, `GrpcTestHelpers.cs`; ProjectReference → GrpcContracts |
|
||||||
|
| 6 (Tests) | `SatelliteProvider.Tests` | + `DeliverAsync_TooManyWaypoints_Throws` |
|
||||||
|
|
||||||
|
**Project count**: **10** (+1 vs cycle 8).
|
||||||
|
|
||||||
|
## Cross-Project Import Edges
|
||||||
|
|
||||||
|
| Edge | Cycle 9 delta |
|
||||||
|
|------|---------------|
|
||||||
|
| Api → GrpcContracts | **NEW** |
|
||||||
|
| IntegrationTests → GrpcContracts | **NEW** |
|
||||||
|
| GrpcContracts → (none) | leaf contract project |
|
||||||
|
|
||||||
|
**Total ProjectReference edges**: **23** (+2 vs cycle 8). **Import cycles**: 0.
|
||||||
|
|
||||||
|
## Contract coverage
|
||||||
|
|
||||||
|
| Surface | Contract location |
|
||||||
|
|---------|-------------------|
|
||||||
|
| gRPC `DeliverRouteTiles` | `SatelliteProvider.GrpcContracts/tile_provision.proto` |
|
||||||
|
| REST tile delivery | unchanged (existing contracts) |
|
||||||
|
|
||||||
|
**gRPC perf coverage**: Unverified — no PT-10 in harness yet.
|
||||||
+7
-7
@@ -37,6 +37,12 @@ If the enum's wire string happens to match a member name case-insensitively (e.g
|
|||||||
|
|
||||||
## Ring buffer (last 15 entries — newest at top)
|
## Ring buffer (last 15 entries — newest at top)
|
||||||
|
|
||||||
|
- [2026-06-25] [tooling] When host port 5433 is occupied by a sibling Postgres container, integration and perf gates must not depend on publishing postgres to the host — use a self-contained test compose file (internal network only) or a documented `ports: !reset []` override on the dev stack so Step 11/15 can run without stopping sibling projects (cycle 9: `fleet-viewer-dev-db` blocked both integration tests and perf until compose was adjusted).
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-25_cycle9.md
|
||||||
|
- [2026-06-25] [testing] Adding a new transport (gRPC) over shared orchestrator logic does not automatically extend the perf harness — REST PT-01..PT-08 can pass while the new RPC surface stays Unverified until an explicit PT-NN scenario and threshold land in `performance-tests.md` + `run-performance-tests.sh` (cycle 9: gRPC DeliverRouteTiles had no perf scenario; gate passed on REST-only evidence).
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-25_cycle9.md
|
||||||
|
- [2026-06-25] [architecture] Shared wire contracts belong in a leaf `*.GrpcContracts` (or equivalent) project referenced by both server and test client — keeping proto in the API project forces test projects to reference the full API assembly and couples codegen to the delivery layer (cycle 9: `SatelliteProvider.GrpcContracts` extracted from Api).
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-25_cycle9.md
|
||||||
- [2026-05-23] [process] Step-14 security-audit Medium findings whose remediation fits the small-fix threshold (≤2 SP, ≤2 files, ≤1 contract bump) should be resolved within the same autodev invocation rather than deferred to cycle N+1 — the fix lands with the same commit chain that introduced the surface, the contract version reflects the fix immediately, and the traceability matrix and blackbox-tests.md sub-cases are written while the finding is fresh; codify the option as a first-class A/B/C choice in `.cursor/skills/autodev/flows/existing-code.md` Step-14 action (cycle 8: F-AZ809-1 unbounded `geofences.polygons` DoS — discovered in commit `ac40a8b`, resolved in commit `8fca6e0` with `MaxPolygons = 50` cap + unit + integration test + `route-creation.md` v1.0.1 patch bump, ~30 minutes from finding to fix landed).
|
- [2026-05-23] [process] Step-14 security-audit Medium findings whose remediation fits the small-fix threshold (≤2 SP, ≤2 files, ≤1 contract bump) should be resolved within the same autodev invocation rather than deferred to cycle N+1 — the fix lands with the same commit chain that introduced the surface, the contract version reflects the fix immediately, and the traceability matrix and blackbox-tests.md sub-cases are written while the finding is fresh; codify the option as a first-class A/B/C choice in `.cursor/skills/autodev/flows/existing-code.md` Step-14 action (cycle 8: F-AZ809-1 unbounded `geofences.polygons` DoS — discovered in commit `ac40a8b`, resolved in commit `8fca6e0` with `MaxPolygons = 50` cap + unit + integration test + `route-creation.md` v1.0.1 patch bump, ~30 minutes from finding to fix landed).
|
||||||
Source: _docs/06_metrics/retro_2026-05-23_cycle8.md
|
Source: _docs/06_metrics/retro_2026-05-23_cycle8.md
|
||||||
- [2026-05-23] [process] Retrospective recommendations ship end-to-end in the next cycle only when they (a) name concrete tracker tickets / files / endpoints in the action text, (b) are sized as a coherent cycle theme rather than scattered one-off fixes, and (c) the next cycle's planning phase pulls the recommendation directly into the task slate without re-deriving scope — phrase recommendations to satisfy all three or they become multi-cycle carry-overs (cycle 7 Action 3 named the 4 AZ-795 child endpoints + the SP sizing → cycle 8 shipped AZ-808 + AZ-809 + AZ-810 + AZ-811 + AZ-812 as the coherent strict-validation theme, first directly-traceable cross-cycle improvement-action end-to-end in project history).
|
- [2026-05-23] [process] Retrospective recommendations ship end-to-end in the next cycle only when they (a) name concrete tracker tickets / files / endpoints in the action text, (b) are sized as a coherent cycle theme rather than scattered one-off fixes, and (c) the next cycle's planning phase pulls the recommendation directly into the task slate without re-deriving scope — phrase recommendations to satisfy all three or they become multi-cycle carry-overs (cycle 7 Action 3 named the 4 AZ-795 child endpoints + the SP sizing → cycle 8 shipped AZ-808 + AZ-809 + AZ-810 + AZ-811 + AZ-812 as the coherent strict-validation theme, first directly-traceable cross-cycle improvement-action end-to-end in project history).
|
||||||
@@ -60,10 +66,4 @@ If the enum's wire string happens to match a member name case-insensitively (e.g
|
|||||||
- [2026-05-12] [architecture] Cross-repo cryptographic invariants (UUID namespaces, deterministic-key formulas, base32/64 alphabets, tile-zoom conventions) MUST live as code-level constants in BOTH repos with reference-vector tests on BOTH sides — documentation alone is insufficient because constant drift surfaces only as 100% lookup misses in production, harder to detect than a unit-test failure (cycle 5: AZ-503 introduced `TileNamespace = 5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c` which must byte-match the same constant in `gps-denied-onboard/components/c6_tile_cache/_uuid.py`; the satellite-provider side has the constant + 10 Python-generated reference vectors in `Uuidv5Tests.cs` and the sibling repo will mirror).
|
- [2026-05-12] [architecture] Cross-repo cryptographic invariants (UUID namespaces, deterministic-key formulas, base32/64 alphabets, tile-zoom conventions) MUST live as code-level constants in BOTH repos with reference-vector tests on BOTH sides — documentation alone is insufficient because constant drift surfaces only as 100% lookup misses in production, harder to detect than a unit-test failure (cycle 5: AZ-503 introduced `TileNamespace = 5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c` which must byte-match the same constant in `gps-denied-onboard/components/c6_tile_cache/_uuid.py`; the satellite-provider side has the constant + 10 Python-generated reference vectors in `Uuidv5Tests.cs` and the sibling repo will mirror).
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle5.md
|
Source: _docs/06_metrics/retro_2026-05-12_cycle5.md
|
||||||
- [2026-05-12] [tooling] Local Docker/colima DNS cold-start is a recurring class of failure that contaminates the Step-15 perf gate — when the perf-mode "one re-run" rule fires twice across consecutive cycles with the same root-cause class (DNS / NTP / resolver), the harness must escalate from "re-run" to a deterministic fix at the harness layer (DNS pre-warm in script, OR move gate to CI), not just another re-run (cycle 5: PT-01 failed Run #1 on `tile.googleapis.com` cold-start, then Run #2 on `mt0.google.com` cold-start; the warmup probe between runs only touched the hostnames it explicitly named).
|
- [2026-05-12] [tooling] Local Docker/colima DNS cold-start is a recurring class of failure that contaminates the Step-15 perf gate — when the perf-mode "one re-run" rule fires twice across consecutive cycles with the same root-cause class (DNS / NTP / resolver), the harness must escalate from "re-run" to a deterministic fix at the harness layer (DNS pre-warm in script, OR move gate to CI), not just another re-run (cycle 5: PT-01 failed Run #1 on `tile.googleapis.com` cold-start, then Run #2 on `mt0.google.com` cold-start; the warmup probe between runs only touched the hostnames it explicitly named).
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle5.md
|
Source: _docs/06_metrics/retro_2026-05-12_cycle5.md
|
||||||
- [2026-05-12] [process] When a /autodev cycle's task spec contradicts the live codebase by ≥2 missing prerequisites, the implement skill should preferentially split into foundation + follow-up via A/B/C (option C) rather than (A) silently expand the SP budget or (B) defer the entire task — both halves remain individually shippable and individually testable, the cross-PBI dependency is captured as a blocked-link in the tracker (cycle 5: AZ-503 → AZ-503-foundation + AZ-505 split when `flight_id` / `FlightId` / `voting_status` all missing from live code; AZ-503-foundation shipped this cycle, AZ-505 blocks-on-it for cycle 6).
|
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle5.md
|
|
||||||
- [2026-05-12] [dependencies] Major-version bumps of direct deps cascade through transitives; the task spec must list the transitive packages whose major version changes as a result OR explicitly note "transitive major-version drift not analyzed in spec" — verify with `dotnet restore --dry-run` against a scratch branch before writing the spec (cycle 4: AZ-500 surprise-bumped `Microsoft.OpenApi` 1.x → 2.x via the `Microsoft.AspNetCore.OpenApi` 8.0.25 → 10.0.7 path; forced an unscheduled Swashbuckle bump + Program.cs refactor mid-implementation).
|
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle4.md
|
|
||||||
- [2026-05-12] [process] When a scope-protected task newly *exposes* a pre-existing bug elsewhere in the codebase (vs. introducing a new one), surface it as a recommended follow-up PBI in the batch report AND list it as a "newly exposed bug" separate from "newly introduced findings" in the deploy report — bugs that already existed don't count as cycle-introduced regressions, but they must not be silently re-buried (cycle 4: AZ-500's bootstrap fix unmasked the pre-existing `scripts/run-performance-tests.sh:417` `grep -o | wc -l` + `pipefail` bug).
|
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle4.md
|
|
||||||
+16
-6
@@ -2,14 +2,24 @@
|
|||||||
|
|
||||||
## Current Step
|
## Current Step
|
||||||
flow: existing-code
|
flow: existing-code
|
||||||
step: 11
|
step: 17
|
||||||
name: Run Tests
|
name: Retrospective
|
||||||
status: pending
|
status: completed
|
||||||
sub_step:
|
sub_step:
|
||||||
phase: 1
|
phase: 4
|
||||||
name: full-suite
|
name: lessons-updated
|
||||||
detail: "Step 10 complete; smoke passed, full suite pending"
|
detail: "cycle 9 complete; release skipped; no re-entry (release not Released)"
|
||||||
retry_count: 0
|
retry_count: 0
|
||||||
cycle: 9
|
cycle: 9
|
||||||
tracker: jira
|
tracker: jira
|
||||||
auto_push: true
|
auto_push: true
|
||||||
|
|
||||||
|
## Step 16.5
|
||||||
|
release_verdict: skipped
|
||||||
|
release_skip_reason: user chose B — skip release, proceed to retrospective
|
||||||
|
|
||||||
|
## Cycle 9 Summary
|
||||||
|
tasks: AZ-1074, AZ-1075
|
||||||
|
gates: tests PASS, security PASS (delta), perf PASS (8/8 REST)
|
||||||
|
artifacts: deploy_cycle9.md, perf_2026-06-25_cycle9.md, retro_2026-06-25_cycle9.md, security_report_cycle9.md
|
||||||
|
uncommitted: yes — operator commit/push pending
|
||||||
|
|||||||
+34
-14
@@ -1,24 +1,47 @@
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
extends:
|
image: postgres:16
|
||||||
file: docker-compose.yml
|
container_name: satellite-provider-postgres
|
||||||
service: postgres
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: satelliteprovider
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
api:
|
api:
|
||||||
extends:
|
platform: linux/amd64
|
||||||
file: docker-compose.yml
|
build:
|
||||||
service: api
|
context: .
|
||||||
|
dockerfile: SatelliteProvider.Api/Dockerfile
|
||||||
|
container_name: satellite-provider-api
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=https://+:8080
|
||||||
|
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/certs/api.pfx
|
||||||
|
- ASPNETCORE_Kestrel__Certificates__Default__Password=satellite-dev-cert
|
||||||
|
- ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=satelliteprovider;Username=postgres;Password=postgres
|
||||||
|
- MapConfig__ApiKey=${GOOGLE_MAPS_API_KEY}
|
||||||
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
|
- JWT_ISSUER=${JWT_ISSUER}
|
||||||
|
- JWT_AUDIENCE=${JWT_AUDIENCE}
|
||||||
|
volumes:
|
||||||
|
- ./tiles:/app/tiles
|
||||||
|
- ./ready:/app/ready
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- ./certs/api.pfx:/app/certs/api.pfx:ro
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: SatelliteProvider.IntegrationTests/Dockerfile
|
dockerfile: SatelliteProvider.IntegrationTests/Dockerfile
|
||||||
container_name: satellite-provider-integration-tests
|
container_name: satellite-provider-integration-tests
|
||||||
# AZ-505 AC-5: API now serves HTTPS for HTTP/2 via ALPN. The matching
|
|
||||||
# public cert is mounted into /usr/local/share/ca-certificates so the
|
|
||||||
# Dockerfile entrypoint can register it with update-ca-certificates
|
|
||||||
# before tests run. After that every HttpClient trusts it transparently
|
|
||||||
# — no per-test handler shim is required.
|
|
||||||
environment:
|
environment:
|
||||||
- API_URL=https://api:8080
|
- API_URL=https://api:8080
|
||||||
- INTEGRATION_TESTS_MODE=${INTEGRATION_TESTS_MODE:-full}
|
- INTEGRATION_TESTS_MODE=${INTEGRATION_TESTS_MODE:-full}
|
||||||
@@ -32,8 +55,6 @@ services:
|
|||||||
- ./ready:/app/ready
|
- ./ready:/app/ready
|
||||||
- ./tiles:/app/tiles
|
- ./tiles:/app/tiles
|
||||||
- ./certs/api.crt:/usr/local/share/ca-certificates/satellite-provider-dev.crt:ro
|
- ./certs/api.crt:/usr/local/share/ca-certificates/satellite-provider-dev.crt:ro
|
||||||
# AZ-505 AC-5: register the dev CA at runtime so HttpClient trusts the API.
|
|
||||||
# update-ca-certificates picks up everything under /usr/local/share/ca-certificates/.
|
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -47,4 +68,3 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
docker compose -f "$PROJECT_ROOT/docker-compose.yml" -f "$PROJECT_ROOT/docker-compose.tests.yml" down --remove-orphans || true
|
docker compose -f "$PROJECT_ROOT/docker-compose.tests.yml" down --remove-orphans || true
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
@@ -173,7 +173,6 @@ fi
|
|||||||
INTEGRATION_TESTS_MODE="$mode" \
|
INTEGRATION_TESTS_MODE="$mode" \
|
||||||
INTEGRATION_KEEP_STATE="$INTEGRATION_KEEP_STATE_VALUE" \
|
INTEGRATION_KEEP_STATE="$INTEGRATION_KEEP_STATE_VALUE" \
|
||||||
docker compose \
|
docker compose \
|
||||||
-f "$PROJECT_ROOT/docker-compose.yml" \
|
|
||||||
-f "$PROJECT_ROOT/docker-compose.tests.yml" \
|
-f "$PROJECT_ROOT/docker-compose.tests.yml" \
|
||||||
up --build --abort-on-container-exit --exit-code-from integration-tests
|
up --build --abort-on-container-exit --exit-code-from integration-tests
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user