[AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs

Captures the full output of autodev existing-code Phase A through
Step 4 (Code Testability Revision) for the Azaion UI workspace:

- Step 1 Document: _docs/02_document/ (FINAL_report, architecture,
  glossary, components/, modules/, diagrams/, system-flows,
  module-layout) plus _docs/00_problem/ + _docs/01_solution/ +
  _docs/legacy/ + _docs/how_to_test + README.
- Step 2 Architecture Baseline: architecture_compliance_baseline.md.
- Step 3 Test Spec: _docs/02_document/tests/ (environment,
  test-data, blackbox/performance/resilience/security/
  resource-limit tests, traceability-matrix), enum_spec_snapshot,
  expected_results/results_report.md (98 rows), plus the
  run-tests.sh + run-performance-tests.sh runners.
- Step 4 Code Testability Revision: 01-testability-refactoring/
  run dir (list-of-changes C01-C07, deferred_to_refactor,
  analysis/research_findings + refactoring_roadmap) and the 7
  child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/
  plus _dependencies_table.md.
- _docs/_autodev_state.md pins the cursor at Step 4 / refactor
  Phase 4 entry so /autodev resumes cleanly.

Epic AZ-447 (UI testability gates) tracks the 7 child tasks that
will land in subsequent commits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:38:49 +03:00
parent da0a5aa187
commit 510df68bcf
84 changed files with 13065 additions and 0 deletions
@@ -0,0 +1,73 @@
# Module: `src/api/sse.ts`
> **Source**: `src/api/sse.ts` (25 lines)
> **Topo batch**: B3 (depends on B2 leaf: `api/client` for `getToken()` only)
## Purpose
A 25-line wrapper around the browser's native `EventSource` that (a) appends the current bearer token as an `access_token` query parameter (since `EventSource` does not let callers set headers), (b) parses each `MessageEvent` payload as JSON, and (c) returns a cleanup function. Used by features that listen for server-pushed updates (annotations queue, flight ingestion progress, etc.).
## Public interface
```ts
export function createSSE<T>(
url: string,
onMessage: (data: T) => void,
onError?: (err: Event) => void,
): () => void
```
The returned function closes the underlying `EventSource`. Callers MUST call it on unmount to avoid leaking long-lived connections.
## Internal logic
1. `const token = getToken()` — read the current bearer from `api/client.ts`.
2. Build `fullUrl`:
- If `token` is non-null: append `access_token=<token>` using `&` if the URL already has a query string, `?` otherwise.
- If `token` is null: use `url` as-is.
3. `new EventSource(fullUrl)`.
4. `source.onmessage = (e) => { try { onMessage(JSON.parse(e.data) as T) } catch { /* ignore */ } }`.
- JSON parse errors are silently swallowed. The contract is that the server sends valid JSON; a malformed frame degrades to "skipped".
5. `source.onerror = (e) => onError?.(e)` — forwarded straight through. The browser auto-reconnects by default; `onError` lets callers observe the disconnect.
6. Return `() => source.close()`.
## Dependencies
- **Internal**: `./client``getToken()`.
- **External**: `EventSource` (browser global).
## Consumers (intra-repo)
From the §7a dependency graph:
- `src/features/annotations/AnnotationsSidebar.tsx` — subscribes to the annotations stream.
- `src/features/flights/FlightsPage.tsx` — subscribes to flight ingestion / state updates.
## Data models
None defined here. The generic `T` is supplied by the caller.
## Configuration
URLs are passed in by callers (string-literal at call sites). The same testability remark as `api/client.ts` applies: a `VITE_API_BASE_URL` is the natural Step 4 fix.
## External integrations
Whichever backend exposes the SSE endpoint at the URL the caller provides. Per `nginx.conf`, the suite's `/api/*` reverse proxy forwards SSE traffic by default (no special EventSource-blocking config) — verify in Step 4.
## Security
- **Bearer in query string**: `access_token=<jwt>` ends up in browser-history URLs, server access logs, and proxy logs. This is a **known weakness of the EventSource API** — the API has no headers parameter, so cookie or query are the only options. The trade-off was made knowingly (SSE is a long-lived GET; the bearer is short-lived; nginx access logs are an internal-only artefact). Document in `security_approach.md` (Step 6) and consider rotating to a dedicated SSE-only short-lived token in Step 8.
- **`getToken()` on connect, no refresh**: if the bearer rotates mid-session (via `client.ts`'s 401 retry path), the `EventSource` keeps using the old token and will eventually error. Callers must observe `onError` and reconnect. The current consumers (`AnnotationsSidebar`, `FlightsPage`) do NOT do this — they create the source once on mount. Flag for Step 4 / Step 8.
- **Silent JSON parse error**: a single malformed frame is skipped without a `console.warn`. Acceptable for production noise reduction but obscures real backend bugs in dev. Defer.
## Tests
None.
## Notes / open questions
- **No reconnect / backoff logic** — relies on the browser's built-in EventSource auto-reconnect. The default is "keep retrying"; on a flaky connection this may produce a tight loop with backend logs. Confirm acceptable in Step 6 against the `annotations/` and `flights/` service rate-limit posture.
- **Cleanup on token-less call**: `getToken()` returning `null` produces an unauthenticated `EventSource`. The backend should reject with `401`, the `EventSource` then errors, and `onError?` fires. The caller is expected to interpret this as "user logged out, stop subscribing". None of the current consumers do that explicitly; instead, the `ProtectedRoute` gate prevents `useEffect` from ever running while logged out, so the path is unreachable in practice. Document but no action needed.
- The function is fully synchronous — does not return a `Promise`. The connection is initiated on call but the first message may arrive later. Consumers must handle the "no messages yet" UI state.
- The query-string token assembly does NOT URL-encode the token. JWTs are URL-safe Base64 by default and contain only `AZ, az, 09, -, _, .`, so this is safe for the current token shape. If the token format ever changes, add `encodeURIComponent`. Note for Step 8.