mirror of
https://github.com/azaion/annotations.git
synced 2026-06-21 18:01:07 +00:00
03f879206e
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>
74 lines
9.1 KiB
Markdown
74 lines
9.1 KiB
Markdown
# 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 0–18: `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.
|