docs+src: complete Steps 1-3 outcomes + auth re-sync baseline

This commit captures everything produced during autodev existing-code
Steps 1 (Document), 2 (Architecture Baseline Scan), and 3 (Test Spec),
together with the targeted auth + CORS re-sync triggered on 2026-05-14
when codebase drift was detected at Step 4 entry. None of this work was
previously committed.

Step 1 (Document) — 50+ _docs/02_document/ files: problem, solution,
architecture, system flows, glossary, module-layout, per-component
specs (01..06), modules, deployment, diagrams, data model, FINAL
report, verification log, discovery.

Step 2 (Architecture Baseline) — architecture_compliance_baseline.md.
Verdict PASS_WITH_WARNINGS (0 Critical, 0 High, 1 Medium, 2 Low). No
High/Critical findings; auto-chained to Step 3 per existing-code flow.

Step 3 (Test Spec) — _docs/02_document/tests/* (67 scenarios across
blackbox, security, resilience, resource-limit, performance), plus
e2e/docker-compose.test.yml, e2e/seed/run.sh, scripts/run-tests.sh,
scripts/run-performance-tests.sh. Coverage 88% over the active scope
(40 of 45 items covered, 6 RB-deferred, 5 documented-as-uncovered).

Targeted auth + CORS re-sync — replaces the deleted in-house token
issuer with a JWKS-verifier model. AuthController and TokenService
removed; JwtExtensions switched from HS256 symmetric to ES256 over
admin's JWKS. ConfigurationResolver and CorsConfigurationValidator
added under src/Infrastructure/. ADR-002 and ADR-006 retired; SEC-01,
SEC-02, SEC-03 marked Closed. One new testability risk recorded in
architecture.md Open Risks Section 6 (JWKS HTTPS gating).

Source changes:
- src/Auth/JwtExtensions.cs (modified) — ES256, JWKS, alg pinning
- src/Program.cs (modified) — DI wiring for ConfigurationResolver
  and CorsConfigurationValidator
- src/Controllers/AuthController.cs (deleted) — no in-service issuance
- src/Services/TokenService.cs (deleted) — same
- src/Infrastructure/ConfigurationResolver.cs (new)
- src/Infrastructure/CorsConfigurationValidator.cs (new)
- .env.example (new) — required env var documentation
- .gitignore (updated)

Cross-repo coordination: _docs/cross-repo/flights_h1_h2_h3_change_spec
captures the change-spec for downstream services that consumed the now
deleted /auth endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 20:19:05 +03:00
parent 08eadc1158
commit 03f879206e
66 changed files with 6006 additions and 133 deletions
+73
View File
@@ -0,0 +1,73 @@
# Azaion.Annotations — Acceptance criteria (retrospective)
> Every criterion has a measurable value and a code/config evidence pointer. **No automated test suite exists in the repo today** (`_docs/02_document/00_discovery.md`), so the criteria below are derived from validation rules, configuration limits, and explicit code branches — they are the contract a future test suite (autodev existing-code Step 3 + Step 6) must encode. Criteria that depend on a Refactor Backlog item landing first are flagged with `[after RB-XX]`.
## Functional — annotation lifecycle (component `01 annotations-rest`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-01 | `POST /annotations` with `image_bytes + detections` for the same payload returns the same `id` on every call. | `id` is `XxHash3.Hash128` (32 hex chars) of the sampled `image_bytes` window — byte-stable. | `Services/AnnotationService.cs` `GenerateAnnotationId(...)` (post RB-04 — currently `XxHash64`). |
| AC-F-02 | A repeat `POST /annotations` for an existing id is a no-op write (no duplicate row, no duplicate file). | DB reads return existing row before insert; file write is `WriteAllBytesAsync` overwriting same bytes. | `Services/AnnotationService.cs`. |
| AC-F-03 | `POST /annotations` writes a YOLO-format label file at `images_dir/<id>.txt` containing one line per detection: `<class_id> <cx> <cy> <w> <h>`. | Exact format with space-separated floats, normalised 0..1, line-per-detection. | `Services/AnnotationService.cs` (label-file write site). |
| AC-F-04 | `POST /annotations` returns HTTP 200 with the persisted entity (id, status, detections). | Response shape mirrors `AnnotationDto`. | `Controllers/AnnotationsController.cs`. |
| AC-F-05 | `[after RB-01]` Every successful `POST/PUT/PATCH/DELETE /annotations/*` emits exactly one SSE event AND inserts exactly one `annotations_queue_records` row, with the correct `QueueOperation` enum. | Created=10, Updated=20, Deleted=40. | `_docs/02_document/architecture.md` ADR-009; `Services/QueueOperation.cs`. |
| AC-F-06 | `[after RB-01]` `DELETE /annotations/{id}` flips the row to `Status=Deleted (40)`, relocates `images_dir/<id>.{jpg,txt}` to `deleted_dir/`, and emits the `Deleted` lifecycle event. | Row count unchanged; files moved; status transitions per `AnnotationStatus`. | `_docs/02_document/architecture.md` ADR-009 + glossary "Soft-delete". |
| AC-F-07 | `[after RB-01 + RB-08]` Soft-deleted rows do not appear in `GET /annotations` or `GET /dataset` results. | Filter `WHERE Status <> 40` enforced at every read path. | RB-01, RB-08 in `_docs/02_document/architecture.md`. |
| AC-F-08 | `[after RB-02]` There is no `silent_detection` column, field, DTO property, or branch in code. | Schema diff + grep produces zero matches. | RB-02. |
## Functional — realtime + sync (component `02 annotations-realtime-sync`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-10 | A connected SSE client receives the lifecycle event for a successful `POST /annotations` within 1 second of the response. | <1s P99 in single-instance, single-pod local run. | `Services/AnnotationEventService.cs`. |
| AC-F-11 | A subscriber that joins **after** the event has been published does not receive it (channel is fire-and-forget). | No backfill replay in `Channel<>`. | ADR-001. |
| AC-F-12 | `FailsafeProducer` consumes a row from `annotations_queue_records` and publishes a MessagePack-gzip frame to the `azaion-annotations` stream within the configured drain interval. | Drain loop interval is the configured cadence; row deletion happens after stream confirm. | `Services/FailsafeProducer.cs`. |
| AC-F-13 | `[after RB-09]` Every wire message carries `(annotation_id, operation, date_time)` so a downstream consumer can dedupe re-deliveries. | Three fields present on the wire schema. | `_docs/02_document/architecture.md` ADR-013. |
## Functional — media (component `03 media`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-20 | `POST /media` accepts a multipart upload, persists the file to the configured directory, and returns the persisted `MediaDto`. | HTTP 200 + JSON body. | `Controllers/MediaController.cs`, `Services/MediaService.cs`. |
| AC-F-21 | `POST /media/batch` accepts N files in one request, writes N rows + N files, and returns N persisted DTOs. | N inputs → N outputs, atomic per file. | same. |
## Functional — dataset (component `04 dataset`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-30 | `GET /dataset` honors filter parameters (mission id, status, class). | Returned rows match filter conditions. | `Controllers/DatasetController.cs`, `Services/DatasetService.cs`. |
| AC-F-31 | `POST /dataset/status/bulk` flips status on N rows in a single SQL statement. | One UPDATE WHERE id IN (…). | `Services/DatasetService.cs`. |
## Functional — settings & metadata (component `05 settings-metadata`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-40 | `PUT /settings/directories` persists changes and triggers `pathResolver.Reset()` so subsequent path lookups reflect the new values. | Verified — `Services/SettingsService.cs:71, 85`. | `Services/SettingsService.cs`. |
| AC-F-41 | `GET /classes` returns the 19 seeded detection classes (ids 018: `ArmorVehicle, Truck, Vehicle, Artillery, Shadow, Trenches, MilitaryMan, TyreTracks, AdditionArmoredTank, Smoke, Plane, Moto, CamouflageNet, CamouflageBranches, Roof, Building, Caponier, Ammo, Protect.Struct`). | 19 rows; ids stable from `DatabaseMigrator`. | `Database/DatabaseMigrator.cs:101-121`. |
| AC-F-42 | `[after RB-06]` `[ADM]` write endpoints exist for `/classes`; the in-memory cache invalidates on write via `Reset()`. | Cache hit ratio observable; cache miss on each write. | RB-06. |
## Functional — auth & platform (component `06 platform`)
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-F-50 | A request bearing an ES256 access token issued by admin (`iss = JWT_ISSUER`, `aud = JWT_AUDIENCE`, signature verifies against the JWKS at `JWT_JWKS_URL`, `exp` in the future) reaches the controller. Tokens that fail issuer / audience / signature / lifetime validation, or whose `alg` is not `ES256`, return HTTP 401. | `JwtBearerHandler` defaults + `AddJwtAuth` parameters. | `Auth/JwtExtensions.cs`. |
| AC-F-51 | Annotations does not host any token-issuance or token-refresh endpoint. Long-running callers refresh against admin's `POST /token/refresh` and pass the resulting access token to annotations. | No `[AllowAnonymous]` route except `/health`; `AuthController` removed. | `Program.cs`, suite admin docs. |
| AC-F-52 | Endpoints under policy `ANN` reject callers without that role with HTTP 403. Endpoints under `DATASET` reject non-DATASET callers with HTTP 403. Endpoints under `ADM` reject non-ADM with HTTP 403. | `Authorization` middleware. | `Auth/JwtExtensions.cs`. |
| AC-F-53 | All errors are returned in the `{ error: { code, message, …details } }` envelope. | Single envelope shape across all controllers. | `Middleware/ErrorHandlingMiddleware.cs`, `_docs/02_document/common-helpers/01_http-error-envelope.md`. |
| AC-F-54 | `GET /health` returns HTTP 200 within 5 seconds of process start (Dockerfile `HEALTHCHECK`). | 200 OK on `/health`. | `Dockerfile`, `Program.cs`. |
## Non-functional
| ID | Criterion | Measurable value | Evidence |
|----|-----------|------------------|----------|
| AC-N-01 | Container boot to `/health` 200 ≤ Docker `HEALTHCHECK` interval/timeout configured in the suite-level orchestrator. | Per `Dockerfile` HEALTHCHECK directive (consult orchestrator config for actual values). | `Dockerfile`. |
| AC-N-02 | `DatabaseMigrator.MigrateAsync()` is idempotent — second boot against the same DB makes no schema changes. | `IF NOT EXISTS` / `ON CONFLICT DO NOTHING` everywhere. | `Database/DatabaseMigrator.cs`. |
| AC-N-03 | `FailsafeProducer` keeps `annotations_queue_records` depth bounded under steady-state lifecycle traffic. | Queue depth metric (to be exposed during Step 14 Observability work). | `Services/FailsafeProducer.cs`. |
| AC-N-04 | The service emits zero unhandled exceptions to clients — every uncaught exception is mapped via `ErrorHandlingMiddleware` into the error envelope. | Middleware terminal handler. | `Middleware/ErrorHandlingMiddleware.cs`. |
| AC-N-05 | Single SSE connection survives ≥ 30 minutes idle with bounded memory (channel is unbounded; growth must come from real traffic, not heartbeats). | Heap stable across 30-minute idle window. | `Services/AnnotationEventService.cs`. |
## Gaps acknowledged
- No measurable latency / throughput targets (P50, P95, P99) are stated anywhere in code. Need to be set during Step 15 (Performance Test).
- No security audit findings yet (Step 14). Items like JWT issuer validation, CORS tightening, and Swagger gating are planned, not yet acceptance criteria.
- No backup / RPO / RTO contract for `images_dir` and `deleted_dir` — the storage layer is treated as durable by assumption.