Files
annotations/_docs/00_problem/acceptance_criteria.md
T
Oleksandr Bezdieniezhnykh 03f879206e 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>
2026-05-14 20:19:05 +03:00

74 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.