mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-27 07:31:13 +00:00
chore: WIP pre-implement cycle 14 baseline
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,9 @@ when:
|
|||||||
event: [push, pull_request, manual]
|
event: [push, pull_request, manual]
|
||||||
branch: [dev, stage, main]
|
branch: [dev, stage, main]
|
||||||
|
|
||||||
|
# Unit tests are architecture-neutral — run once on the colocated arm64 agent
|
||||||
|
# (suite/_infra/ci/README.md: unit lanes stay arm64-only to avoid burning the
|
||||||
|
# remote amd64 agent). Build-push fans out to arm64 + amd64 via matrix.
|
||||||
labels:
|
labels:
|
||||||
platform: arm64
|
platform: arm64
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ when:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- 01-test
|
- 01-test
|
||||||
|
|
||||||
# Multi-arch matrix. Adding amd64 = uncommenting the second entry once an
|
# Multi-arch matrix — matches suite/_infra/ci/README.md § Build-push step.
|
||||||
# amd64 agent is online.
|
# arm64 agent (colocated Jetson) → {branch}-arm
|
||||||
|
# amd64 agent (remote host) → {branch}-amd64 (production deploy tag)
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- PLATFORM: arm64
|
- PLATFORM: arm64
|
||||||
TAG_SUFFIX: arm
|
TAG_SUFFIX: arm
|
||||||
# - PLATFORM: amd64
|
- PLATFORM: amd64
|
||||||
# TAG_SUFFIX: amd
|
TAG_SUFFIX: amd64
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
platform: ${PLATFORM}
|
platform: ${PLATFORM}
|
||||||
|
|||||||
@@ -156,11 +156,27 @@ Configuration sections in appsettings.json:
|
|||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
**Run tests:**
|
**Run tests** (Apple Silicon Mac — native `linux/arm64`, no Rosetta):
|
||||||
|
```bash
|
||||||
|
./scripts/run-tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run tests** (Docker Compose with tests):
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit
|
docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### CI / Deploy platforms
|
||||||
|
|
||||||
|
| Context | Architecture | Image tag |
|
||||||
|
|---------|--------------|-----------|
|
||||||
|
| Woodpecker unit tests | `linux/arm64` agent | — |
|
||||||
|
| Woodpecker build (arm64 agent) | `linux/arm64` | `{branch}-arm` |
|
||||||
|
| Woodpecker build (amd64 agent) | `linux/amd64` | `{branch}-amd64` |
|
||||||
|
| Production deploy | amd64 host | `${BRANCH}-amd64` (Watchtower) |
|
||||||
|
|
||||||
|
See `_docs/02_document/deployment/ci_cd_pipeline.md` and suite `_infra/ci/README.md`.
|
||||||
|
|
||||||
### Important Notes
|
### Important Notes
|
||||||
|
|
||||||
1. **DO NOT run `dotnet build` or `dotnet restore` via terminal tools** - these commands hang. Ask user to run manually.
|
1. **DO NOT run `dotnet build` or `dotnet restore` via terminal tools** - these commands hang. Ask user to run manually.
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ EXPOSE 8081
|
|||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
RUN apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends protobuf-compiler \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
ENV PROTOBUF_PROTOC=/usr/bin/protoc
|
||||||
COPY ["SatelliteProvider.Api/SatelliteProvider.Api.csproj", "SatelliteProvider.Api/"]
|
COPY ["SatelliteProvider.Api/SatelliteProvider.Api.csproj", "SatelliteProvider.Api/"]
|
||||||
COPY ["SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj", "SatelliteProvider.GrpcContracts/"]
|
COPY ["SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj", "SatelliteProvider.GrpcContracts/"]
|
||||||
COPY ["SatelliteProvider.Common/SatelliteProvider.Common.csproj", "SatelliteProvider.Common/"]
|
COPY ["SatelliteProvider.Common/SatelliteProvider.Common.csproj", "SatelliteProvider.Common/"]
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
RUN apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends protobuf-compiler \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
ENV PROTOBUF_PROTOC=/usr/bin/protoc
|
||||||
COPY ["SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj", "SatelliteProvider.IntegrationTests/"]
|
COPY ["SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj", "SatelliteProvider.IntegrationTests/"]
|
||||||
COPY ["SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj", "SatelliteProvider.GrpcContracts/"]
|
COPY ["SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj", "SatelliteProvider.GrpcContracts/"]
|
||||||
COPY ["SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj", "SatelliteProvider.TestSupport/"]
|
COPY ["SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj", "SatelliteProvider.TestSupport/"]
|
||||||
@@ -12,7 +16,7 @@ RUN dotnet build "SatelliteProvider.IntegrationTests.csproj" -c Release -o /app/
|
|||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
RUN dotnet publish "SatelliteProvider.IntegrationTests.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
RUN dotnet publish "SatelliteProvider.IntegrationTests.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
ENTRYPOINT ["dotnet", "SatelliteProvider.IntegrationTests.dll"]
|
ENTRYPOINT ["dotnet", "SatelliteProvider.IntegrationTests.dll"]
|
||||||
|
|||||||
@@ -203,9 +203,10 @@ No dependency cycles detected. The dependency graph is a clean DAG.
|
|||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
- **Woodpecker CI** pipelines in `.woodpecker/`:
|
- **Woodpecker CI** pipelines in `.woodpecker/` (suite contract — see `suite/_infra/ci/README.md`):
|
||||||
- `01-test.yml`: runs `dotnet restore` + `dotnet test` on push/PR to dev/stage/main (ARM64)
|
- `01-test.yml`: `dotnet restore` + `dotnet test` on push/PR to dev/stage/main (`platform: arm64` only — unit tests are arch-neutral)
|
||||||
- `02-build-push.yml`: builds Docker image and pushes to private registry (depends on 01-test, ARM64 matrix with AMD64 slot commented out)
|
- `02-build-push.yml`: matrix fans out to `arm64` (`{branch}-arm`) and `amd64` (`{branch}-amd64`) agents; pushes to Gitea registry. Production deploy (`suite/_infra/deploy/satellite-provider/`) pulls `*-amd64`.
|
||||||
|
- **Local dev** (Apple Silicon Mac): `scripts/run-tests.sh` runs unit + integration tests as native `linux/arm64` (no Rosetta).
|
||||||
|
|
||||||
## Updates Since Baseline
|
## Updates Since Baseline
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|--------|-------|--------|-------|-------------|
|
|--------|-------|--------|-------|-------------|
|
||||||
| `Validate` | imageBytes, contentType, `UavTileMetadata` | `UavTileQualityResult` (accept + reason code) | No | none (decode exceptions caught and translated to `INVALID_FORMAT`) |
|
| `Validate` | imageBytes, contentType, `UavTileMetadata` | `UavTileQualityResult` (accept + reason code) | No | none (decode exceptions caught and translated to `INVALID_FORMAT`) |
|
||||||
|
|
||||||
Rules run in fixed order (Format → Size band → Dimensions → Captured-at age → Blank/uniform); first failure short-circuits. Thresholds come from `UavQualityConfig`. Time comes from injected `TimeProvider` (defaults to `TimeProvider.System`) for deterministic tests.
|
Rules run in fixed order (Format → Size band → Dimensions → Captured-at age → Blank/uniform); first failure short-circuits. Thresholds come from `UavQualityConfig`. Time comes from injected `TimeProvider` (defaults to `TimeProvider.System`) for deterministic tests. AZ-1126: `UavTileMetadata.CapturedAt` is `DateTimeOffset`; Rule 4 compares via `UtcDateTime` without `DateTimeKind` branching.
|
||||||
|
|
||||||
### Service: UavTileUploadHandler (implements IUavTileUploadHandler, AZ-488)
|
### Service: UavTileUploadHandler (implements IUavTileUploadHandler, AZ-488)
|
||||||
| Method | Input | Output | Async | Error Types |
|
| Method | Input | Output | Async | Error Types |
|
||||||
|
|||||||
@@ -2,15 +2,24 @@
|
|||||||
|
|
||||||
## Platform
|
## Platform
|
||||||
|
|
||||||
**CI Server**: Woodpecker CI (self-hosted)
|
**CI Server**: Woodpecker CI (self-hosted) — see suite [`_infra/ci/README.md`](../../../../_infra/ci/README.md) for agent install and registry wiring.
|
||||||
**Agent architecture**: ARM64 (AMD64 prepared but not yet active)
|
|
||||||
|
| Agent pool | Woodpecker label | Host | Role for this repo |
|
||||||
|
|------------|------------------|------|-------------------|
|
||||||
|
| ARM64 | `platform: arm64` | Colocated with CI server (Jetson) | Unit tests (`01-test`); builds `{branch}-arm` images |
|
||||||
|
| AMD64 | `platform: amd64` | Separate remote host | Builds `{branch}-amd64` images consumed by production deploy |
|
||||||
|
|
||||||
|
**Developer machine**: Apple Silicon Mac (M1/M2/M3, `darwin/arm64`). Local Docker runs native `linux/arm64` — see [tests/environment.md](../tests/environment.md) § Platform.
|
||||||
|
|
||||||
## Pipeline Stages
|
## Pipeline Stages
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
Push[Push/PR to dev/stage/main] --> Test[01-test]
|
Push[Push/PR to dev/stage/main] --> Test[01-test arm64]
|
||||||
Test --> Build[02-build-push]
|
Test --> BuildArm[02-build-push arm64]
|
||||||
|
Test --> BuildAmd[02-build-push amd64]
|
||||||
|
BuildArm --> RegistryArm["registry … :branch-arm"]
|
||||||
|
BuildAmd --> RegistryAmd["registry … :branch-amd64"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 01-test (Unit Tests)
|
### 01-test (Unit Tests)
|
||||||
@@ -19,31 +28,41 @@ flowchart LR
|
|||||||
|----------|-------|
|
|----------|-------|
|
||||||
| Trigger | push, pull_request, manual |
|
| Trigger | push, pull_request, manual |
|
||||||
| Branches | dev, stage, main |
|
| Branches | dev, stage, main |
|
||||||
| Image | mcr.microsoft.com/dotnet/sdk:10.0 (was `:8.0` through cycle 3 — bumped by AZ-500) |
|
| Agent | `platform: arm64` only (unit tests are arch-neutral; suite convention) |
|
||||||
|
| Image | `mcr.microsoft.com/dotnet/sdk:10.0` |
|
||||||
| Steps | `dotnet restore` → `dotnet test` (Release config) |
|
| Steps | `dotnet restore` → `dotnet test` (Release config) |
|
||||||
| Output | TRX test results |
|
| Output | TRX test results |
|
||||||
|
|
||||||
|
Integration and perf suites are **not** run in CI — they run locally via `scripts/run-tests.sh` and `scripts/run-performance-tests.sh` (Docker Compose).
|
||||||
|
|
||||||
### 02-build-push (Docker Build & Push)
|
### 02-build-push (Docker Build & Push)
|
||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| Trigger | push, manual |
|
| Trigger | push, manual |
|
||||||
| Branches | dev, stage, main |
|
| Branches | dev, stage, main |
|
||||||
| Depends on | 01-test (must pass) |
|
| Depends on | `01-test` (must pass) |
|
||||||
|
| Agent | `matrix:` fans out to `arm64` and `amd64` |
|
||||||
| Image | docker (DinD via socket mount) |
|
| Image | docker (DinD via socket mount) |
|
||||||
| Tag format | `{branch}-arm` (e.g., `dev-arm`) |
|
| Dockerfile | `SatelliteProvider.Api/Dockerfile` (same file for both arches — multi-arch base images) |
|
||||||
| Registry | Private (from secrets: registry_host, registry_user, registry_token) |
|
| Tag format | `{branch}-arm` (arm64 agent), `{branch}-amd64` (amd64 agent) |
|
||||||
|
| Registry | Gitea OCI via Caddy TLS (`registry_host`, `registry_user`, `registry_token` secrets) |
|
||||||
|
|
||||||
## Multi-Architecture Strategy
|
## Multi-Architecture Strategy
|
||||||
|
|
||||||
- Currently: ARM64 only
|
Follows the suite Woodpecker contract (`matrix:` + `labels: platform: ${PLATFORM}`):
|
||||||
- Prepared: AMD64 entry commented out in matrix
|
|
||||||
- Tag suffix distinguishes architectures (`-arm`, `-amd`)
|
| Matrix entry | Agent | Registry tag | Deploy consumer |
|
||||||
|
|--------------|-------|--------------|-----------------|
|
||||||
|
| `PLATFORM: arm64`, `TAG_SUFFIX: arm` | Colocated Jetson agent | e.g. `dev-arm` | Not used by current deploy profiles |
|
||||||
|
| `PLATFORM: amd64`, `TAG_SUFFIX: amd64` | Remote amd64 agent | e.g. `dev-amd64` | [`suite/_infra/deploy/satellite-provider/`](../../../../_infra/deploy/satellite-provider/) — Watchtower tracks `${BRANCH}-amd64` |
|
||||||
|
|
||||||
|
Production deploy is **amd64-only** (dedicated satellite-provider host). The arm64 build validates that the Dockerfile and gRPC proto codegen path work on the colocated agent.
|
||||||
|
|
||||||
## Secrets
|
## Secrets
|
||||||
|
|
||||||
| Secret | Purpose |
|
| Secret | Purpose |
|
||||||
|--------|---------|
|
|--------|---------|
|
||||||
| registry_host | Container registry URL |
|
| registry_host | Container registry URL (Gitea + Caddy, host:port) |
|
||||||
| registry_user | Registry username |
|
| registry_user | Registry username (`azaion`) |
|
||||||
| registry_token | Registry password/token |
|
| registry_token | Gitea `ci-push` PAT (`write:package`) |
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
**Base image**: `mcr.microsoft.com/dotnet/aspnet:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
|
**Base image**: `mcr.microsoft.com/dotnet/aspnet:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
|
||||||
**Build image**: `mcr.microsoft.com/dotnet/sdk:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
|
**Build image**: `mcr.microsoft.com/dotnet/sdk:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
|
||||||
**Build strategy**: Multi-stage (restore → build → publish → runtime)
|
**Build strategy**: Multi-stage (restore → build → publish → runtime)
|
||||||
|
**Target platform**: Native host architecture — no `platform:` pins in compose. **Mac M1** dev and the Woodpecker **arm64** agent run `linux/arm64`; the Woodpecker **amd64** agent runs `linux/amd64` for production images. On arm64, Docker build stages install Debian `protobuf-compiler` and set `PROTOBUF_PROTOC` (bundled `Grpc.Tools` protoc segfaults). See [tests/environment.md](../tests/environment.md) § Platform and [ci_cd_pipeline.md](ci_cd_pipeline.md) § Multi-Architecture Strategy.
|
||||||
|
|
||||||
|
**Registry tags** (Woodpecker `02-build-push`): `{branch}-arm` (arm64 agent), `{branch}-amd64` (amd64 agent). Production deploy (`suite/_infra/deploy/satellite-provider/`) pulls `${BRANCH}-amd64` via Watchtower.
|
||||||
**Exposed ports**: 8080 (HTTP), 8081 (management/metrics)
|
**Exposed ports**: 8080 (HTTP), 8081 (management/metrics)
|
||||||
|
|
||||||
## Container Composition (docker-compose.yml)
|
## Container Composition (docker-compose.yml)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
|||||||
| GET | `/api/satellite/tiles/latlon` | `GetTileByLatLon` | Download single tile by lat/lon/zoom. AZ-811 (cycle 8) renamed the query params `Latitude/Longitude/ZoomLevel` → `lat/lon/zoom` (OSM convention) and added strict validation: range-checked `lat`/`lon`/`zoom` via `WithValidation<GetTileByLatLonQuery>()`, plus a `RejectUnknownQueryParamsEndpointFilter` that rejects any extra query keys (catches typos like `?latitude=` that pre-AZ-811 silently bound to 0). Contract: `_docs/02_document/contracts/api/tile-latlon.md` v1.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
| GET | `/api/satellite/tiles/latlon` | `GetTileByLatLon` | Download single tile by lat/lon/zoom. AZ-811 (cycle 8) renamed the query params `Latitude/Longitude/ZoomLevel` → `lat/lon/zoom` (OSM convention) and added strict validation: range-checked `lat`/`lon`/`zoom` via `WithValidation<GetTileByLatLonQuery>()`, plus a `RejectUnknownQueryParamsEndpointFilter` that rejects any extra query keys (catches typos like `?latitude=` that pre-AZ-811 silently bound to 0). Contract: `_docs/02_document/contracts/api/tile-latlon.md` v1.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
||||||
| POST | `/api/satellite/tiles/inventory` | `GetTilesInventory` | Bulk tile-existence/metadata lookup (AZ-505) — body is XOR of `tiles[{z,x,y}]` (Form A) and `locationHashes[uuid]` (Form B), each capped at 5000 entries. Response is one entry per request entry, in input order. AZ-794 (cycle 7) renamed the coord triple from `tileZoom/tileX/tileY` → `z/x/y` (OSM convention); AZ-796 (cycle 7) added strict input validation via `WithValidation<TileInventoryRequest>()` so malformed payloads return RFC 7807 `ValidationProblemDetails` instead of silently coercing to zero. Contracts: `_docs/02_document/contracts/api/tile-inventory.md` v2.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
| POST | `/api/satellite/tiles/inventory` | `GetTilesInventory` | Bulk tile-existence/metadata lookup (AZ-505) — body is XOR of `tiles[{z,x,y}]` (Form A) and `locationHashes[uuid]` (Form B), each capped at 5000 entries. Response is one entry per request entry, in input order. AZ-794 (cycle 7) renamed the coord triple from `tileZoom/tileX/tileY` → `z/x/y` (OSM convention); AZ-796 (cycle 7) added strict input validation via `WithValidation<TileInventoryRequest>()` so malformed payloads return RFC 7807 `ValidationProblemDetails` instead of silently coercing to zero. Contracts: `_docs/02_document/contracts/api/tile-inventory.md` v2.0.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
||||||
| GET | `/api/satellite/tiles/mgrs` | `GetSatelliteTilesByMgrs` | MGRS stub (returns empty) |
|
| GET | `/api/satellite/tiles/mgrs` | `GetSatelliteTilesByMgrs` | MGRS stub (returns empty) |
|
||||||
| POST | `/api/satellite/upload` | `UploadUavTileBatch` | UAV tile batch upload (AZ-488) — multipart envelope, 5-rule quality gate, per-source UPSERT with `source='uav'`. Requires the `RequiresGpsPermission` policy. AZ-810 (cycle 8) added a strict **metadata-layer** validator that runs BEFORE the quality gate via the custom `UavUploadValidationFilter`: 14 rules covering required fields (`[JsonRequired]`), per-item ranges (`latitude`/`longitude`/`tileZoom`/`tileSizeMeters`/`capturedAt`), envelope alignment (`items.Count == files.Count`), and unknown-field / type-mismatch rejection via `UnmappedMemberHandling.Disallow`. Errors surface as RFC 7807 `ValidationProblemDetails` matching `error-shape.md` v1.0.0 with `errors["metadata.…"]` keys. Contract: `_docs/02_document/contracts/api/uav-tile-upload.md` v1.2.0 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
| POST | `/api/satellite/upload` | `UploadUavTileBatch` | UAV tile batch upload (AZ-488) — multipart envelope, 5-rule quality gate, per-source UPSERT with `source='uav'`. Requires the `RequiresGpsPermission` policy. AZ-810 (cycle 8) added a strict **metadata-layer** validator that runs BEFORE the quality gate via the custom `UavUploadValidationFilter`: 14 rules covering required fields (`[JsonRequired]`), per-item ranges (`latitude`/`longitude`/`tileZoom`/`tileSizeMeters`/`capturedAt`), envelope alignment (`items.Count == files.Count`), and unknown-field / type-mismatch rejection via `UnmappedMemberHandling.Disallow`. AZ-1126 (cycle 13) types `capturedAt` as `DateTimeOffset` with `UtcOffsetRequiredDateTimeOffsetConverter` — offset-less ISO strings fail at deserialization. Errors surface as RFC 7807 `ValidationProblemDetails` matching `error-shape.md` v1.0.0 with `errors["metadata.…"]` keys. Contract: `_docs/02_document/contracts/api/uav-tile-upload.md` v1.2.1 + `_docs/02_document/contracts/api/error-shape.md` v1.0.0. |
|
||||||
| POST | `/api/satellite/request` | `RequestRegion` | Queue region for async tile processing |
|
| POST | `/api/satellite/request` | `RequestRegion` | Queue region for async tile processing |
|
||||||
| GET | `/api/satellite/region/{id}` | `GetRegionStatus` | Get region processing status |
|
| GET | `/api/satellite/region/{id}` | `GetRegionStatus` | Get region processing status |
|
||||||
| 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. |
|
||||||
@@ -46,7 +46,7 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
|||||||
- `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`)
|
- `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`)
|
||||||
|
|
||||||
### Common/DTO (AZ-488)
|
### Common/DTO (AZ-488)
|
||||||
- `UavTileMetadata`, `UavTileBatchMetadataPayload` — per-item metadata + envelope shape. AZ-810 cycle 8 added `[JsonRequired]` to every non-optional axis (`latitude`, `longitude`, `tileZoom`, `tileSizeMeters`, `capturedAt` on the per-item record; `items` on the envelope) so the deserializer rejects partial payloads with HTTP 400 before the FluentValidation + `IUavTileQualityGate` layers run. `flightId` stays optional per AZ-503 anonymous-flight semantics.
|
- `UavTileMetadata`, `UavTileBatchMetadataPayload` — per-item metadata + envelope shape. AZ-810 cycle 8 added `[JsonRequired]` to every non-optional axis (`latitude`, `longitude`, `tileZoom`, `tileSizeMeters`, `capturedAt` on the per-item record; `items` on the envelope) so the deserializer rejects partial payloads with HTTP 400 before the FluentValidation + `IUavTileQualityGate` layers run. AZ-1126 (cycle 13) adds `[JsonConverter(typeof(UtcOffsetRequiredDateTimeOffsetConverter))]` on `CapturedAt` so offset-less timestamps fail at deserialization. `flightId` stays optional per AZ-503 anonymous-flight semantics.
|
||||||
- `UavTileBatchUploadResponse`, `UavTileUploadResultItem` — per-item response shape
|
- `UavTileBatchUploadResponse`, `UavTileUploadResultItem` — per-item response shape
|
||||||
- `UavTileUploadStatus`, `UavTileRejectReasons` — string-constant enumerations exposed in the v1.0.0 contract
|
- `UavTileUploadStatus`, `UavTileRejectReasons` — string-constant enumerations exposed in the v1.0.0 contract
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ Per-tile metadata payload inside a UAV batch upload (`POST /api/satellite/upload
|
|||||||
- `Latitude`, `Longitude` (double)
|
- `Latitude`, `Longitude` (double)
|
||||||
- `TileZoom` (int)
|
- `TileZoom` (int)
|
||||||
- `TileSizeMeters` (double)
|
- `TileSizeMeters` (double)
|
||||||
- `CapturedAt` (DateTime, UTC; subject to AZ-488 Rule 4 future-skew / age checks)
|
- `CapturedAt` (`DateTimeOffset`, JSON via `UtcOffsetRequiredDateTimeOffsetConverter` — AZ-1126): must include an explicit UTC offset (`Z` or `+00:00`); offset-less ISO-8601 strings are rejected at deserialization with HTTP 400. Freshness comparisons use `UtcDateTime` (no manual `DateTimeKind` normalization). Subject to AZ-488 Rule 4 future-skew / age checks at the quality-gate layer.
|
||||||
- `FlightId` (Guid?, JSON: `"flightId"`) — AZ-503 optional flight identifier. When set, the per-item `tiles.id` becomes `Uuidv5(TileNamespace, "{z}/{x}/{y}/uav/{flightId}")`, the on-disk path is `./tiles/uav/{flightId}/{z}/{x}/{y}.jpg`, and the UPSERT conflict key separates this row from rows belonging to other flights at the same cell. When `null`, the per-item id uses the zero-UUID `00000000-0000-0000-0000-000000000000` placeholder and the on-disk path uses the literal `none` segment (`./tiles/uav/none/{z}/{x}/{y}.jpg`). The placeholder UUID is purely a key-space marker — it never lands in the `flight_id` column (which stays `NULL`); the UPSERT uses `COALESCE(flight_id, '00000000-...')` for the conflict check.
|
- `FlightId` (Guid?, JSON: `"flightId"`) — AZ-503 optional flight identifier. When set, the per-item `tiles.id` becomes `Uuidv5(TileNamespace, "{z}/{x}/{y}/uav/{flightId}")`, the on-disk path is `./tiles/uav/{flightId}/{z}/{x}/{y}.jpg`, and the UPSERT conflict key separates this row from rows belonging to other flights at the same cell. When `null`, the per-item id uses the zero-UUID `00000000-0000-0000-0000-000000000000` placeholder and the on-disk path uses the literal `none` segment (`./tiles/uav/none/{z}/{x}/{y}.jpg`). The placeholder UUID is purely a key-space marker — it never lands in the `flight_id` column (which stays `NULL`); the UPSERT uses `COALESCE(flight_id, '00000000-...')` for the conflict check.
|
||||||
|
|
||||||
### UavTileBatchMetadataPayload (added AZ-488)
|
### UavTileBatchMetadataPayload (added AZ-488)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Module: Tests/SatelliteProvider.IntegrationTests
|
# Module: Tests/SatelliteProvider.IntegrationTests
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Console application that runs end-to-end integration tests against a live API instance. Designed to run in Docker alongside the API and PostgreSQL containers.
|
Console application that runs end-to-end integration tests against a live API instance. Designed to run in Docker alongside the API and PostgreSQL containers. Images build for the **host-native** Docker platform (`linux/arm64` on Apple Silicon Macs; `linux/amd64` on amd64 Linux). CI unit tests run arm64-only; production images are built on the Woodpecker amd64 agent (`{branch}-amd64`). See `docker-compose.tests.yml` header, [tests/environment.md](../tests/environment.md) § Platform, and [deployment/ci_cd_pipeline.md](../deployment/ci_cd_pipeline.md).
|
||||||
|
|
||||||
## Public Interface
|
## Public Interface
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ Console application that runs end-to-end integration tests against a live API in
|
|||||||
- `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`.
|
- `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`.
|
||||||
- `RegionRequestValidationTests`, `CreateRouteValidationTests`, `UavUploadValidationTests`, `GetTileByLatLonValidationTests` (added cycle 8 — AZ-808..AZ-811) — per-endpoint strict-validation integration suites exercising `ValidationEndpointFilter<T>` / `UavUploadValidationFilter` / `RejectUnknownQueryParamsEndpointFilter` + `GlobalExceptionHandler` end-to-end. AZ-1113 (cycle 10) tightened assertions on deserializer/binding 400 paths to expect static messages per `error-shape.md` v1.0.1; `UavUploadValidationTests.MetadataNotAnObject_Returns400` additionally asserts the response body contains no `System.` substring.
|
- `RegionRequestValidationTests`, `CreateRouteValidationTests`, `UavUploadValidationTests`, `GetTileByLatLonValidationTests` (added cycle 8 — AZ-808..AZ-811) — per-endpoint strict-validation integration suites exercising `ValidationEndpointFilter<T>` / `UavUploadValidationFilter` / `RejectUnknownQueryParamsEndpointFilter` + `GlobalExceptionHandler` end-to-end. AZ-1113 (cycle 10) tightened assertions on deserializer/binding 400 paths to expect static messages per `error-shape.md` v1.0.1; `UavUploadValidationTests.MetadataNotAnObject_Returns400` additionally asserts the response body contains no `System.` substring. AZ-1126 (cycle 13): `ItemCapturedAtOffsetLess_Returns400` — offset-less `capturedAt` rejected with HTTP 400 mentioning `capturedAt`.
|
||||||
|
|
||||||
### Supporting Classes
|
### Supporting Classes
|
||||||
- `Models.cs` — HTTP response DTOs for deserialization
|
- `Models.cs` — HTTP response DTOs for deserialization
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ Existing baseline (pre-cycle-2) test classes cover `TileService`, `RegionService
|
|||||||
- `Authentication/JwtTokenFactoryTests` — `Create_ProducesTokenValidatedByMatchingParameters`, `CreateExpired_TokenFailsValidationWithLifetimeException`, `Create_WithExtraClaims_PropagatesClaimsThroughValidation`, `TamperSignature_TokenFailsValidationWithSignatureException`. The factory itself lives in `SatelliteProvider.TestSupport` after AZ-491 (single source of truth); this project consumes it via `ProjectReference`.
|
- `Authentication/JwtTokenFactoryTests` — `Create_ProducesTokenValidatedByMatchingParameters`, `CreateExpired_TokenFailsValidationWithLifetimeException`, `Create_WithExtraClaims_PropagatesClaimsThroughValidation`, `TamperSignature_TokenFailsValidationWithSignatureException`. The factory itself lives in `SatelliteProvider.TestSupport` after AZ-491 (single source of truth); this project consumes it via `ProjectReference`.
|
||||||
|
|
||||||
### AZ-488 — UAV tile upload
|
### AZ-488 — UAV tile upload
|
||||||
- `UavTileQualityGateTests` — one happy path + ≥ 1 reject path per rule (Rule 1 INVALID_FORMAT × 2, Rule 2 SIZE_OUT_OF_BAND × 2, Rule 3 WRONG_DIMENSIONS × 1, Rule 4 CAPTURED_AT_FUTURE / _TOO_OLD × 2, Rule 5 IMAGE_TOO_UNIFORM × 1) + rule-ordering determinism. Uses a `FixedTimeProvider` for Rule-4 isolation and `UavTileImageFactory` for deterministic JPEG fixtures.
|
- `UavTileQualityGateTests` — one happy path + ≥ 1 reject path per rule (Rule 1 INVALID_FORMAT × 2, Rule 2 SIZE_OUT_OF_BAND × 2, Rule 3 WRONG_DIMENSIONS × 1, Rule 4 CAPTURED_AT_FUTURE / _TOO_OLD × 2, Rule 5 IMAGE_TOO_UNIFORM × 1) + rule-ordering determinism. Uses a `FixedTimeProvider` for Rule-4 isolation and `UavTileImageFactory` for deterministic JPEG fixtures. AZ-1126: freshness rules exercised against `DateTimeOffset` inputs.
|
||||||
|
- `UtcOffsetRequiredDateTimeOffsetConverterTests` (AZ-1126) — deserializer rejects offset-less ISO strings; accepts `Z` / `+00:00` forms.
|
||||||
- `UavTileUploadHandlerTests` — end-to-end with a mocked `ITileRepository`. Cycle-2 baseline: 1-item happy path, 3-item mixed batch (file written + `InsertAsync` called only for accepted), per-source UPSERT pass-through. AZ-503 additions: `HandleAsync_TwoFlightsSameCell_ProduceDistinctIdsAndPathsButSameLocationHash` (multi-flight coexistence with shared `location_hash`); `HandleAsync_IdenticalUpload_ProducesIdenticalIdAndDeterministicContentSha` (idempotent re-insert preserves deterministic `id` + `content_sha256`). AZ-1113 (cycle 10): `HandleAsync_InvalidMetadataJson_ReturnsEnvelopeError` — defense-in-depth metadata parse returns static envelope error (no `ex.Message` echo).
|
- `UavTileUploadHandlerTests` — end-to-end with a mocked `ITileRepository`. Cycle-2 baseline: 1-item happy path, 3-item mixed batch (file written + `InsertAsync` called only for accepted), per-source UPSERT pass-through. AZ-503 additions: `HandleAsync_TwoFlightsSameCell_ProduceDistinctIdsAndPathsButSameLocationHash` (multi-flight coexistence with shared `location_hash`); `HandleAsync_IdenticalUpload_ProducesIdenticalIdAndDeterministicContentSha` (idempotent re-insert preserves deterministic `id` + `content_sha256`). AZ-1113 (cycle 10): `HandleAsync_InvalidMetadataJson_ReturnsEnvelopeError` — defense-in-depth metadata parse returns static envelope error (no `ex.Message` echo).
|
||||||
|
|
||||||
### AZ-1124 — PT-10 gRPC stream perf harness (cycle 12)
|
### AZ-1124 — PT-10 gRPC stream perf harness (cycle 12)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Ripple Log — Cycle 13
|
||||||
|
|
||||||
|
Tasks: AZ-1126 (capturedAt DateTimeOffset / F-AZ810-2)
|
||||||
|
|
||||||
|
- `_docs/02_document/modules/common_dtos.md` — `UavTileMetadata.CapturedAt` type + converter (changed by AZ-1126)
|
||||||
|
- `_docs/02_document/modules/api_program.md` — upload endpoint contract v1.2.1 + `UtcOffsetRequiredDateTimeOffsetConverter` (changed by AZ-1126)
|
||||||
|
- `_docs/02_document/components/03_tile_downloader/description.md` — `UavTileQualityGate` Rule 4 uses `DateTimeOffset.UtcDateTime` (changed by AZ-1126)
|
||||||
|
- `_docs/02_document/modules/tests_unit.md` — `UtcOffsetRequiredDateTimeOffsetConverterTests` (Step 13)
|
||||||
|
- `_docs/02_document/modules/tests_integration.md` — `ItemCapturedAtOffsetLess_Returns400` (Step 13)
|
||||||
|
- `_docs/02_document/tests/blackbox-tests.md` — BT-34 (test-spec sync Step 12)
|
||||||
|
- `_docs/02_document/tests/traceability-matrix.md` — AZ-1126 AC-1..AC-4 rows (test-spec sync Step 12)
|
||||||
|
|
||||||
|
Contract `uav-tile-upload.md` v1.2.1 and `error-shape.md` were patched during implement (batch 01).
|
||||||
@@ -428,3 +428,22 @@ Cycle 8 extends the AZ-795 shared validation infrastructure (FluentValidation +
|
|||||||
**AC trace**: AZ-1113 AC-1..AC-3 (blackbox); AC-4 (`UavTileUploadHandler` envelope) verified by `UavTileUploadHandlerTests` unit only; AC-5 (contract doc) verified at Step 13.
|
**AC trace**: AZ-1113 AC-1..AC-3 (blackbox); AC-4 (`UavTileUploadHandler` envelope) verified by `UavTileUploadHandlerTests` unit only; AC-5 (contract doc) verified at Step 13.
|
||||||
**Notes**: This is a cross-cutting tightening of Inv-5 for 4xx paths — BT-27..BT-31 strict-validation scenarios remain the binding functional specs; BT-33 adds the message-content contract on top. SEC-14..SEC-16 mirror these three sub-cases in the security category.
|
**Notes**: This is a cross-cutting tightening of Inv-5 for 4xx paths — BT-27..BT-31 strict-validation scenarios remain the binding functional specs; BT-33 adds the message-content contract on top. SEC-14..SEC-16 mirror these three sub-cases in the security category.
|
||||||
|
|
||||||
|
## Cycle 13 — AZ-1126 `capturedAt` DateTimeOffset (F-AZ810-2 closure)
|
||||||
|
|
||||||
|
Cycle 13 tightens UAV upload metadata time handling: `UavTileMetadata.CapturedAt` is `DateTimeOffset` with a strict JSON converter requiring an explicit UTC offset. Offset-less ISO-8601 strings fail at deserialization before FluentValidation. Contract `uav-tile-upload.md` patched 1.2.0 → 1.2.1.
|
||||||
|
|
||||||
|
## BT-34: UAV Upload `capturedAt` Requires Explicit UTC Offset
|
||||||
|
|
||||||
|
**Trigger**: `POST /api/satellite/upload` multipart calls exercising AZ-1126 AC-2 and AC-3 on the existing AZ-810 validation surface.
|
||||||
|
**Precondition**: API up; valid JWT with `permissions:["GPS"]`. `uav-tile-upload.md` v1.2.1 frozen.
|
||||||
|
**Expected**: Offset-less timestamps rejected at deserializer; offset-aware UTC clients unchanged.
|
||||||
|
|
||||||
|
| # | AC | Trigger excerpt | Expected | Test method |
|
||||||
|
|---|-----|-----------------|----------|-------------|
|
||||||
|
| 1 | AC-2 | Valid JPEG + metadata with `capturedAt: "2026-06-26T12:00:00"` (no `Z` / offset) | HTTP 400; `errors` mentions `capturedAt` | `UavUploadValidationTests.ItemCapturedAtOffsetLess_Returns400` |
|
||||||
|
| 2 | AC-3 | Valid JPEG + metadata with `capturedAt` as ISO-8601 UTC (`...Z` or `...+00:00`) | HTTP 200 (happy path unchanged vs AZ-810 BT-30 `pos`) | `UavUploadValidationTests.HappyPath_Returns200` + existing AZ-488 suite |
|
||||||
|
|
||||||
|
**Pass criterion**: Sub-case 1 returns HTTP 400 with `capturedAt` in the error surface. Sub-case 2 remains green in the full integration suite (cycle 13 Step 11: 457 unit + all integration groups passed).
|
||||||
|
**AC trace**: AZ-1126 AC-1 (type migration — unit: `UtcOffsetRequiredDateTimeOffsetConverterTests`, `UavTileMetadataValidatorTests`, `UavTileQualityGateTests`); AC-2, AC-3 (blackbox); AC-4 (contract doc — verified at Step 13).
|
||||||
|
**Notes**: Closes security finding F-AZ810-2. Does not change inventory response `capturedAt` (`DateTime?` read path) or gRPC surfaces. BT-30 sub-cases 9a/9b (future/too-old freshness) remain valid for offset-aware timestamps only.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
# Test Environment
|
# Test Environment
|
||||||
|
|
||||||
|
## Platform
|
||||||
|
|
||||||
|
Three execution contexts — all use **native** Docker for the host CPU (no Rosetta / QEMU emulation).
|
||||||
|
|
||||||
|
| Context | Host CPU | Docker platform | How tests/builds run |
|
||||||
|
|---------|----------|-----------------|----------------------|
|
||||||
|
| **Local dev** | Apple Silicon Mac (`darwin/arm64`, e.g. M1) | `linux/arm64` | `scripts/run-tests.sh` sets `DOCKER_DEFAULT_PLATFORM=linux/arm64`; compose builds follow the host |
|
||||||
|
| **CI unit tests** | Woodpecker `platform: arm64` agent (colocated Jetson) | `linux/arm64` | `.woodpecker/01-test.yml` — `dotnet test` in sdk image (arm64-only by suite convention) |
|
||||||
|
| **CI image build** | Woodpecker `platform: arm64` **or** `amd64` agent | matches agent | `.woodpecker/02-build-push.yml` matrix — same Dockerfile, tags `{branch}-arm` / `{branch}-amd64` |
|
||||||
|
|
||||||
|
**Mac M1 rule**: do **not** pin `platform: linux/amd64` in compose files or Dockerfiles. That forces Rosetta/QEMU emulation and logs warnings such as `The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)`.
|
||||||
|
|
||||||
|
**amd64 agent rule**: the remote Woodpecker amd64 agent builds production images natively. Deploy (`suite/_infra/deploy/satellite-provider/`) pulls `${BRANCH}-amd64` — built only on the amd64 agent, not cross-compiled from arm64.
|
||||||
|
|
||||||
|
| Surface | Behavior |
|
||||||
|
|---------|----------|
|
||||||
|
| `scripts/run-tests.sh` | On `darwin/arm64`, exports `DOCKER_DEFAULT_PLATFORM=linux/arm64` before every `docker run` / `docker compose` |
|
||||||
|
| `docker-compose.tests.yml` | No `platform:` override — images match the host (arm64 on Mac, amd64 on an amd64 Linux dev box) |
|
||||||
|
| `docker-compose.yml` (dev / perf) | Same — no arch pin |
|
||||||
|
| API + integration Dockerfiles | Multi-arch `sdk:10.0` / `aspnet:10.0`; build stage installs Debian `protobuf-compiler` + `PROTOBUF_PROTOC=/usr/bin/protoc` on **arm64** (bundled `Grpc.Tools` `linux_arm64/protoc` segfaults; harmless on amd64) |
|
||||||
|
|
||||||
|
Suite CI/agent reference: [`suite/_infra/ci/README.md`](../../../../_infra/ci/README.md) § Agent pools and § Build-push step.
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
| Component | Technology | Configuration |
|
| Component | Technology | Configuration |
|
||||||
|
|||||||
@@ -164,6 +164,10 @@
|
|||||||
| AZ-812 AC-4 | `curl` probe with `{"id":"<guid>","lat":49.94,"lon":36.31,"sizeMeters":200,"zoomLevel":18,"stitchTiles":false}` returns HTTP 200 + valid `regionId`; old `{"latitude":..,"longitude":..}` returns HTTP 400 with `UnmappedMemberHandling.Disallow` rejecting the unknown fields | BT-28 sub-case `pos` (new names accepted) + sub-case `9` (`OldLatLongNames_Returns400` — old `latitude`/`longitude` rejected as unknown). The strict-deserializer behavior is what AZ-795's `UnmappedMemberHandling.Disallow` makes possible; pre-cycle-8 the rename would have silently coerced old names to `Lat=0, Lon=0` | ✓ |
|
| AZ-812 AC-4 | `curl` probe with `{"id":"<guid>","lat":49.94,"lon":36.31,"sizeMeters":200,"zoomLevel":18,"stitchTiles":false}` returns HTTP 200 + valid `regionId`; old `{"latitude":..,"longitude":..}` returns HTTP 400 with `UnmappedMemberHandling.Disallow` rejecting the unknown fields | BT-28 sub-case `pos` (new names accepted) + sub-case `9` (`OldLatLongNames_Returns400` — old `latitude`/`longitude` rejected as unknown). The strict-deserializer behavior is what AZ-795's `UnmappedMemberHandling.Disallow` makes possible; pre-cycle-8 the rename would have silently coerced old names to `Lat=0, Lon=0` | ✓ |
|
||||||
| AZ-812 AC-5 | Docs updated: `common_dtos.md`, `api_program.md`, `system-flows.md` (F2) | Doc-state AC — all three files updated in cycle-8 batch; verified at Step 13 review | ◐ doc-verified at Step 13 |
|
| AZ-812 AC-5 | Docs updated: `common_dtos.md`, `api_program.md`, `system-flows.md` (F2) | Doc-state AC — all three files updated in cycle-8 batch; verified at Step 13 review | ◐ doc-verified at Step 13 |
|
||||||
| AZ-812 AC-6 | Contract doc coordination: `region-request.md` v1.0.0 published directly with `lat`/`lon` (because AZ-808 + AZ-812 shipped in same cycle) — no `v1.0.0 → v2.0.0` bump needed | Doc-state AC — `region-request.md` v1.0.0 Change Log section names both AZ-808 (validation rules) and AZ-812 (`lat`/`lon` field names); verified at Step 13 review | ✓ |
|
| AZ-812 AC-6 | Contract doc coordination: `region-request.md` v1.0.0 published directly with `lat`/`lon` (because AZ-808 + AZ-812 shipped in same cycle) — no `v1.0.0 → v2.0.0` bump needed | Doc-state AC — `region-request.md` v1.0.0 Change Log section names both AZ-808 (validation rules) and AZ-812 (`lat`/`lon` field names); verified at Step 13 review | ✓ |
|
||||||
|
| AZ-1126 AC-1 | `UavTileMetadata.CapturedAt` is `DateTimeOffset`; freshness comparisons use UTC without manual `DateTimeKind` normalization | `UtcOffsetRequiredDateTimeOffsetConverterTests` (unit); `UavTileMetadataValidatorTests` + `UavTileQualityGateTests` + `UavTileUploadHandlerTests` (unit); existing AZ-810 BT-30 sub-cases 9a/9b remain green with offset-aware timestamps (cycle 13 Step 11 full run) | ✓ |
|
||||||
|
| AZ-1126 AC-2 | Offset-less `capturedAt` (no explicit UTC offset) rejected with HTTP 400 referencing `capturedAt` | BT-34 sub-case 1 (blackbox); `UavUploadValidationTests.ItemCapturedAtOffsetLess_Returns400` (integration) | ✓ |
|
||||||
|
| AZ-1126 AC-3 | Compliant clients sending `Z` or `+00:00` timestamps unchanged | BT-34 sub-case 2 (blackbox); `UavUploadValidationTests.HappyPath_Returns200` + full AZ-488 integration suite green (cycle 13 Step 11) | ✓ |
|
||||||
|
| AZ-1126 AC-4 | `uav-tile-upload.md` v1.2.1 documents offset requirement and F-AZ810-2 closure | Doc-state AC — contract patch in cycle 13 batch; verified at Step 13 review | ✓ |
|
||||||
|
|
||||||
## Restrictions → Test Mapping
|
## Restrictions → Test Mapping
|
||||||
|
|
||||||
@@ -220,7 +224,8 @@
|
|||||||
| Cycle 10 — AZ-1113 REST 400 error message sanitization (integration + unit + blackbox + contract patch) | 3 integration assertion paths (inventory deserializer, latlon bind, UAV metadata) + 3 unit methods (`GlobalExceptionHandlerTests` ×2, `UavTileUploadHandlerTests` ×1) + 1 blackbox (BT-33 with 3 sub-cases) + 3 security (SEC-14..SEC-16) + `error-shape.md` v1.0.1 patch | 5/5 in-scope (AZ-1113 AC-1..AC-5) | — |
|
| Cycle 10 — AZ-1113 REST 400 error message sanitization (integration + unit + blackbox + contract patch) | 3 integration assertion paths (inventory deserializer, latlon bind, UAV metadata) + 3 unit methods (`GlobalExceptionHandlerTests` ×2, `UavTileUploadHandlerTests` ×1) + 1 blackbox (BT-33 with 3 sub-cases) + 3 security (SEC-14..SEC-16) + `error-shape.md` v1.0.1 patch | 5/5 in-scope (AZ-1113 AC-1..AC-5) | — |
|
||||||
| Cycle 11 — AZ-1123 perf compose documentation (deployment + test env docs) | doc-only (`containerization.md` compose overlays, `environment.md` perf cross-link) | 3/3 in-scope (AZ-1123 AC-1..AC-3); doc-verified at Step 13 | — |
|
| Cycle 11 — AZ-1123 perf compose documentation (deployment + test env docs) | doc-only (`containerization.md` compose overlays, `environment.md` perf cross-link) | 3/3 in-scope (AZ-1123 AC-1..AC-3); doc-verified at Step 13 | — |
|
||||||
| Cycle 12 — AZ-1124 PT-10 gRPC stream perf (perf harness + unit) | 1 perf (PT-10) + 3 unit (`PerfBootstrapPt10Tests`) + integration bootstrap (`SatelliteProvider.IntegrationTests --run-pt10`) | 6/6 in-scope (AZ-1124 AC-1..AC-6); 1 AC gated at Step 15 (AC-3); 1 doc-verified at Step 13 (AC-5) | — |
|
| Cycle 12 — AZ-1124 PT-10 gRPC stream perf (perf harness + unit) | 1 perf (PT-10) + 3 unit (`PerfBootstrapPt10Tests`) + integration bootstrap (`SatelliteProvider.IntegrationTests --run-pt10`) | 6/6 in-scope (AZ-1124 AC-1..AC-6); 1 AC gated at Step 15 (AC-3); 1 doc-verified at Step 13 (AC-5) | — |
|
||||||
| **Total** | **173** | **130/130 in-scope (100%); 3 ACs gated at Step 15 (2 AZ-504 + 1 AZ-1124 AC-3); 11 prior-cycle ACs doc-verified at Step 13 (2 cycle-7 + 8 cycle-8 + 1 AZ-1124 AC-5 pending); 2 advisory non-tested (cycle-8 AZ-809 AC-9/AC-10)** | **8/8 (100%)** |
|
| Cycle 13 — AZ-1126 capturedAt DateTimeOffset (integration + unit + blackbox + contract patch) | 1 integration method (`UavUploadValidationTests.ItemCapturedAtOffsetLess_Returns400`) + 4 unit files (`UtcOffsetRequiredDateTimeOffsetConverterTests`, updated UAV validator/gate/handler tests) + 1 blackbox (BT-34 with 2 sub-cases) + `uav-tile-upload.md` v1.2.1 patch | 4/4 in-scope (AZ-1126 AC-1..AC-4); 1 doc-verified at Step 13 (AC-4); closes F-AZ810-2 | — |
|
||||||
|
| **Total** | **174** | **134/134 in-scope (100%); 3 ACs gated at Step 15 (2 AZ-504 + 1 AZ-1124 AC-3); 11 prior-cycle ACs doc-verified at Step 13 (2 cycle-7 + 8 cycle-8 + 1 AZ-1124 AC-5); 2 advisory non-tested (cycle-8 AZ-809 AC-9/AC-10)** | **8/8 (100%)** |
|
||||||
|
|
||||||
**Coverage shape notes (Cycle 5 — AZ-503 foundation):**
|
**Coverage shape notes (Cycle 5 — AZ-503 foundation):**
|
||||||
- AZ-503 was split mid-cycle (Option C, autodev Step 10 batch 2): 7 of 12 original ACs land here; 5 (AC-5, AC-6, AC-9, AC-10, AC-12) are deferred to AZ-505 with a `Blocks` link in Jira and an entry in `_docs/02_tasks/_dependencies_table.md`. The deferred rows above are marked `◐ deferred → AZ-505` so the matrix surfaces the scope boundary explicitly.
|
- AZ-503 was split mid-cycle (Option C, autodev Step 10 batch 2): 7 of 12 original ACs land here; 5 (AC-5, AC-6, AC-9, AC-10, AC-12) are deferred to AZ-505 with a `Blocks` link in Jira and an entry in `_docs/02_tasks/_dependencies_table.md`. The deferred rows above are marked `◐ deferred → AZ-505` so the matrix surfaces the scope boundary explicitly.
|
||||||
|
|||||||
@@ -264,6 +264,13 @@ Step 9 cycle 10: 1 task created (AZ-1113 = 2 pts) — REST 400 error message san
|
|||||||
Step 9 cycle 11: 1 task created (AZ-1123 = 1 pt) — document `docker-compose.perf.yml` host-port conflict playbook (cycle 10 retro action).
|
Step 9 cycle 11: 1 task created (AZ-1123 = 1 pt) — document `docker-compose.perf.yml` host-port conflict playbook (cycle 10 retro action).
|
||||||
Step 9 cycle 12: 1 task created (AZ-1124 = 3 pts) — PT-10 gRPC `DeliverRouteTiles` stream perf scenario (cycle 9–11 retro carry-over).
|
Step 9 cycle 12: 1 task created (AZ-1124 = 3 pts) — PT-10 gRPC `DeliverRouteTiles` stream perf scenario (cycle 9–11 retro carry-over).
|
||||||
Step 9 cycle 13: 1 task created (AZ-1126 = 2 pts) — `DateTime` → `DateTimeOffset` on `UavTileMetadata.capturedAt` (F-AZ810-2). Child of AZ-795.
|
Step 9 cycle 13: 1 task created (AZ-1126 = 2 pts) — `DateTime` → `DateTimeOffset` on `UavTileMetadata.capturedAt` (F-AZ810-2). Child of AZ-795.
|
||||||
|
Step 9 cycle 14: 1 task created (AZ-1131 = 1 pt) — align `environment.md` integration command with `run-tests.sh` (cycle 13 retro carry-over).
|
||||||
|
|
||||||
|
### Step 9 cycle 14 (environment.md integration command — AZ-1131)
|
||||||
|
|
||||||
|
| Task | Depends On | Points | Status |
|
||||||
|
|------|-----------|--------|--------|
|
||||||
|
| AZ-1131 environment.md integration command | — | 1 | Todo |
|
||||||
|
|
||||||
### Step 9 cycle 13 (capturedAt DateTimeOffset — AZ-1126)
|
### Step 9 cycle 13 (capturedAt DateTimeOffset — AZ-1126)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Align environment.md integration test command with run-tests.sh
|
||||||
|
|
||||||
|
**Task**: AZ-1131_environment_md_integration_command
|
||||||
|
**Name**: Align environment.md integration command with run-tests.sh
|
||||||
|
**Description**: Fix stale integration-test orchestration docs that still reference `docker-compose.yml` + `docker-compose.tests.yml` while `scripts/run-tests.sh` uses self-contained `docker-compose.tests.yml` only.
|
||||||
|
**Complexity**: 1 points
|
||||||
|
**Dependencies**: None
|
||||||
|
**Component**: test environment documentation
|
||||||
|
**Tracker**: AZ-1131 (https://denyspopov.atlassian.net/browse/AZ-1131)
|
||||||
|
**Epic**: None
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
`_docs/02_document/tests/environment.md` § Test Execution documents:
|
||||||
|
|
||||||
|
`docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit`
|
||||||
|
|
||||||
|
`scripts/run-tests.sh` (canonical Step 11 gate) runs integration tests with:
|
||||||
|
|
||||||
|
`docker compose -f docker-compose.tests.yml up --build --abort-on-container-exit --exit-code-from integration-tests`
|
||||||
|
|
||||||
|
`docker-compose.tests.yml` is self-contained (postgres + api + integration-tests). The dual-file command is stale since the test stack was split out. This mismatch has been a carry-over since cycle 12 retro and misleads operators copying docs instead of the script.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- `environment.md` matches the real integration test orchestration path
|
||||||
|
- Operators can run integration tests by following either `environment.md` or `run-tests.sh` without conflicting instructions
|
||||||
|
- Stale dual-compose references in agent-facing docs are corrected or cross-linked
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### Included
|
||||||
|
|
||||||
|
- Update `environment.md` § Test Execution to document `docker-compose.tests.yml` only and cite `scripts/run-tests.sh` as the canonical entry point
|
||||||
|
- Ripple fix: `README.md` and `AGENTS.md` integration-test command lines (same stale dual-compose pattern)
|
||||||
|
- No compose file or script behavior changes
|
||||||
|
|
||||||
|
### Excluded
|
||||||
|
|
||||||
|
- Historical task specs in `_docs/02_tasks/done/` (AZ-285 AC-2 records the old command — leave as historical artifact)
|
||||||
|
- Perf orchestration docs (already correct — uses `docker-compose.yml` + `docker-compose.perf.yml`)
|
||||||
|
- CI Woodpecker pipeline changes
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
**AC-1: environment.md accuracy**
|
||||||
|
Given `environment.md` § Test Execution
|
||||||
|
When an operator reads the documented integration command
|
||||||
|
Then it matches `scripts/run-tests.sh` Step 2 (`docker compose -f docker-compose.tests.yml …`) and names the script as canonical
|
||||||
|
|
||||||
|
**AC-2: Agent-facing doc ripple**
|
||||||
|
Given `README.md` and `AGENTS.md` integration test sections
|
||||||
|
When an operator follows those instructions
|
||||||
|
Then they reference `docker-compose.tests.yml` only (or point to `./scripts/run-tests.sh` exclusively)
|
||||||
|
|
||||||
|
**AC-3: No runtime change**
|
||||||
|
Given the task is complete
|
||||||
|
When `./scripts/run-tests.sh --smoke` is run
|
||||||
|
Then behavior is unchanged (docs-only delta)
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
**Compatibility**
|
||||||
|
- Documented manual command must remain valid on darwin/arm64 without requiring `docker-compose.yml`
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
| AC Ref | What to Test | Required Outcome |
|
||||||
|
|--------|-------------|-----------------|
|
||||||
|
| AC-3 | N/A (docs only) | Verified by grep + optional smoke run |
|
||||||
|
|
||||||
|
## Blackbox Tests
|
||||||
|
|
||||||
|
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|
||||||
|
|--------|------------------------|-------------|-------------------|----------------|
|
||||||
|
| AC-1 | `environment.md` on disk | Ripgrep for `docker-compose.yml -f docker-compose.tests.yml` | Zero matches in `environment.md` | — |
|
||||||
|
| AC-2 | `README.md`, `AGENTS.md` | Ripgrep for stale dual-compose integration command | Zero matches or explicit redirect to `run-tests.sh` | — |
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Documentation-only — no compose or script edits unless a comment in `run-tests.sh` is needed for cross-link clarity
|
||||||
|
|
||||||
|
## Risks & Mitigation
|
||||||
|
|
||||||
|
**Risk 1: Operators relied on dual-compose for host port mapping**
|
||||||
|
- *Mitigation*: `docker-compose.tests.yml` does not publish postgres to host; document that this is intentional (internal network only)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Deploy Report — Cycle 13 (AZ-1126)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Cycle**: 13
|
||||||
|
**Scope**: Migrate UAV upload `capturedAt` to `DateTimeOffset` (F-AZ810-2 closure).
|
||||||
|
|
||||||
|
## What is shipping
|
||||||
|
|
||||||
|
### Code changes
|
||||||
|
|
||||||
|
| Area | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `UavTileMetadata.CapturedAt` | `DateTime` → `DateTimeOffset` |
|
||||||
|
| `UtcOffsetRequiredDateTimeOffsetConverter` | **New** — rejects offset-less ISO-8601 at deserialization |
|
||||||
|
| `UavTileMetadataValidator` | Freshness rules use `UtcDateTime` without `DateTimeKind` branching |
|
||||||
|
| `UavTileQualityGate` / `UavTileUploadHandler` | UTC comparisons without manual kind normalization |
|
||||||
|
| `UavUploadValidationFilter` | Propagates converter `JsonException.Message` for metadata parse failures |
|
||||||
|
| Unit + integration tests | Offset-less rejection + backward-compatible `Z` / `+00:00` happy paths |
|
||||||
|
| `uav-tile-upload.md` | v1.2.0 → **v1.2.1** (offset requirement documented) |
|
||||||
|
|
||||||
|
### Database migrations
|
||||||
|
|
||||||
|
**None.**
|
||||||
|
|
||||||
|
### Configuration changes
|
||||||
|
|
||||||
|
| Setting | Change |
|
||||||
|
|---------|--------|
|
||||||
|
| New env vars | **None** |
|
||||||
|
| Container image | Rebuild only — same `aspnet:10.0` base; no Dockerfile changes |
|
||||||
|
| Consumer contracts | `uav-tile-upload.md` patch bump — wire shape unchanged for offset-aware clients |
|
||||||
|
|
||||||
|
### Contract changes (consumer-visible)
|
||||||
|
|
||||||
|
| Contract | Change | Consumer action |
|
||||||
|
|----------|--------|-----------------|
|
||||||
|
| `uav-tile-upload.md` v1.2.1 | `capturedAt` must include explicit UTC offset (`Z` or `+00:00`); offset-less strings → HTTP 400 | UAV upload clients sending `"2026-06-26T12:00:00"` without offset must add `Z` or `+00:00` |
|
||||||
|
| REST / gRPC wire shapes (other) | Unchanged | No action |
|
||||||
|
|
||||||
|
## Verification gates passed in this cycle
|
||||||
|
|
||||||
|
| Gate | Result | Evidence |
|
||||||
|
|------|--------|----------|
|
||||||
|
| Step 11 — Functional tests | **PASS** | Full suite via `./scripts/run-tests.sh` (mode=full) |
|
||||||
|
| Step 12 — Test-Spec Sync | **PASS** | Traceability AZ-1126 AC-1..AC-4 |
|
||||||
|
| Step 13 — Update Docs | **PASS** | `ripple_log_cycle13.md`, module + contract docs |
|
||||||
|
| Step 14 — Security Audit | **PASS** (delta) | `security_report_cycle13.md`; F-AZ810-2 **resolved** |
|
||||||
|
| Step 15 — Performance Test | **PASS** | `perf_2026-06-26_cycle13.md` — 11/11 thresholds |
|
||||||
|
|
||||||
|
## Security carry-overs (post-cycle-13)
|
||||||
|
|
||||||
|
| ID | Status |
|
||||||
|
|----|--------|
|
||||||
|
| F-AZ810-2 | **Resolved** (AZ-1126) |
|
||||||
|
| D-AZ795-1 | Open — FluentValidation 12.0.0 → 12.1.1 |
|
||||||
|
| D2-cy4 | Open — test SDK JWT advisory (test-runtime only) |
|
||||||
|
|
||||||
|
## Operator runbook
|
||||||
|
|
||||||
|
1. **Commit and push** cycle-13 changes to `origin/dev`; confirm Woodpecker `01-test` + `02-build-push` green.
|
||||||
|
2. **No migration** — deploy new API image only.
|
||||||
|
3. **Smoke-test** after deploy:
|
||||||
|
- POST `/api/satellite/upload` with valid JPEG + `capturedAt` lacking offset → HTTP 400 referencing `capturedAt`
|
||||||
|
- POST with `capturedAt` as ISO-8601 UTC (`...Z` or `...+00:00`) → HTTP 200 on otherwise valid payload
|
||||||
|
- Region, route, inventory, gRPC tile delivery happy paths unchanged
|
||||||
|
4. **Notify UAV upload consumers** (`gps-denied-onboard`, mission planner) of the offset requirement per contract v1.2.1.
|
||||||
|
|
||||||
|
## Release note
|
||||||
|
|
||||||
|
`/release` prerequisites (`scripts/deploy.sh`, `_docs/04_release/`) are **not present** in this repo — production promotion remains operator-driven (image build + compose on target host). Step 16.5 should be **skipped** unless release infrastructure is onboarded.
|
||||||
|
|
||||||
|
**Verdict**: Cleared for retrospective (Step 17). Release (16.5) skipped — no release execution harness.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Dependency Scan (Cycle 13)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: Cycle-13 delta over cycle-10 baseline. Surface = AZ-1126 (`DateTimeOffset` migration — no package manifest changes).
|
||||||
|
**Method**: `dotnet list SatelliteProvider.sln package --vulnerable`.
|
||||||
|
|
||||||
|
## Cycle-13 Package Manifest Diff
|
||||||
|
|
||||||
|
| csproj | Cycle 10 baseline | Cycle 13 change |
|
||||||
|
|--------|-------------------|-----------------|
|
||||||
|
| All csproj | unchanged | **+0** packages added or bumped |
|
||||||
|
|
||||||
|
## Vulnerable Package Scan (2026-06-26)
|
||||||
|
|
||||||
|
| Project | Finding | Severity | Notes |
|
||||||
|
|---------|---------|----------|-------|
|
||||||
|
| `SatelliteProvider.Api` | none | — | Production runtime — clean |
|
||||||
|
| `SatelliteProvider.Common` | none | — | `UtcOffsetRequiredDateTimeOffsetConverter` is in-repo code |
|
||||||
|
| `SatelliteProvider.IntegrationTests` | transitive JWT 7.0.3 | Moderate | GHSA-59j7-ghrg-fj52 — test-runtime only (pre-existing) |
|
||||||
|
| `SatelliteProvider.TestSupport` | `System.IdentityModel.Tokens.Jwt` 7.0.3 | Moderate | test-runtime only — pre-existing |
|
||||||
|
|
||||||
|
## Cycle-13 Findings
|
||||||
|
|
||||||
|
**No new dependency CVEs.** AZ-1126 is a code-only DTO/converter change.
|
||||||
|
|
||||||
|
## Carry-overs
|
||||||
|
|
||||||
|
- **D-AZ795-1** (Low): FluentValidation 12.0.0 → 12.1.1 — still open
|
||||||
|
- **D2-cy4** (Medium, test-runtime): JWT test packages — still open
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-13 delta) — zero new CVEs.
|
||||||
|
|
||||||
|
Cumulative: **PASS_WITH_WARNINGS** — D2-cy4 + D-AZ795-1 carry-overs unchanged.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Infrastructure & Configuration Review (Cycle 13)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: Cycle-13 infrastructure changes only.
|
||||||
|
|
||||||
|
| File | Change | Security relevance |
|
||||||
|
|------|--------|-------------------|
|
||||||
|
| All Docker / compose / CI / appsettings | **unchanged** | AZ-1126 is application-code + contract doc only |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-13 delta) — no infrastructure surface change.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# OWASP Top 10 Review (Cycle 13)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Framework**: OWASP Top 10:2021
|
||||||
|
**Mode**: Delta review — AZ-1126 over cycle-10 baseline.
|
||||||
|
|
||||||
|
| Category | Cycle-10 status | Cycle-13 delta |
|
||||||
|
|----------|-----------------|----------------|
|
||||||
|
| A01 — Broken Access Control | PASS | No change |
|
||||||
|
| A02 — Cryptographic Failures | PASS | No change |
|
||||||
|
| A03 — Injection | PASS | No change |
|
||||||
|
| A04 — Insecure Design | PASS | No change |
|
||||||
|
| A05 — Security Misconfiguration | PASS | No change |
|
||||||
|
| A06 — Vulnerable Components | PASS_WITH_WARNINGS | No new packages; D-AZ795-1 + D2-cy4 carry-overs unchanged |
|
||||||
|
| A07 — Auth Failures | PASS | No change |
|
||||||
|
| A08 — Data Integrity Failures | PASS | Improved time-handling integrity on UAV upload metadata |
|
||||||
|
| A09 — Logging / Monitoring Failures | PASS_WITH_WARNINGS → **improved** | F-AZ810-2 **resolved**; F-AZ795-1/2 + F-AZ810-1 remain resolved |
|
||||||
|
| A10 — SSRF | N/A | No URL-fetch changes |
|
||||||
|
|
||||||
|
## A08 / A09 detail
|
||||||
|
|
||||||
|
AZ-1126 eliminates ambiguous `DateTimeKind.Unspecified` handling on the UAV upload metadata input path. Offset-less client timestamps now fail fast with HTTP 400 instead of being interpreted against host local timezone in dev environments.
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-13 delta).
|
||||||
|
|
||||||
|
Cumulative: **PASS_WITH_WARNINGS** — dependency carry-overs only (D-AZ795-1, D2-cy4).
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Security Audit Report (Cycle 13)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Scope**: Cycle-13 delta — AZ-1126 (`capturedAt` DateTimeOffset / F-AZ810-2 closure).
|
||||||
|
**Trigger**: `/autodev` Step 14 — user chose **A) Run security audit**.
|
||||||
|
**Verdict (cycle-13 delta)**: **PASS** — F-AZ810-2 resolved; 0 new Critical/High/Medium.
|
||||||
|
**Verdict (cumulative)**: **PASS_WITH_WARNINGS** — D-AZ795-1, D2-cy4 remain open.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Severity | Cycle 13 at audit | Cumulative open |
|
||||||
|
|----------|-------------------|-----------------|
|
||||||
|
| Critical | 0 | 0 |
|
||||||
|
| High | 0 | 0 |
|
||||||
|
| Medium | 0 | 1 (D2-cy4 test-runtime) |
|
||||||
|
| Low | 0 new | 1 (D-AZ795-1) |
|
||||||
|
|
||||||
|
## OWASP Top 10:2021 (cycle-13 delta)
|
||||||
|
|
||||||
|
See `owasp_review_cycle13.md` — A08/A09 improved; all other categories unchanged PASS/N/A.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
| # | Severity | Category | Location | Title | Status |
|
||||||
|
|---|----------|----------|----------|-------|--------|
|
||||||
|
| F-AZ810-2 | Low | Time-handling (A08/A09) | `UavTileMetadata.CapturedAt` | `DateTime` vs `DateTimeOffset` | **RESOLVED** (AZ-1126) |
|
||||||
|
|
||||||
|
## Carry-overs (still open)
|
||||||
|
|
||||||
|
- **D-AZ795-1** — FluentValidation 12.0.0 → 12.1.1
|
||||||
|
- **D2-cy4** — test SDK transitive JWT advisory (Moderate, test-runtime only)
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Immediate
|
||||||
|
- None blocking cycle 13 ship.
|
||||||
|
|
||||||
|
### Short-term
|
||||||
|
- D-AZ795-1: bump FluentValidation when a coordinated package bump task lands.
|
||||||
|
|
||||||
|
### Long-term
|
||||||
|
- D2-cy4: pin JWT test packages when upstream resolves GHSA-59j7-ghrg-fj52 for 7.0.3 line.
|
||||||
|
|
||||||
|
## Artifacts
|
||||||
|
|
||||||
|
- `dependency_scan_cycle13.md`
|
||||||
|
- `static_analysis_cycle13.md`
|
||||||
|
- `owasp_review_cycle13.md`
|
||||||
|
- `infrastructure_review_cycle13.md`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Static Analysis (Cycle 13)
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Mode**: Delta scan
|
||||||
|
**Scope**: AZ-1126 `capturedAt` → `DateTimeOffset` + `UtcOffsetRequiredDateTimeOffsetConverter`. Cycle-10 baseline remains authoritative elsewhere.
|
||||||
|
|
||||||
|
**Files in scope**:
|
||||||
|
- `SatelliteProvider.Common/DTO/UavTileMetadata.cs`
|
||||||
|
- `SatelliteProvider.Common/Json/UtcOffsetRequiredDateTimeOffsetConverter.cs`
|
||||||
|
- `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs`
|
||||||
|
- `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs`
|
||||||
|
- `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs`
|
||||||
|
- `SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs`
|
||||||
|
- Unit + integration tests for offset-less rejection
|
||||||
|
|
||||||
|
**Method**: Read changed call sites; verify offset-less ISO strings rejected before persistence; confirm no new `ex.Message` echoes; grep for remaining `DateTimeKind` branching on upload path.
|
||||||
|
|
||||||
|
## Resolved findings (AZ-1126)
|
||||||
|
|
||||||
|
### F-AZ810-2 — `UavTileMetadata.CapturedAt` typed `DateTime` not `DateTimeOffset` (Low / Informational) — **RESOLVED**
|
||||||
|
|
||||||
|
- **Location**: `UavTileMetadata.cs`, validators, quality gate, upload handler.
|
||||||
|
- **Resolution**: `CapturedAt` is `DateTimeOffset` with `UtcOffsetRequiredDateTimeOffsetConverter` rejecting offset-less strings at deserialization. Freshness rules compare via `UtcDateTime`. Integration test `ItemCapturedAtOffsetLess_Returns400` binds the rejection path.
|
||||||
|
|
||||||
|
## Pass areas (cycle-13 delta)
|
||||||
|
|
||||||
|
| Area | Result |
|
||||||
|
|------|--------|
|
||||||
|
| SQL injection | N/A — no SQL changes |
|
||||||
|
| Hardcoded secrets | None introduced |
|
||||||
|
| Information disclosure (400 paths) | Unchanged from AZ-1113 — static strings preserved |
|
||||||
|
| New attack surface | Narrower — ambiguous timestamps rejected earlier |
|
||||||
|
| Inventory read path | `TileInventoryEntry.CapturedAt` remains `DateTime?` — intentional, out of scope |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS** (cycle-13 delta) — F-AZ810-2 closed; zero new findings.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Performance Report — Cycle 13
|
||||||
|
|
||||||
|
**Date**: 2026-06-26
|
||||||
|
**Cycle**: 13 (AZ-1126 capturedAt DateTimeOffset)
|
||||||
|
**Runner**: `scripts/run-performance-tests.sh` (default: `PERF_REPEAT_COUNT=20`, `PERF_UAV_BATCH_SIZE=10`, `PERF_PT10_SLOW_MS=50`)
|
||||||
|
**Stack**: `docker compose -f docker-compose.yml -f docker-compose.perf.yml up -d --build`
|
||||||
|
**API_URL**: `https://localhost:18980`
|
||||||
|
**Verdict**: **PASS** (11/11 thresholds; exit 0)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
First perf attempt failed at PT-01 (exit 7) because the perf stack was not running — resolved by starting the compose overlay before re-run.
|
||||||
|
|
||||||
|
## REST scenarios (PT-01..PT-08)
|
||||||
|
|
||||||
|
| Scenario | Result | Key metric |
|
||||||
|
|----------|--------|------------|
|
||||||
|
| PT-01 cold tile | Pass | 1519 ms |
|
||||||
|
| PT-02 cached tile | Pass | 230 ms |
|
||||||
|
| PT-03 region 200m | Pass | 2433 ms |
|
||||||
|
| PT-04 region 500m stitch | Pass | 2158 ms |
|
||||||
|
| PT-05 concurrent regions | Pass | 2378 ms |
|
||||||
|
| PT-06 route create | Pass | 203 ms |
|
||||||
|
| PT-07 cold/warm | Pass | warm p95 54 ms vs cold 2148 ms |
|
||||||
|
| PT-08 UAV batch | Pass | batch p95 225 ms |
|
||||||
|
|
||||||
|
AZ-1126 does not change perf probe payloads (PT-08 still uses offset-aware `capturedAt` from fixture generator).
|
||||||
|
|
||||||
|
## PT-10 (gRPC stream)
|
||||||
|
|
||||||
|
| Metric | p50 | p95 | Threshold | Verdict |
|
||||||
|
|--------|-----|-----|-----------|---------|
|
||||||
|
| first_batch_ms | 43 ms | 63 ms | ≤ 30000 ms | Pass |
|
||||||
|
| total_stream_ms | 43 ms | 64 ms | ≤ 120000 ms | Pass |
|
||||||
|
| slow-consumer | — | — | completes without DeliveryError | Pass |
|
||||||
|
|
||||||
|
Iteration 1 cold path ~3950 ms first batch (empty volume); iterations 2–20 warm cached.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Retrospective — Cycle 13 (2026-06-26)
|
||||||
|
|
||||||
|
**Tasks**: AZ-1126 (capturedAt DateTimeOffset, 2 SP). **1 task, 2 SP, 1 batch.**
|
||||||
|
**Mode**: cycle-end. Step 16.5 (Release) **skipped** (no release harness).
|
||||||
|
**Previous retro**: `retro_2026-06-26_cycle12.md`
|
||||||
|
|
||||||
|
## Implementation Summary
|
||||||
|
|
||||||
|
| Metric | Cycle 13 | Δ vs cycle 12 |
|
||||||
|
|--------|----------|---------------|
|
||||||
|
| Tasks implemented | **1** | unchanged |
|
||||||
|
| Total complexity delivered | **2 SP** | -1 SP |
|
||||||
|
| Blocked tasks | **0** | unchanged |
|
||||||
|
| Auto-fix attempts | **1** (filter message propagation) | new |
|
||||||
|
|
||||||
|
## Quality
|
||||||
|
|
||||||
|
| Gate | Result |
|
||||||
|
|------|--------|
|
||||||
|
| Code review | PASS_WITH_WARNINGS (batch_01_cycle13) |
|
||||||
|
| Step 11 full suite | **PASS** (pre-verified in Step 10) |
|
||||||
|
| Step 14 security | **PASS** (delta) — F-AZ810-2 **resolved** |
|
||||||
|
| Step 15 perf | **PASS** 11/11 (first run failed: stack not up) |
|
||||||
|
| Step 16 deploy | **PASS** — `deploy_cycle13.md` |
|
||||||
|
|
||||||
|
## Cycle 13 delta
|
||||||
|
|
||||||
|
- **F-AZ810-2 closed** — cycle 12 retro Action #1 shipped; `DateTimeOffset` + strict converter eliminates `DateTimeKind` ambiguity on UAV upload path.
|
||||||
|
- **Filter regression caught in batch** — generic `JsonException` handler initially masked converter diagnostics; fixed by propagating exception message.
|
||||||
|
- **Security finding → task → closure** — 2 SP dedicated cycle resolved a multi-cycle Low carry-over without scope creep.
|
||||||
|
|
||||||
|
## Comparison with cycle 12 retro actions
|
||||||
|
|
||||||
|
| Cycle 12 action | Cycle 13 outcome |
|
||||||
|
|-----------------|------------------|
|
||||||
|
| F-AZ810-2 DateTimeOffset (~1 SP) | **Done** (AZ-1126) |
|
||||||
|
| Align `environment.md` integration command | **Open** — not scheduled |
|
||||||
|
| PT-09 shell harness promotion | **Open** — optional |
|
||||||
|
|
||||||
|
## Top 3 Improvement Actions (cycle 14 candidates)
|
||||||
|
|
||||||
|
1. **D-AZ795-1** — bump FluentValidation 12.0.0 → 12.1.1 (~1 SP) — sole remaining Low prod dependency finding
|
||||||
|
2. **Align `environment.md` integration command** with `run-tests.sh` (`docker-compose.tests.yml` only) (~0.5 SP) — third-cycle carry-over
|
||||||
|
3. **Perf gate preflight** — document or script-check that perf compose stack is up before PT-01 (~0.5 SP) — cycle 13 first-run exit 7
|
||||||
|
|
||||||
|
## Cycle 13 Verdict
|
||||||
|
|
||||||
|
**Successful security-closure cycle** — AZ-1126 delivered a focused type migration with full gate coverage (security + perf + deploy). One auto-fix in the validation filter; no architecture drift.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Structural Snapshot — 2026-06-26 (post-cycle 13, capturedAt DateTimeOffset)
|
||||||
|
|
||||||
|
Cycle 13 delta against `structure_2026-06-25_cycle10.md` (no cycle 11/12 structure snapshots on disk). Source: `_docs/02_document/module-layout.md` + on-disk `*.csproj` graph.
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
| Layer | csproj | Cycle 13 delta |
|
||||||
|
|-------|--------|----------------|
|
||||||
|
| 2 (Common) | `SatelliteProvider.Common` | `UtcOffsetRequiredDateTimeOffsetConverter` + `UavTileMetadata.CapturedAt` type change |
|
||||||
|
| 4 (API) | `SatelliteProvider.Api` | Validator + `UavUploadValidationFilter` message propagation |
|
||||||
|
| 3 (Application) | `SatelliteProvider.Services.TileDownloader` | Quality gate + upload handler UTC comparisons |
|
||||||
|
| 6 (Tests) | `SatelliteProvider.Tests`, `SatelliteProvider.IntegrationTests` | Converter + UAV validation tests |
|
||||||
|
|
||||||
|
**Project count**: **10** (unchanged).
|
||||||
|
|
||||||
|
## Cross-Project Import Edges
|
||||||
|
|
||||||
|
**Total ProjectReference edges**: **23** (unchanged). **Import cycles**: 0.
|
||||||
|
|
||||||
|
## Contract coverage
|
||||||
|
|
||||||
|
| Surface | Contract | Cycle 13 delta |
|
||||||
|
|---------|----------|----------------|
|
||||||
|
| UAV upload metadata | `uav-tile-upload.md` v1.2.1 | patch — explicit UTC offset on `capturedAt` |
|
||||||
|
| gRPC `DeliverRouteTiles` | `tile_provision.proto` | unchanged |
|
||||||
|
| REST error envelope | `error-shape.md` v1.0.1 | unchanged |
|
||||||
|
|
||||||
|
**gRPC perf coverage**: PT-10 verified (cycle 12); cycle 13 did not regress.
|
||||||
+6
-4
@@ -41,6 +41,12 @@ If the enum's wire string happens to match a member name case-insensitively (e.g
|
|||||||
Source: _docs/06_metrics/perf_2026-06-26_cycle12.md
|
Source: _docs/06_metrics/perf_2026-06-26_cycle12.md
|
||||||
## Ring buffer (last 15 entries — newest at top)
|
## Ring buffer (last 15 entries — newest at top)
|
||||||
|
|
||||||
|
- [2026-06-26] [process] Multi-cycle security carry-overs that name a concrete finding ID and fit ≤2 SP ship cleanly as a sole cycle theme — cycle 12 retro Action #1 → cycle 13 AZ-1126 closed F-AZ810-2 in one batch with full security + perf gate coverage.
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-26_cycle13.md
|
||||||
|
- [2026-06-26] [testing] Custom `JsonConverter` exceptions must propagate through boundary filters — a generic metadata parse string in `UavUploadValidationFilter` masked `UtcOffsetRequiredDateTimeOffsetConverter` diagnostics until integration tests failed (cycle 13 AZ-1126 auto-fix).
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-26_cycle13.md
|
||||||
|
- [2026-06-26] [tooling] Step 15 perf gate exit 7 on first run when the perf compose stack is not up — preflight with `docker compose -f docker-compose.yml -f docker-compose.perf.yml up -d` before `run-performance-tests.sh` or add a health check to the script (cycle 13).
|
||||||
|
Source: _docs/06_metrics/retro_2026-06-26_cycle13.md
|
||||||
- [2026-06-25] [testing] PT-07 cold-vs-warm region latency is sensitive to outlier cold p95 on a warm compose volume — the perf gate should drain the region queue before the warm pass and accept warm p50 < cold p50 when p95 is within noise (cycle 10: two marginal PT-07 FAILs before harness fix; AZ-1113 did not touch region paths).
|
- [2026-06-25] [testing] PT-07 cold-vs-warm region latency is sensitive to outlier cold p95 on a warm compose volume — the perf gate should drain the region queue before the warm pass and accept warm p50 < cold p50 when p95 is within noise (cycle 10: two marginal PT-07 FAILs before harness fix; AZ-1113 did not touch region paths).
|
||||||
Source: _docs/06_metrics/retro_2026-06-25_cycle10.md
|
Source: _docs/06_metrics/retro_2026-06-25_cycle10.md
|
||||||
- [2026-06-25] [process] Retrospective security recommendations that name concrete finding IDs (F-AZ795-1/2, F-AZ810-1) and fit ≤2 SP can ship as the sole cycle theme and close multi-cycle carry-overs in one batch — cycle 9 Action #3 → cycle 10 AZ-1113 resolved all three Low A09 findings.
|
- [2026-06-25] [process] Retrospective security recommendations that name concrete finding IDs (F-AZ795-1/2, F-AZ810-1) and fit ≤2 SP can ship as the sole cycle theme and close multi-cycle carry-overs in one batch — cycle 9 Action #3 → cycle 10 AZ-1113 resolved all three Low A09 findings.
|
||||||
@@ -65,7 +71,3 @@ If the enum's wire string happens to match a member name case-insensitively (e.g
|
|||||||
Source: _docs/06_metrics/retro_2026-05-22_cycle7.md
|
Source: _docs/06_metrics/retro_2026-05-22_cycle7.md
|
||||||
- [2026-05-22] [testing] When a strict-validation layer ships (`JsonSerializerOptions.UnmappedMemberHandling.Disallow`, FluentValidation rules, explicit DTO `[JsonRequired]`), expect the project's own integration tests to surface latent bugs the prior lenient defaults had been masking — silent PascalCase fallback property names, out-of-range fixture coordinates, wrong-cased JSON keys; correct them in the same PR or the test suite goes red and the strict layer looks like a regression instead of the bug-finder it is (cycle 7: `IdempotentPostTests.RoutePoint` had been posting `{"Lat":...}` against a `[JsonPropertyName("lat")]` DTO for months; the new strict deserializer caught it and the 2-line payload fix landed alongside the strict layer; **cycle 8 instance**: `UavUploadTests.NextTestCoordinate` produced lat > 90°, caught by AZ-810 validator, 2-file clamp fix in batch 4).
|
- [2026-05-22] [testing] When a strict-validation layer ships (`JsonSerializerOptions.UnmappedMemberHandling.Disallow`, FluentValidation rules, explicit DTO `[JsonRequired]`), expect the project's own integration tests to surface latent bugs the prior lenient defaults had been masking — silent PascalCase fallback property names, out-of-range fixture coordinates, wrong-cased JSON keys; correct them in the same PR or the test suite goes red and the strict layer looks like a regression instead of the bug-finder it is (cycle 7: `IdempotentPostTests.RoutePoint` had been posting `{"Lat":...}` against a `[JsonPropertyName("lat")]` DTO for months; the new strict deserializer caught it and the 2-line payload fix landed alongside the strict layer; **cycle 8 instance**: `UavUploadTests.NextTestCoordinate` produced lat > 90°, caught by AZ-810 validator, 2-file clamp fix in batch 4).
|
||||||
Source: _docs/06_metrics/retro_2026-05-22_cycle7.md
|
Source: _docs/06_metrics/retro_2026-05-22_cycle7.md
|
||||||
- [2026-05-22] [architecture] Contract MAJOR bumps for projects with ≤2 known consumers should ship without a wire-format adapter — the cost of maintaining a dual-accepting endpoint outweighs the benefit when the operator runbook can coordinate the consumer cut-over directly; only invest in an adapter when consumer count or release-cadence asymmetry makes coordinated cut-over impractical (cycle 7: `tile-inventory.md` 1.0.0 → 2.0.0 renamed `tileZoom/tileX/tileY → z/x/y` and shipped breaking; the only consumer is `gps-denied-onboard` and the AZ-777 follow-up loop handled the coordination via the operator runbook in `deploy_cycle7.md`).
|
|
||||||
Source: _docs/06_metrics/retro_2026-05-22_cycle7.md
|
|
||||||
- [2026-05-12] [tooling] Kestrel `HttpProtocols.Http1AndHttp2` silently serves only HTTP/1.1 over a plaintext listener — ALPN requires TLS, so any "enable HTTP/2" task without TLS in its definition-of-done will downgrade transparently and the only log line is at INFO; tasks that mention HTTP/2 / h2 / multiplexing / ALPN MUST resolve the TLS-vs-h2c choice at spec-write time and the test gate MUST assert `HttpVersion == 2.0` not just a 200 (cycle 6: AZ-505 AC-5 first landed on h2c plaintext, required a post-merge TLS+ALPN pivot with dev-cert plumbing across compose/tests/perf/docs).
|
|
||||||
Source: _docs/06_metrics/retro_2026-05-12_cycle6.md
|
|
||||||
|
|||||||
@@ -2,23 +2,23 @@
|
|||||||
|
|
||||||
## Current Step
|
## Current Step
|
||||||
flow: existing-code
|
flow: existing-code
|
||||||
step: 11
|
step: 10
|
||||||
name: Run Tests
|
name: Implement
|
||||||
status: not_started
|
status: in_progress
|
||||||
sub_step:
|
sub_step:
|
||||||
phase: 0
|
phase: 1
|
||||||
name: awaiting-invocation
|
name: parse
|
||||||
detail: ""
|
detail: ""
|
||||||
retry_count: 0
|
retry_count: 0
|
||||||
cycle: 13
|
cycle: 14
|
||||||
tracker: jira
|
tracker: jira
|
||||||
auto_push: true
|
auto_push: true
|
||||||
|
|
||||||
## Last Completed Cycle
|
## Last Completed Cycle
|
||||||
cycle: 12
|
cycle: 13
|
||||||
step_14_security: skipped
|
step_14_security: completed
|
||||||
step_15_perf: completed
|
step_15_perf: completed
|
||||||
step_16_deploy: skipped
|
step_16_deploy: completed
|
||||||
step_16_5_release: skipped
|
step_16_5_release: skipped
|
||||||
step_17_retrospective: completed
|
step_17_retrospective: completed
|
||||||
verdict: cycle_complete
|
verdict: cycle_complete
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
# Integration test stack.
|
||||||
|
# Platform: native host arch — linux/arm64 on Apple Silicon Mac (M1+), linux/amd64
|
||||||
|
# on an amd64 Linux host. No platform: pin (Mac must not force linux/amd64 / Rosetta).
|
||||||
|
# CI: unit tests arm64-only (.woodpecker/01-test.yml); image builds arm64 + amd64
|
||||||
|
# (.woodpecker/02-build-push.yml matrix). See tests/environment.md § Platform.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16
|
image: postgres:16
|
||||||
@@ -13,7 +19,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
api:
|
api:
|
||||||
platform: linux/amd64
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: SatelliteProvider.Api/Dockerfile
|
dockerfile: SatelliteProvider.Api/Dockerfile
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
api:
|
api:
|
||||||
platform: linux/amd64
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: SatelliteProvider.Api/Dockerfile
|
dockerfile: SatelliteProvider.Api/Dockerfile
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
case "$(uname -m)" in
|
||||||
|
arm64|aarch64)
|
||||||
|
export DOCKER_DEFAULT_PLATFORM=linux/arm64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
docker compose -f "$PROJECT_ROOT/docker-compose.tests.yml" down --remove-orphans || true
|
docker compose -f "$PROJECT_ROOT/docker-compose.tests.yml" down --remove-orphans || true
|
||||||
}
|
}
|
||||||
@@ -80,6 +86,13 @@ Environment:
|
|||||||
the API container validates. May be a DEV-ONLY value for local runs.
|
the API container validates. May be a DEV-ONLY value for local runs.
|
||||||
JWT_AUDIENCE Required for any integration test mode (AZ-494). Same contract as
|
JWT_AUDIENCE Required for any integration test mode (AZ-494). Same contract as
|
||||||
JWT_ISSUER — must match what the API container validates.
|
JWT_ISSUER — must match what the API container validates.
|
||||||
|
|
||||||
|
Platform:
|
||||||
|
Mac M1+ (darwin/arm64): scripts/run-tests.sh sets DOCKER_DEFAULT_PLATFORM=linux/arm64.
|
||||||
|
Do not pin platform: linux/amd64 locally — forces Rosetta/QEMU emulation.
|
||||||
|
Woodpecker CI: unit tests on arm64 agent only; image builds fan out to arm64
|
||||||
|
({branch}-arm) and amd64 ({branch}-amd64) agents. Production deploy pulls
|
||||||
|
*-amd64 (suite/_infra/deploy/satellite-provider/).
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user