diff --git a/SatelliteProvider.IntegrationTests/GrpcTestHelpers.cs b/SatelliteProvider.IntegrationTests/GrpcTestHelpers.cs index b58de6c..9c8dfeb 100644 --- a/SatelliteProvider.IntegrationTests/GrpcTestHelpers.cs +++ b/SatelliteProvider.IntegrationTests/GrpcTestHelpers.cs @@ -18,7 +18,7 @@ public static class GrpcTestHelpers var caCertPath = ResolveCaCertPath(); if (!string.IsNullOrEmpty(caCertPath)) { - var caCert = X509Certificate2.CreateFromPemFile(caCertPath); + var caCert = X509Certificate2.CreateFromPem(File.ReadAllText(caCertPath)); handler.SslOptions = new SslClientAuthenticationOptions { RemoteCertificateValidationCallback = (_, certificate, _, errors) => diff --git a/_docs/02_document/modules/tests_integration.md b/_docs/02_document/modules/tests_integration.md index d3a178a..e4e6df0 100644 --- a/_docs/02_document/modules/tests_integration.md +++ b/_docs/02_document/modules/tests_integration.md @@ -24,7 +24,7 @@ Console application that runs end-to-end integration tests against a live API in ### Supporting Classes - `Models.cs` — HTTP response DTOs for deserialization - `RouteTestHelpers.cs` — shared utilities (wait-for-completion polling, geofence polygon builders, test data) -- `Program.cs` — test runner entry point (handles `--smoke` / `--full` mode selection, `--keep-state` opt-out flag, default-token issuance via `JwtTokenFactory`, the AZ-493 DB-reset hook, and the AZ-492 `--mint-only` / `--gen-uav-fixture` perf-bootstrap subcommands that short-circuit before any HTTP / DB setup) +- `Program.cs` — test runner entry point (handles `--smoke` / `--full` mode selection, `--keep-state` opt-out flag, default-token issuance via `JwtTokenFactory`, the AZ-493 DB-reset hook, and the AZ-492 perf-bootstrap subcommands `--mint-only` / `--gen-uav-fixture` plus AZ-1124 `--run-pt10` that short-circuit before any HTTP / DB setup) - `JwtTestHelpers.cs` — runner-side JWT concerns: - `ResolveSecretOrThrow` reads `JWT_SECRET` env var with size validation - `ResolveIssuerOrThrow` / `ResolveAudienceOrThrow` (AZ-494) read `JWT_ISSUER` / `JWT_AUDIENCE` with fail-fast contract @@ -34,7 +34,7 @@ Console application that runs end-to-end integration tests against a live API in - `DefaultSubject = "integration-tests"` is the canonical runner subject value - Token *minting* lives in the shared `SatelliteProvider.TestSupport.JwtTokenFactory` (AZ-491) — runner-side concerns (env reads, HttpClient mutation, the iss/aud-aware mint wrapper) deliberately stay here. - `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 --mint-only` and `--gen-uav-fixture `. +- `PerfBootstrap.cs` (AZ-492; PT-10 extended by AZ-1124) — 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; `RunPt10Async()` opens a gRPC channel via `GrpcTestHelpers`, streams `DeliverRouteTiles` for `PERF_REPEAT_COUNT` cold iterations plus one slow-consumer pass (`PERF_PT10_SLOW_MS` delay between events), and prints `PT10_*` metric lines to stdout for `scripts/run-performance-tests.sh` to gate. Invoked via `dotnet --mint-only`, `--gen-uav-fixture `, and `--run-pt10`. - `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`. diff --git a/_docs/02_document/modules/tests_unit.md b/_docs/02_document/modules/tests_unit.md index 9cb82a0..9b499d8 100644 --- a/_docs/02_document/modules/tests_unit.md +++ b/_docs/02_document/modules/tests_unit.md @@ -17,6 +17,9 @@ Existing baseline (pre-cycle-2) test classes cover `TileService`, `RegionService - `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. - `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) +- `PerfBootstrapPt10Tests` — static-review + percentile helper coverage for the perf bootstrap: `Percentile_MatchesHarnessFormula_AZ1124_AC2` (p50/p95 math matches shell gate), `PerfScript_DoesNotInlineJwtMint_AZ1124_AC6` (grep `run-performance-tests.sh` for `--run-pt10` and absence of inline `JwtSecurityToken` mint), `Program_DispatchesRunPt10Subcommand_AZ1124_AC1` (grep `Program.cs` for `--run-pt10` dispatch). + ### AZ-1113 — REST 400 error message sanitization (cycle 10) - `GlobalExceptionHandlerTests` — extends AZ-795/353 coverage: `TryHandleAsync_DeserializationFailure_WritesValidationProblemDetailsWithJsonPath_AZ795` asserts `errors[]` values are `"The field value is invalid."` with no `.NET` type leak; `TryHandleAsync_BadHttpRequestExceptionWithoutJson_UsesStaticDetail` asserts non-JSON bind failures emit `detail: "The request could not be processed."` (existing 5xx sanitization tests unchanged). - `Authentication/PermissionsRequirementTests` — `PermissionsAuthorizationHandler` correctly accepts a `permissions` claim shaped as a single string OR as a JSON array, rejects when the requested permission is absent, and short-circuits when the principal has no `permissions` claim at all. diff --git a/_docs/02_document/ripple_log_cycle12.md b/_docs/02_document/ripple_log_cycle12.md new file mode 100644 index 0000000..d189f0b --- /dev/null +++ b/_docs/02_document/ripple_log_cycle12.md @@ -0,0 +1,14 @@ +# Ripple Log — Cycle 12 + +Tasks: AZ-1124 (PT-10 gRPC stream perf) + +- `_docs/02_document/tests/performance-tests.md` — PT-10 scenario block (changed by AZ-1124) +- `_docs/02_document/tests/traceability-matrix.md` — AZ-1124 AC-1..AC-6 + NFR row + cycle-12 coverage notes (test-spec sync) +- `scripts/run-performance-tests.sh` — PT-10 section + header PT-01..PT-10 (changed by AZ-1124) +- `SatelliteProvider.IntegrationTests/PerfBootstrap.cs` + `Program.cs` — `--run-pt10` bootstrap (changed by AZ-1124) +- `SatelliteProvider.Tests/PerfBootstrapPt10Tests.cs` — AC-6 static-review unit tests (changed by AZ-1124) +- `_docs/02_document/modules/tests_integration.md` — `--run-pt10` subcommand docs (Step 13) +- `_docs/02_document/modules/tests_unit.md` — `PerfBootstrapPt10Tests` entry (Step 13) +- `_docs/02_document/tests/environment.md` — `PERF_PT10_SLOW_MS` / PT-10 env vars (Step 13) + +No new blackbox or security scenarios. gRPC functional surface unchanged (BT-32 remains binding). diff --git a/_docs/02_document/tests/environment.md b/_docs/02_document/tests/environment.md index a3d2589..c2b9f23 100644 --- a/_docs/02_document/tests/environment.md +++ b/_docs/02_document/tests/environment.md @@ -46,7 +46,17 @@ **Hardware dependencies found**: None **Execution method**: `docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit` -**Performance tests** (Step 15 / `scripts/run-performance-tests.sh`): start the API with the perf overlay when host port 5433 is occupied — `docker compose -f docker-compose.yml -f docker-compose.perf.yml up -d --build`. Details: [containerization.md](../deployment/containerization.md#compose-overlays-dev--test--perf). +**Performance tests** (Step 15 / `scripts/run-performance-tests.sh`): start the API with the perf overlay when host port 5433 is occupied — `docker compose -f docker-compose.yml -f docker-compose.perf.yml up -d --build`. Details: [containerization.md](../deployment/containerization.md#compose-overlays-dev--test--perf). PT-10 (`DeliverRouteTiles` gRPC stream) runs via `dotnet SatelliteProvider.IntegrationTests --run-pt10` inside the script; host-side default `API_URL=https://localhost:18980` with TLS trust via `./certs/api.crt` (`PERF_CA_CERT` override). Harness knobs: + +| Variable | Default | Purpose | +|----------|---------|---------| +| `PERF_REPEAT_COUNT` | 20 | Cold iterations for PT-07/PT-08/PT-10 distribution | +| `PERF_UAV_BATCH_SIZE` | 10 | Items per PT-08 upload batch | +| `PERF_PT10_SLOW_MS` | 50 | Delay between gRPC stream events on the slow-consumer sub-check (PT-10) | +| `PERF_JWT_TOKEN` | (minted in-script) | Pre-minted Bearer token; skips `--mint-only` when set | +| `PERF_CA_CERT` | `$PROJECT_ROOT/certs/api.crt` | TLS trust anchor for host-side perf probes (REST + gRPC) | + +See [performance-tests.md](performance-tests.md) § PT-10 for pass thresholds. | Property | Value | |----------|-------| diff --git a/_docs/02_document/tests/traceability-matrix.md b/_docs/02_document/tests/traceability-matrix.md index 2c3328d..396319d 100644 --- a/_docs/02_document/tests/traceability-matrix.md +++ b/_docs/02_document/tests/traceability-matrix.md @@ -204,7 +204,7 @@ |----------|-------------|-------------|---------------------| | Blackbox (positive) | 12 | 19/22 | — | | Blackbox (negative) | 5 | — | — | -| Performance | 9 | 5 | 1 | +| Performance | 10 | 6 | 1 | | Resilience | 6 | 4 | 3 | | Security | 14 | 9 (AZ-487 AC-1..AC-7, AZ-488 AC-6, leak-hygiene NFR) + 3 (AZ-1113 AC-1..AC-3) | 1 (AZ-487 supersedes "No authentication") | | Resource Limits | 7 | 5 | 4 | @@ -219,7 +219,8 @@ | Cycle 9 — AZ-1074 + AZ-1075 gRPC RouteTileDelivery (integration + unit + blackbox) | 1 integration file (`RouteTileDeliveryGrpcTests`) + orchestrator unit tests + 1 blackbox (BT-32 with 6 sub-cases) + `SatelliteProvider.GrpcContracts` | 7/7 (AZ-1074 AC-1..AC-4, AZ-1075 AC-1..AC-3) | — | | 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 | — | -| **Total** | **170** | **124/124 in-scope (100%); 2 AZ-504 ACs gated at Step 15; 10 prior-cycle ACs doc-verified at Step 13 (2 cycle-7 + 8 cycle-8); 2 advisory non-tested (cycle-8 AZ-809 AC-9/AC-10)** | **8/8 (100%)** | +| 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%)** | **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. @@ -285,11 +286,17 @@ | AZ-1123 AC-3 | Integration (`docker-compose.tests.yml` only) vs perf overlay distinction documented | doc-state AC; verified at Step 13 (both deployment + test env docs) | ✓ | | AZ-1124 AC-1 | PT-10 exercises real gRPC `DeliverRouteTiles` stream with Bearer metadata | PT-10 (performance); `SatelliteProvider.IntegrationTests --run-pt10` (integration bootstrap) | ✓ | | AZ-1124 AC-2 | PT-10 reports `first_batch_ms` + `total_stream_ms` p50/p95 | PT-10 stdout metrics (`PT10_*` lines) | ✓ | -| AZ-1124 AC-3 | PT-10 threshold gate (`p95(first_batch_ms) ≤ 30000`, `p95(total_stream_ms) ≤ 120000`) | PT-10 shell gate in `scripts/run-performance-tests.sh` | ◐ gate at Step 15 | +| AZ-1124 AC-3 | PT-10 threshold gate (`p95(first_batch_ms) ≤ 30000`, `p95(total_stream_ms) ≤ 120000`) | PT-10 shell gate in `scripts/run-performance-tests.sh`; cycle 12 Step 15 measured p95=48ms / 48ms | ✓ | | AZ-1124 AC-4 | PT-10 slow-consumer smoke completes without `DeliveryError` | PT-10 `PT10_SLOW_CONSUMER=PASS` sub-check | ✓ | -| AZ-1124 AC-5 | PT-10 documented in `performance-tests.md`; gRPC stream perf no longer Unverified | doc-state AC; verified at Step 13 | ◐ doc-verified at Step 13 | +| AZ-1124 AC-5 | PT-10 documented in `performance-tests.md`; gRPC stream perf no longer Unverified | doc-state AC; verified at Step 13 | ✓ | | AZ-1124 AC-6 | PT-10 reuses `PerfBootstrap` / `JwtTokenFactory` / `GrpcTestHelpers` — no third JWT mint in shell | `PerfBootstrapPt10Tests` (unit — static review) | ✓ | +**Coverage shape notes (Cycle 12 — AZ-1124 PT-10 gRPC stream perf):** +- First gRPC **performance** scenario — closes cycle 9–11 retro carry-over where BT-32 / AZ-1074 functional coverage left stream latency `Unverified`. PT-10 reuses `GrpcTestHelpers`, `PerfBootstrap` JWT mint, and the standard 2-waypoint fixture; no new production RPC or proto change. +- AC-3 (p95 thresholds) and AC-5 (spec/traceability doc state) follow the established split: threshold gate at Step 15; doc-state AC verified at Step 13 (`performance-tests.md` § PT-10, module docs for `--run-pt10`). +- `PerfBootstrapPt10Tests` (3 methods) satisfies AC-6 static-review gate — no third JWT mint in the shell script. +- Cycle-update rule check: no NFR conflicts. PT-10 cold-path budgets align with PT-01 family (GM download on fresh volume). + **Coverage shape notes (Cycle 11 — AZ-1123 perf compose documentation):** - Documentation-only cycle — no new runtime tests, blackbox scenarios, perf thresholds, or security findings. Cycle-update adds traceability rows only; existing Step 11 smoke (450/450) is regression evidence. - Closes cycle 9/10 retro action to document `docker-compose.perf.yml` (file landed cycle 10; playbook landed cycle 11). diff --git a/_docs/02_tasks/_dependencies_table.md b/_docs/02_tasks/_dependencies_table.md index 941f8b2..b168658 100644 --- a/_docs/02_tasks/_dependencies_table.md +++ b/_docs/02_tasks/_dependencies_table.md @@ -263,12 +263,13 @@ Step 9 cycle 9: 2 tasks created (AZ-1074 = 5 pts, AZ-1075 = 3 pts) — total 8 p Step 9 cycle 10: 1 task created (AZ-1113 = 2 pts) — REST 400 error message sanitization (F-AZ795-1/2, F-AZ810-1). Child of AZ-795. 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 13: 1 task created (AZ-1126 = 2 pts) — `DateTime` → `DateTimeOffset` on `UavTileMetadata.capturedAt` (F-AZ810-2). Child of AZ-795. -### Step 9 cycle 12 (PT-10 gRPC stream perf — AZ-1124) +### Step 9 cycle 13 (capturedAt DateTimeOffset — AZ-1126) | Task | Depends On | Points | Status | |------|-----------|--------|--------| -| AZ-1124 PT-10 gRPC stream perf scenario | AZ-1074, AZ-1075, AZ-492 | 3 | Todo | +| AZ-1126 capturedAt DateTimeOffset (F-AZ810-2) | AZ-810, AZ-488 | 2 | Todo | ## Coverage Verification diff --git a/_docs/02_tasks/todo/AZ-1126_captured_at_datetimeoffset.md b/_docs/02_tasks/todo/AZ-1126_captured_at_datetimeoffset.md new file mode 100644 index 0000000..50b8199 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-1126_captured_at_datetimeoffset.md @@ -0,0 +1,106 @@ +# Migrate UAV upload capturedAt to DateTimeOffset + +**Task**: AZ-1126_captured_at_datetimeoffset +**Name**: Migrate UavTileMetadata.capturedAt to DateTimeOffset (F-AZ810-2) +**Description**: Close security carry-over F-AZ810-2 by typing `UavTileMetadata.CapturedAt` as `DateTimeOffset` instead of `DateTime`, eliminating ambiguous `DateTimeKind.Unspecified` handling on the UAV upload metadata input path. +**Complexity**: 2 points +**Dependencies**: AZ-810 (HARD — metadata validation layer); AZ-488 (original upload endpoint) +**Component**: SatelliteProvider.Common (UavTileMetadata) + SatelliteProvider.Api (validators) + SatelliteProvider.Services.TileDownloader (quality gate + upload handler) +**Tracker**: AZ-1126 +**Epic**: AZ-795 + +## Problem + +Security finding F-AZ810-2 (cycle 8, open through cycle 12) flags that `UavTileMetadata.CapturedAt` is typed `DateTime` rather than `DateTimeOffset`. `DateTime` accepts `DateTimeKind.Unspecified` values that deserialize from offset-less ISO-8601 strings, forcing manual `Kind` normalization in the upload handler and quality gate. This is a time-handling correctness gap that can skew freshness-window checks in non-UTC dev environments. + +## Outcome + +- `capturedAt` on the UAV upload metadata input is unambiguously UTC-aware at the type level +- Offset-less or `Unspecified` timestamps are rejected before persistence +- F-AZ810-2 is marked resolved in the next security audit cycle +- Wire compatibility preserved for clients sending ISO-8601 UTC with explicit offset (`Z` or `+00:00`) + +## Scope + +### Included + +- Change `UavTileMetadata.CapturedAt` from `DateTime` to `DateTimeOffset` +- Update FluentValidation rules, quality gate, and upload handler to compare via UTC without manual `Kind` branching +- Reject offset-less / ambiguous `capturedAt` values with HTTP 400 +- Unit tests for the new rejection path and existing freshness-window rules +- Integration test proving offset-less `capturedAt` is rejected +- Patch `_docs/02_document/contracts/api/uav-tile-upload.md` 1.2.0 → 1.2.1 (change log + clarify offset requirement) + +### Excluded + +- `TileInventoryEntry.CapturedAt` response field (remains `DateTime?` — DB read path) +- `TileEntity.CapturedAt` persistence layer type +- Changes to gRPC tile delivery or other non-UAV-upload surfaces +- MAJOR contract version bump (wire JSON shape unchanged for compliant clients) + +## Acceptance Criteria + +**AC-1: Type migration** +Given the UAV upload metadata DTO +When deserialized from JSON +Then `CapturedAt` is `DateTimeOffset` and freshness comparisons use UTC without manual `DateTimeKind` normalization + +**AC-2: Reject ambiguous timestamps** +Given a UAV upload batch with `capturedAt` lacking an explicit UTC offset (offset-less ISO string) +When POST `/api/satellite/upload` +Then HTTP 400 with a validation or deserialization error referencing `capturedAt` + +**AC-3: Backward-compatible UTC clients** +Given a UAV upload batch with `capturedAt` as ISO-8601 UTC (`...Z` or `...+00:00`) +When POST `/api/satellite/upload` with otherwise valid payload +Then HTTP 200 (or the same non-timestamp rejection as before timestamp validation) + +**AC-4: Contract patch** +Given the implementation is complete +When `uav-tile-upload.md` is reviewed +Then version is 1.2.1 with a change-log entry documenting the offset requirement and F-AZ810-2 closure + +## Non-Functional Requirements + +**Compatibility** +- Compliant clients already sending `Z`-suffixed timestamps must not break + +**Security** +- Closes F-AZ810-2 (Low / informational time-handling finding) + +## Unit Tests + +| AC Ref | What to Test | Required Outcome | +|--------|-------------|-----------------| +| AC-1 | `UavTileMetadataValidator` freshness window with `DateTimeOffset` | Future/too-old rules still fire | +| AC-2 | Deserializer or validator with offset-less `capturedAt` | Rejected | +| AC-1 | `UavTileQualityGate` captured-at rules | Still accept/reject correctly | + +## Blackbox Tests + +| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References | +|--------|------------------------|-------------|-------------------|----------------| +| AC-2 | Valid JPEG + metadata with `capturedAt: "2026-06-26T12:00:00"` (no offset) | POST `/api/satellite/upload` | HTTP 400 mentioning `capturedAt` | — | +| AC-3 | Valid JPEG + metadata with `capturedAt` as `DateTime.UtcNow.ToString("o")` | POST `/api/satellite/upload` | HTTP 200 (happy path unchanged) | Compatibility | + +## Constraints + +- Must remain a child of epic AZ-795 strict-validation theme +- No breaking wire change for offset-aware clients + +## Risks & Mitigation + +**Risk 1: Client sends offset-less timestamps** +- *Risk*: Legitimate clients using `"2026-06-26T12:00:00"` without `Z` start failing +- *Mitigation*: Contract patch documents requirement; rejection is intentional per F-AZ810-2 + +## Contract + +This task patches the producer contract at `_docs/02_document/contracts/api/uav-tile-upload.md` (1.2.0 → 1.2.1). + +Consumers: `gps-denied-onboard`, mission planner UI, any UAV upload client. + +### Document Dependencies + +- `_docs/02_document/contracts/api/uav-tile-upload.md` v1.2.0 (patch to 1.2.1) +- `_docs/02_document/contracts/api/error-shape.md` v1.0.0 (unchanged) diff --git a/_docs/06_metrics/perf_2026-06-26_cycle12.md b/_docs/06_metrics/perf_2026-06-26_cycle12.md new file mode 100644 index 0000000..e919040 --- /dev/null +++ b/_docs/06_metrics/perf_2026-06-26_cycle12.md @@ -0,0 +1,26 @@ +# Performance Report — Cycle 12 + +**Date**: 2026-06-26 +**Cycle**: 12 (AZ-1124 PT-10 gRPC stream perf) +**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) + +## PT-10 (new — AZ-1124) + +| Metric | p50 | p95 | Threshold | Verdict | +|--------|-----|-----|-----------|---------| +| first_batch_ms | 43 ms | 48 ms | ≤ 30000 ms | Pass | +| total_stream_ms | 43 ms | 48 ms | ≤ 120000 ms | Pass | +| slow-consumer | — | — | completes without DeliveryError | Pass | + +Warm compose volume — iterations 2–20 served cached tiles; iteration 1 cold path 150 ms first batch. + +## REST scenarios (PT-01..PT-08) + +All passed on warm volume. Notable: PT-07 cold p95=106 ms, warm p95=65 ms (warm faster). + +## Step 15 fix applied during gate + +First run failed PT-10: `CreateFromPemFile(api.crt)` rejected cert-only PEM on host-side gRPC channel. Fixed in `GrpcTestHelpers.CreateChannel` — load CA via `X509Certificate2.CreateFromPem(File.ReadAllText(...))`. Re-run green. diff --git a/_docs/06_metrics/retro_2026-06-26_cycle12.md b/_docs/06_metrics/retro_2026-06-26_cycle12.md new file mode 100644 index 0000000..155568b --- /dev/null +++ b/_docs/06_metrics/retro_2026-06-26_cycle12.md @@ -0,0 +1,37 @@ +# Retrospective — Cycle 12 (2026-06-26) + +**Tasks**: AZ-1124 (PT-10 gRPC stream perf, 3 SP). **1 task, 3 SP, 1 batch.** +**Mode**: cycle-end. Steps 14 (Security) and 16/16.5 (Deploy/Release) **skipped**. +**Previous retro**: `retro_2026-06-25_cycle11.md` + +## Implementation Summary + +| Metric | Cycle 12 | Δ vs cycle 11 | +|--------|----------|---------------| +| Tasks implemented | **1** | unchanged | +| Total complexity delivered | **3 SP** | +2 SP | +| Blocked tasks | **0** | unchanged | + +## Quality + +| Gate | Result | +|------|--------| +| Code review | PASS (batch_01_cycle12) | +| Step 11 full suite | **PASS** (exit 0, ~21 min) | +| Step 15 perf (PT-01..PT-10) | **PASS** after `GrpcTestHelpers` PEM fix | +| Security | Skipped (harness-only; no prod surface) | + +## Cycle 12 delta + +- **PT-10 landed** — first gRPC perf scenario; closes cycle 9–11 retro Action #2. +- **Step 15 caught TLS bug** — `CreateFromPemFile(api.crt)` fails on cert-only PEM for host-side gRPC; fixed to `CreateFromPem(text)`. + +## Top 3 Improvement Actions (cycle 13 candidates) + +1. **F-AZ810-2** `DateTime` → `DateTimeOffset` on `capturedAt` (~1 SP) — carry-over +2. **Align `environment.md` integration command** with `run-tests.sh` (`docker-compose.tests.yml` only) (~0.5 SP) — carry-over +3. **Promote PT-09** inventory inline test into shell harness if production SLO tightens (~2 SP) — optional + +## Cycle 12 Verdict + +**Successful perf-harness cycle** — AZ-1124 delivered PT-10 end-to-end; gRPC stream NFR no longer Unverified. Minor TLS loading fix required at Step 15 gate. diff --git a/_docs/LESSONS.md b/_docs/LESSONS.md index cade7d6..97bb6ed 100644 --- a/_docs/LESSONS.md +++ b/_docs/LESSONS.md @@ -37,6 +37,8 @@ If the enum's wire string happens to match a member name case-insensitively (e.g - [2026-06-25] [process] Documentation-only autodev cycles should still run Step 12 traceability rows (doc-verified ACs) and Step 13 ripple logs, while Steps 14–15 are appropriately skipped when no code surface changes — avoids empty cycle artifacts without running meaningless security/perf gates (cycle 11: AZ-1123). Source: _docs/06_metrics/retro_2026-06-25_cycle11.md +- [2026-06-26] [tooling] Host-side gRPC perf (`--run-pt10`) must load cert-only PEM CA files via `X509Certificate2.CreateFromPem(text)` — `CreateFromPemFile(api.crt)` throws when no private key is present; REST curl `--cacert` tolerates cert-only but .NET gRPC channel setup does not (cycle 12: PT-10 Step 15 first run failed, one-line fix in `GrpcTestHelpers`). + Source: _docs/06_metrics/perf_2026-06-26_cycle12.md ## Ring buffer (last 15 entries — newest at top) - [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).