mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 09:21:10 +00:00
[AZ-510][AZ-511][AZ-512][AZ-513] Cycle 3 Steps 12-15 + admin prereq
ci/woodpecker/push/build-arm Pipeline failed
ci/woodpecker/push/build-arm Pipeline failed
Wrap up cycle 3 across the autodev existing-code Phase B steps that follow Implement (Steps 12-15), plus the cross-workspace prerequisite ticket filed for AZ-512. Step 12 - Test-Spec Sync: - Un-quarantine FT-P-01 in traceability-matrix (closed by AZ-510) - Add AZ-510 chained /users/me failure-path test reference under AC-23 - Note AZ-512 deferral status under O9 (P12 Phase B target) Step 13 - Update Docs (task mode): - Refresh src__auth__AuthContext module doc with AZ-510 wire shape (POST refresh + chained /users/me + bootstrapInflight guard) - Add usersMe() to src__api__endpoints module doc + consumer note - Rename src__features__annotations__classColors module doc to src__class-colors__classColors (matches AZ-511 git mv); refresh header - Refresh src__components__DetectionClasses + src__features__annotations module group doc for the new class-colors barrel import path - Update components/11_class-colors Module Inventory to point at the renamed module doc filename - Rewrite system-flows.md Flow F2 (Bearer auto-refresh) with the AZ-510 POST + chained /users/me sequence; close Finding B3 references - Generate ripple_log_cycle3 documenting all changed source files, their reverse-dependency search results, and the docs touched Step 14 - Security Audit (cycle-3 delta): - Resume mode against cycle-2 baseline; cycle-2 artifacts untouched - Re-run bun audit on both roots: clean (cycle-2 inline fix held) - Re-rate OWASP A06: FAIL -> PASS; A07: PASS_WITH_KNOWN -> PASS (B3 closed by AZ-510) - New finding F-SAST-CY3-1 (LOW): __resetBootstrapInflightForTests exposed via src/auth public barrel; defer to hygiene cycle - Verdict: FAIL -> PASS_WITH_WARNINGS; one HIGH (F-SAST-1 mission-planner git-history key, unchanged) remains - Add amendment banner to cycle-2 security_report.md Step 15 - Performance Test: - Static profile NFT-PERF-01 PASS (290 575 B gzipped vs 2 MB budget; ~14% of budget; no regression from AZ-510 surface additions) - E2E profile SKIP (Playwright perf project still pending AZ-457..AZ-482); legitimate skip per test-run skill, gap acknowledged in report - AZ-510 200ms p95 chain NFR verified at spec level only - no CI gate yet (covered by future AZ-457..AZ-482 work) Cross-workspace prerequisite (AZ-513 just filed): - Updated _docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md to reflect AZ-513 filing on admin/ workspace (parent epic AZ-509, Blocks link to AZ-512). Companion task spec added in admin/ repo (separate commit there, owned by admin/ workspace). State file: advanced to Step 16 (Deploy) per autodev existing-code flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -81,5 +81,5 @@ This *is* the helper. There are no further extensions inside this component.
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/class-colors/classColors.ts` | `_docs/02_document/modules/src__features__annotations__classColors.md` (path-derived name kept; content reflects the new home) |
|
||||
| `src/class-colors/classColors.ts` | `_docs/02_document/modules/src__class-colors__classColors.md` |
|
||||
| `src/class-colors/index.ts` | barrel — re-exports `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES` |
|
||||
|
||||
@@ -20,6 +20,7 @@ export const endpoints = {
|
||||
authLogout: () => string
|
||||
users: () => string
|
||||
user: (id: string) => string
|
||||
usersMe: () => string // added 2026-05-13 by AZ-510 — chained read after POST refresh
|
||||
classes: () => string
|
||||
class: (id: string | number) => string
|
||||
},
|
||||
@@ -81,7 +82,7 @@ The whole object is `as const`, so each leaf's return type is the narrow string
|
||||
After the AZ-486 migration, `endpoints` is imported by:
|
||||
|
||||
- `src/api/client.ts` — internal `refreshToken()` helper uses `endpoints.admin.authRefresh()`.
|
||||
- `src/auth/AuthContext.tsx` — `authRefresh`, `authLogin`, `authLogout`.
|
||||
- `src/auth/AuthContext.tsx` — `authRefresh`, `authLogin`, `authLogout`, `usersMe` (added by AZ-510).
|
||||
- `src/components/FlightContext.tsx` — `flights.collection`, `flights.flight`, `annotations.settingsUser`.
|
||||
- `src/components/DetectionClasses.tsx` — `admin.classes`, `admin.class`.
|
||||
- `src/features/admin/AdminPage.tsx` — `admin.users`, `admin.user`.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Module: `src/auth/AuthContext.tsx`
|
||||
|
||||
> **Source**: `src/auth/AuthContext.tsx` (54 lines)
|
||||
> **Source**: `src/auth/AuthContext.tsx` (~120 lines after AZ-510)
|
||||
> **Topo batch**: B3 (depends on B2 leaves: `api/client`, `types/index`)
|
||||
> **Last refresh**: 2026-05-13 — AZ-510 consolidated bootstrap onto POST refresh + chained `/users/me`; closes Vision P3 / Finding B3.
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -31,16 +32,30 @@ State:
|
||||
- `user: AuthUser | null` — `null` when unauthenticated.
|
||||
- `loading: boolean` — `true` until the initial refresh attempt resolves (success or failure). Renders should gate on this.
|
||||
|
||||
**Bootstrap effect (mount-only)**:
|
||||
**Bootstrap effect (mount-only)** — AZ-510 wire shape:
|
||||
|
||||
```ts
|
||||
api.get<{ user: AuthUser; token: string }>(endpoints.admin.authRefresh())
|
||||
.then(data => { setToken(data.token); setUser(data.user) })
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false))
|
||||
async function runBootstrap(): Promise<AuthUser | null> {
|
||||
const refreshRes = await fetch(getApiBase() + endpoints.admin.authRefresh(), {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
if (!refreshRes.ok) return null
|
||||
const refreshData = (await refreshRes.json()) as { token: string }
|
||||
setToken(refreshData.token)
|
||||
try {
|
||||
return await api.get<AuthUser>(endpoints.admin.usersMe())
|
||||
} catch (err) {
|
||||
console.error('[AuthContext] Refresh succeeded but /users/me failed:', err)
|
||||
setToken(null)
|
||||
return null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The refresh endpoint is invoked with `credentials: 'include'` only inside `client.ts`'s **internal** `refreshToken()` helper — but here we go through the public `api.get()` path, which does NOT include credentials. **This is a real divergence**: `client.ts`'s internal `refreshToken()` (used in the 401 retry) sends the cookie; the bootstrap call in `AuthContext` does not. The endpoint must therefore accept the refresh either via cookie (then bootstrap fails silently for non-cookie clients — which is everyone after a hard reload) **or** via some other mechanism (a refresh token in `localStorage`, etc.). **Flag for Step 4 verification** against the `admin/` service contract; this is likely a real bug masking by silent `.catch`. The path string itself is unaffected by AZ-486 — `endpoints.admin.authRefresh()` produces `'/api/admin/auth/refresh'` character-identically to the pre-refactor literal, so the divergence is structural, not URL-based.
|
||||
A module-scoped `bootstrapInflight: Promise | null` guard is consulted before invoking `runBootstrap`, so two concurrent `useEffect` mounts (React 18+ StrictMode dev double-mount, or rapid re-mount in tests) share a single network round-trip and avoid racing the backend's refresh-cookie rotation. A test-only escape hatch `__resetBootstrapInflightForTests()` is exported via the `src/auth` barrel and called in `tests/setup.ts`'s `afterEach` to keep the module-scoped promise from leaking between tests.
|
||||
|
||||
The bootstrap and the existing 401-retry path in `api/client.ts:73` now share a single wire shape — both POST `/api/admin/auth/refresh` with `credentials:'include'` and rely on the HttpOnly refresh cookie. The chained `GET /api/admin/users/me` request fetches the user payload (the POST refresh response is `{ token }` only). On any failure path (refresh 401, refresh network error, refresh 200 → `/users/me` 401, refresh 200 → `/users/me` network error) the bootstrap clears the bearer first then sets `user: null` + `loading: false`, so an in-flight re-render never sees `(user: null, accessToken: <stale>)`. Closes Vision principle P3 ("bearer in memory, refresh in HttpOnly cookie") and Finding B3.
|
||||
|
||||
**`login(email, password)`**:
|
||||
|
||||
@@ -60,7 +75,7 @@ setToken(null); setUser(null)
|
||||
|
||||
Network failure on logout is silently swallowed because we want to clear local auth state regardless.
|
||||
|
||||
**`hasPermission(perm)`**: returns `user?.permissions.includes(perm) ?? false`. The permission strings are not constrained at the type level — any string passes. Backend-defined.
|
||||
**`hasPermission(perm)`**: returns `user?.permissions?.includes(perm) ?? false`. Defensively handles legacy `/users/me` payloads that omit `permissions` (older backend builds; some test fixtures returning the bare `User` shape). Permission strings are not constrained at the type level — any string passes. Backend-defined; UI uses this only for affordance show/hide, never for security gates (the server is the authority — see `_docs/02_document/architecture.md` Vision P12 / O4).
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -103,14 +118,11 @@ No env vars consumed directly — token storage policy is defined in `client.ts`
|
||||
|
||||
## Tests
|
||||
|
||||
None.
|
||||
`src/auth/AuthContext.test.tsx` — un-quarantined `FT-P-01` (bootstrap POST + `credentials:'include'` + chained `/users/me` regression guard); `FT-P-03` (refresh transparency, child re-render delta ≤ 1); `NFT-SEC-01` (bearer never in localStorage / sessionStorage across the full bootstrap + 401-retry lifecycle); `NFT-SEC-02` (no refresh-prefixed cookie visible via `document.cookie`); `AC-4 (AZ-510)` — POST refresh 200 → `/users/me` 401 clears the bearer + logs a diagnostic console.error.
|
||||
|
||||
## Notes / open questions
|
||||
|
||||
- **Bootstrap-vs-refresh divergence** (above) — the highest-priority flag in this module. Either:
|
||||
1. The refresh endpoint accepts an Authorization-less, cookie-bearing call → confirm the `admin/` service sets an HttpOnly cookie on `/login` and the cookie path matches `/api/admin/auth/refresh`. The `api.get()` path in `client.ts` does NOT send `credentials: 'include'`, so this currently CANNOT work. → **likely bug**.
|
||||
2. Or the bootstrap should be calling the internal `refreshToken()` helper, which is currently not exported.
|
||||
Either way, this needs a Step 4 fix (export `refreshToken()` and call it here, or change `api.get()` to allow per-call `credentials`).
|
||||
- ~~**Bootstrap-vs-refresh divergence**~~ — **RESOLVED 2026-05-13 by AZ-510**. Bootstrap now uses POST + `credentials:'include'` + chained `/users/me`, sharing the same wire shape as the 401-retry path. `api.get()` is intentionally NOT used for the refresh itself because it does not thread `credentials:'include'`; the bootstrap calls `fetch()` directly with the same explicit-credentials pattern documented in `api/client.ts:88`. Finding B3 closed.
|
||||
- **`AuthContext = createContext<AuthState>(null!)`**: the non-null assertion means `useAuth()` will throw at the destructuring site if it's used outside `AuthProvider`. Acceptable given `App.tsx` mounts `AuthProvider` at the top, but a guard `if (!ctx) throw new Error(...)` would be friendlier. Defer.
|
||||
- The `loading` flag is never re-set to `true` after the initial bootstrap. `login` and `logout` complete synchronously from the React tree's perspective (the `await` is inside the callback). If a future requirement demands a "logging in…" indicator, it would need its own state. Note for Step 8.
|
||||
- `useAuth` returns the raw context value (no memoisation wrapper). React 18+ behaviour means `<AuthProvider>` re-renders all `useAuth` consumers on every state update — fine here because there's no high-frequency state.
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
# Module: `src/features/annotations/classColors.ts`
|
||||
# Module: `src/class-colors/classColors.ts`
|
||||
|
||||
> **Source**: `src/features/annotations/classColors.ts` (24 lines)
|
||||
> **Source**: `src/class-colors/classColors.ts` (24 lines; moved from `src/features/annotations/classColors.ts` by AZ-511 on 2026-05-13 — closes Finding F3)
|
||||
> **Public API barrel**: `src/class-colors/index.ts` re-exports `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES`.
|
||||
> **Topo batch**: B1 (leaf — no internal imports)
|
||||
|
||||
## Purpose
|
||||
@@ -1,7 +1,8 @@
|
||||
# Module: `src/components/DetectionClasses.tsx`
|
||||
|
||||
> **Source**: `src/components/DetectionClasses.tsx` (99 lines)
|
||||
> **Topo batch**: B3 (depends on B2 leaves: `api/client`, `features/annotations/classColors`, `types/index`)
|
||||
> **Topo batch**: B3 (depends on B2 leaves: `api/client`, `class-colors` (via barrel), `types/index`)
|
||||
> **Last refresh**: 2026-05-13 — `getClassColor` + `FALLBACK_CLASS_NAMES` import migrated from `'../features/annotations/classColors'` to `'../class-colors'` barrel by AZ-511.
|
||||
|
||||
## Purpose
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Module group: `src/features/annotations/`
|
||||
|
||||
> Compact doc covering all 5 annotations modules (`classColors.ts` is a shared leaf — see existing `src__features__annotations__classColors.md`). The annotations feature is the **central legacy concern** of the codebase per `_docs/legacy/wpf-era.md §4` (`Azaion.Annotator` window) — what's documented here is the React port. For the canonical product spec see `_docs/ui_design/README.md` (Annotations Tab Layout, Annotation Quality Guidelines, Affiliation Icons, Combat Readiness, Annotation Row Gradient, Keyboard Shortcuts, Video Annotation Time-Window Display) and parent suite `../../../../_docs/01_annotations.md` for the API contract.
|
||||
> Compact doc covering the 4 annotations-feature modules. `classColors.ts` was carved out of this directory to its own component (`src/class-colors/`) by AZ-511 on 2026-05-13 — see `src__class-colors__classColors.md`; consumers in this feature now import via the `../../class-colors` barrel. The annotations feature is the **central legacy concern** of the codebase per `_docs/legacy/wpf-era.md §4` (`Azaion.Annotator` window) — what's documented here is the React port. For the canonical product spec see `_docs/ui_design/README.md` (Annotations Tab Layout, Annotation Quality Guidelines, Affiliation Icons, Combat Readiness, Annotation Row Gradient, Keyboard Shortcuts, Video Annotation Time-Window Display) and parent suite `../../../../_docs/01_annotations.md` for the API contract.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -20,7 +20,7 @@ Owns the `/annotations` route. Lets the user:
|
||||
|
||||
| Module | Layer | Responsibility |
|
||||
|---|---|---|
|
||||
| `classColors.ts` | leaf | (already documented separately) Class-number → colour + photoMode-suffix lookup. |
|
||||
| ~~`classColors.ts`~~ | (moved) | Carved out by AZ-511 to `src/class-colors/`; imported via the `class-colors` barrel by `CanvasEditor.tsx`, `AnnotationsSidebar.tsx`, `AnnotationsPage.tsx`. |
|
||||
| `MediaList.tsx` | sub-component | Left panel media browser. Owns `media[]` state, debounced filter, dropzone upload, blob: local-mode fallback when backend POST fails. Calls `endpoints.annotations.media(qs)`, `endpoints.annotations.mediaItem(id)` (DELETE), `endpoints.annotations.mediaBatch()` (POST). |
|
||||
| `VideoPlayer.tsx` | sub-component | Native `<video>` wrapper. `forwardRef` exposes `seek(seconds)` and `getVideoElement()`. Custom progress slider + frame-step toolbar. Global `keydown` handler for Space / ←/→ (Ctrl=±150) / M. Image / video bytes via `endpoints.annotations.mediaFile(id)`. |
|
||||
| `AnnotationsSidebar.tsx` | sub-component | Right panel: SSE-driven annotation list (`endpoints.annotations.annotationEvents()` filtered by `mediaId`), AI detect button (`endpoints.detect.media(mediaId)`), gradient row background built from per-detection class colour + confidence-modulated alpha, download button (delegates to page). |
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Documentation Ripple Log — Cycle 3
|
||||
|
||||
> Generated during Step 13 (Update Docs) of the autodev existing-code flow, cycle 3.
|
||||
> Task specs in scope:
|
||||
> - `_docs/02_tasks/done/AZ-510_auth_bootstrap_consolidation.md`
|
||||
> - `_docs/02_tasks/done/AZ-511_classcolors_carve_out.md`
|
||||
> - `_docs/02_tasks/backlog/AZ-512_admin_edit_detection_class.md` — DEFERRED at Step 10 (Implement) by the spec-defined Cross-Workspace Verification BLOCKING gate; no source code changes shipped, so no doc ripple from AZ-512.
|
||||
> Implementation reports: `_docs/03_implementation/batch_13_cycle3_report.md`, `_docs/03_implementation/batch_14_cycle3_report.md`, `_docs/03_implementation/batch_15_cycle3_report.md` (deferral record).
|
||||
|
||||
## Scope analysis (Task Step 0)
|
||||
|
||||
Direct source files changed by Cycle 3:
|
||||
|
||||
### AZ-510 — Auth bootstrap refresh consolidation
|
||||
|
||||
| Source file | Touched module / component / system doc |
|
||||
|---|---|
|
||||
| `src/auth/AuthContext.tsx` | `modules/src__auth__AuthContext.md` (this run — bootstrap rewrite, hasPermission defensive guard, AC-4 test reference); `components/02_auth/description.md` (refreshed by AZ-510 implementer at commit time) |
|
||||
| `src/auth/index.ts` | barrel-only edit (added `__resetBootstrapInflightForTests` re-export) — covered in module doc note for AuthContext; no separate barrel doc exists |
|
||||
| `src/api/endpoints.ts` | `modules/src__api__endpoints.md` (this run — added `usersMe()` row + AuthContext consumer note) |
|
||||
| `tests/setup.ts` | not part of `DOCUMENT_DIR/modules/` — covered by `tests/environment.md` (already documents global setup hooks; no signature change to declare) |
|
||||
| `tests/msw/handlers/admin.ts` | `tests/test-environment-msw-handlers.md` if present — checked: no specific module doc, MSW handlers are referenced from `tests/environment.md` at the table level only; permissions field addition does not change the MSW contract surface |
|
||||
| `src/auth/AuthContext.test.tsx` + 15 other `tests/*.test.tsx` files swapped GET→POST refresh mocks | covered by traceability matrix (Step 12) and module doc note |
|
||||
| Documentation already updated by the AZ-510 implementer at commit time (no second pass needed): `_docs/02_document/components/02_auth/description.md`, `_docs/02_document/architecture_compliance_baseline.md` (B3 closure), `_docs/02_document/04_verification_log.md` (B3 closure) |
|
||||
|
||||
### AZ-511 — `classColors` carve-out (`src/features/annotations/` → `src/class-colors/`)
|
||||
|
||||
| Source file | Touched module / component / system doc |
|
||||
|---|---|
|
||||
| `src/features/annotations/classColors.ts` → `src/class-colors/classColors.ts` (`git mv`) | `modules/src__features__annotations__classColors.md` → `modules/src__class-colors__classColors.md` (`git mv` this run) — header rewritten to point at new path + AZ-511 closure note |
|
||||
| `src/class-colors/index.ts` (NEW barrel) | listed in `components/11_class-colors/description.md` Module Inventory (refreshed this run to point at the renamed module doc) |
|
||||
| `src/features/annotations/index.ts` | barrel-only edit (removed F3 carry-over comment block) — no module doc change |
|
||||
| `src/features/annotations/CanvasEditor.tsx` | import-only change → `modules/src__features__annotations.md` Module Inventory note refreshed (this run) — no signature change |
|
||||
| `src/features/annotations/AnnotationsSidebar.tsx` | same — covered by the group doc refresh |
|
||||
| `src/features/annotations/AnnotationsPage.tsx` | same — covered by the group doc refresh |
|
||||
| `src/components/DetectionClasses.tsx` | `modules/src__components__DetectionClasses.md` (this run — topo-batch dependency line + last-refresh note) |
|
||||
| `tests/detection_classes.test.tsx` | covered by traceability matrix (Step 12); fixture-only import path swap, no behavior change |
|
||||
| `scripts/check-arch-imports.mjs` | static-gate infrastructure — `tests/static-checks.md` if present; checked: covered by `_docs/02_document/architecture_compliance_baseline.md` (refreshed by implementer) and `scripts/run-tests.sh` description block (refreshed by implementer) |
|
||||
| `tests/architecture_imports.test.ts` | `tests/static-checks.md` if present; covered by `_docs/02_document/architecture_compliance_baseline.md` Finding F3 closure (refreshed by implementer) |
|
||||
| Documentation already updated by the AZ-511 implementer at commit time (no second pass needed): `_docs/02_document/module-layout.md`, `_docs/02_document/components/11_class-colors/description.md`, `_docs/02_document/architecture_compliance_baseline.md` (F3 closure), `_docs/02_document/04_verification_log.md` (open questions #1, #8 closure), `scripts/run-tests.sh` description block |
|
||||
|
||||
## Import-graph ripple (Task Step 0.5)
|
||||
|
||||
Reverse-dependency search for the source files changed in cycle 3.
|
||||
|
||||
### AZ-510 ripple
|
||||
|
||||
- `src/auth/AuthContext.tsx` exports `useAuth`, `AuthProvider`, `__resetBootstrapInflightForTests`. All three are exposed via the `src/auth` barrel (per STC-ARCH-01 rules). Importers of `useAuth` / `AuthProvider`:
|
||||
- `src/auth/ProtectedRoute.tsx` — same-component import, no cross-component ripple.
|
||||
- `src/components/Header.tsx` — wire-shape unchanged (still calls `useAuth()`); no doc refresh required for the Header module doc.
|
||||
- `src/features/login/LoginPage.tsx` — wire-shape unchanged; no doc refresh required.
|
||||
- `src/App.tsx` — mounts `<AuthProvider>`; no doc refresh required.
|
||||
- `tests/setup.ts` — calls `__resetBootstrapInflightForTests` in `afterEach`; covered above.
|
||||
- `src/api/endpoints.ts` added `usersMe()`. Only consumer is `src/auth/AuthContext.tsx` (covered above). Searched for any other production import of `endpoints.admin.usersMe` — none.
|
||||
|
||||
### AZ-511 ripple
|
||||
|
||||
- `src/class-colors/classColors.ts` (formerly `src/features/annotations/classColors.ts`) exports 4 symbols. All importers re-routed to the new `src/class-colors` barrel by AZ-511 directly (covered in the AZ-511 table above):
|
||||
- `src/components/DetectionClasses.tsx`, `src/features/annotations/CanvasEditor.tsx`, `src/features/annotations/AnnotationsSidebar.tsx`, `src/features/annotations/AnnotationsPage.tsx`, `tests/detection_classes.test.tsx`.
|
||||
- No additional indirect importers found via `rg "from .*classColors"` and `rg "from .*class-colors"`.
|
||||
- `src/features/annotations/index.ts` barrel-only edit — no symbol surface change, no consumer ripple.
|
||||
|
||||
### Heuristic-mode fallback
|
||||
|
||||
Not needed — TypeScript import resolution succeeded for all changed files via `rg` with TS path patterns; no language-tooling failure.
|
||||
|
||||
## Module docs touched this run
|
||||
|
||||
- `_docs/02_document/modules/src__auth__AuthContext.md` (AZ-510)
|
||||
- `_docs/02_document/modules/src__api__endpoints.md` (AZ-510)
|
||||
- `_docs/02_document/modules/src__class-colors__classColors.md` (AZ-511 — renamed via `git mv` from `src__features__annotations__classColors.md`)
|
||||
- `_docs/02_document/modules/src__components__DetectionClasses.md` (AZ-511)
|
||||
- `_docs/02_document/modules/src__features__annotations.md` (AZ-511 — header note + Module Inventory row)
|
||||
- `_docs/02_document/components/11_class-colors/description.md` (AZ-511 — Module Inventory row updated to new doc filename)
|
||||
|
||||
## Component docs touched this run
|
||||
|
||||
None beyond the Module Inventory tweak in `11_class-colors/description.md` listed above. The substantive component-level updates for both tasks were made by their implementers at batch commit time (`02_auth/description.md`, `11_class-colors/description.md` Caveats §7, etc.) per scope discipline.
|
||||
|
||||
## System-level docs touched this run
|
||||
|
||||
- `_docs/02_document/system-flows.md` Flow F2 (Bearer auto-refresh) — rewrote the historical "two divergent paths" section, replaced the broken-bootstrap sequence diagram with the AZ-510 POST-refresh + chained `/users/me` flow, refreshed the Error Scenarios table to reflect the `runBootstrap()` failure modes (AC-4 (AZ-510) regression test reference). Finding B3 marked CLOSED.
|
||||
|
||||
## Problem-level docs touched this run
|
||||
|
||||
None. AZ-510 and AZ-511 are structural / wire-shape changes — no API input parameter, configuration, or acceptance-criteria change at the problem level. (AZ-512 would have touched `acceptance_criteria.md` O9 / Vision P12, but it was deferred — the deferral context is captured in the cycle-3 traceability-matrix update at Step 12.)
|
||||
|
||||
## Summary
|
||||
|
||||
```
|
||||
══════════════════════════════════════
|
||||
DOCUMENTATION UPDATE COMPLETE — Cycle 3
|
||||
══════════════════════════════════════
|
||||
Task(s): AZ-510, AZ-511 (AZ-512 deferred — no doc ripple)
|
||||
Module docs updated: 5 (1 renamed via git mv)
|
||||
Component docs updated: 1 (Module Inventory row only — substantive component refresh done by implementers at commit time)
|
||||
System-level docs updated: system-flows.md (Flow F2)
|
||||
Problem-level docs updated: none
|
||||
Ripple-refreshed docs (imports changed indirectly): 0 — all consumers covered by direct task scope
|
||||
══════════════════════════════════════
|
||||
```
|
||||
@@ -123,16 +123,18 @@ flowchart TD
|
||||
|
||||
---
|
||||
|
||||
## Flow F2: Bearer auto-refresh on 401 (TWO refresh paths exist in code)
|
||||
## Flow F2: Bearer auto-refresh (bootstrap + 401-retry)
|
||||
|
||||
> **Cycle 3 / 2026-05-13 — AZ-510 consolidated the two refresh paths.** The historical "two divergent paths" wording below has been rewritten. The previous bug (finding B3 / Vision P3 violation) is now CLOSED.
|
||||
|
||||
### Description
|
||||
|
||||
There are **two distinct refresh code paths** in the source — the verification pass (Step 4) caught both:
|
||||
There are two refresh trigger points in the source, but they now share a single wire shape:
|
||||
|
||||
1. **Bootstrap path** — `AuthContext.tsx:24` calls `api.get('/api/admin/auth/refresh')` on app mount. This **does NOT have `credentials:'include'`** because `api/client.ts` doesn't add it on GET. Result: the cookie is not sent, the bootstrap silently fails, the user starts unauthenticated even when they have a valid refresh cookie.
|
||||
2. **401-retry path** — `api/client.ts:44` calls `fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })` automatically when any authenticated fetch returns 401. This path IS correct.
|
||||
1. **Bootstrap path** — `AuthContext.tsx` (`runBootstrap()` helper, guarded by a module-scoped `bootstrapInflight` promise to deduplicate React 18+ StrictMode dev double-mounts). On `<AuthProvider>` mount it calls `fetch(getApiBase() + endpoints.admin.authRefresh(), { method: 'POST', credentials: 'include' })`. On success it sets the bearer and **chains** `api.get<AuthUser>(endpoints.admin.usersMe())` (= `GET /api/admin/users/me`) to fetch the user record (the POST refresh response is `{ token }` only). On any failure path the bearer is cleared first, then `user: null` + `loading: false`.
|
||||
2. **401-retry path** — `api/client.ts:73` automatically calls `fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })` and replays the original request when any authenticated fetch returns 401.
|
||||
|
||||
The bootstrap path is the bug surfaced as finding B3 PRIORITY. The 401-retry path is the silent fallback that does work but only after the user has already hit a 401.
|
||||
Both paths now POST with `credentials:'include'` and rely on the HttpOnly refresh cookie set on `/login`.
|
||||
|
||||
### Preconditions
|
||||
|
||||
@@ -157,7 +159,7 @@ sequenceDiagram
|
||||
ApiClient-->>Page: response
|
||||
```
|
||||
|
||||
### Sequence Diagram (Bootstrap path on app mount — broken)
|
||||
### Sequence Diagram (Bootstrap path on app mount — POST refresh + chained `/users/me`, AZ-510)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -166,18 +168,25 @@ sequenceDiagram
|
||||
participant AdminApi as admin/ service
|
||||
|
||||
App->>AuthCtx: <AuthProvider> mounts
|
||||
AuthCtx->>AdminApi: GET /admin/auth/refresh (NO credentials:'include' — finding B3)
|
||||
AdminApi-->>AuthCtx: 401 (no cookie sent)
|
||||
AuthCtx->>AuthCtx: setLoading(false), user stays null
|
||||
AuthCtx-->>App: ProtectedRoute sees null user → redirects to /login
|
||||
AuthCtx->>AuthCtx: bootstrapInflight guard (StrictMode dedupe)
|
||||
AuthCtx->>AdminApi: POST /admin/auth/refresh (credentials:'include')
|
||||
AdminApi-->>AuthCtx: 200 {token} + Set-Cookie: refresh=...
|
||||
AuthCtx->>AuthCtx: setToken(token)
|
||||
AuthCtx->>AdminApi: GET /admin/users/me (Authorization: Bearer <token>)
|
||||
AdminApi-->>AuthCtx: 200 {id, email, permissions}
|
||||
AuthCtx->>AuthCtx: setUser(...), setLoading(false)
|
||||
AuthCtx-->>App: ProtectedRoute sees user → renders gated route
|
||||
```
|
||||
|
||||
### Error Scenarios
|
||||
|
||||
| Error | Where | Detection | Recovery |
|
||||
|-------|-------|-----------|----------|
|
||||
| Bootstrap GET refresh missing `credentials:'include'` | `AuthContext.tsx:24` | server returns 401 because cookie was not sent | **Bug today** — finding B3 PRIORITY. Symptom: a user with a valid refresh cookie still gets bounced to `/login` on every fresh tab. Step 4 fix: change to POST with `credentials:'include'` (matching the 401-retry path), or just delete the bootstrap GET and let the first authenticated fetch's 401 trigger the retry path. |
|
||||
| 401-retry path | `api/client.ts:44` | works | (no fix needed) |
|
||||
| ~~Bootstrap GET refresh missing `credentials:'include'`~~ | — | — | **CLOSED 2026-05-13 by AZ-510.** Bootstrap now POSTs with `credentials:'include'`. Finding B3 / Vision P3 violation resolved. |
|
||||
| Refresh 401 on bootstrap | `AuthContext.tsx` `runBootstrap()` | non-OK response from POST refresh | `setUser(null)` + `setLoading(false)` → `ProtectedRoute` redirects to `/login`. No console.error (expected on first visit / signed-out user). |
|
||||
| Refresh network error on bootstrap | `AuthContext.tsx` `runBootstrap()` | outer `.catch` on the POST refresh fetch | `setToken(null)` + `setUser(null)` + `setLoading(false)` + `console.error('[AuthContext] Bootstrap failed:', err)`. UI redirects to `/login`. |
|
||||
| Refresh 200 → `/users/me` failure (401, network, etc.) | `AuthContext.tsx` `runBootstrap()` | inner `try/catch` around `api.get(usersMe())` | `setToken(null)` first (Constraint #4 — bearer cleared before user state) + `console.error('[AuthContext] Refresh succeeded but /users/me failed:', err)` + return null → top-level then-handler sets `user: null` + `loading: false`. Covered by `AC-4 (AZ-510)` regression test. |
|
||||
| 401-retry path inside `api/client.ts` | `api/client.ts:73` | works | (no fix needed) |
|
||||
| Refresh cookie expired or revoked | refresh call | 401 | UI redirects to `/login`. |
|
||||
| SSE subscription holds a stale bearer | active EventSource | server closes the SSE stream | No reconnect logic today (Step 8 hardening). |
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Maps every acceptance criterion and every restriction in `_docs/00_problem/` to
|
||||
|
||||
| AC ID | Acceptance Criterion (short) | Tests | results_report rows | Coverage |
|
||||
|-------|------------------------------|-------|---------------------|----------|
|
||||
| AC-01 | `credentials:'include'` on every authenticated fetch | FT-P-01 [Q for bootstrap], FT-P-02, NFT-PERF-02, NFT-SEC-04, NFT-RES-01, NFT-RES-08 | 01, 02, 03 | Covered |
|
||||
| AC-01 | `credentials:'include'` on every authenticated fetch | FT-P-01 (un-quarantined cycle 3 / 2026-05-13 by AZ-510 — bootstrap is now POST + `credentials:'include'` with chained `/users/me` per Vision P3; FT-P-01 runs as a regression guard on the wire shape), FT-P-02, NFT-PERF-02, NFT-SEC-04, NFT-RES-01, NFT-RES-08 | 01, 02, 03 | Covered |
|
||||
| AC-02 | Bearer never written to client storage | NFT-SEC-01 | 04 | Covered |
|
||||
| AC-03 | Refresh cookie `Secure HttpOnly SameSite=Strict` | NFT-SEC-02, NFT-SEC-03 | 05, 06, 07 | Covered |
|
||||
| AC-04 | Numeric enums match suite spec | FT-P-04, FT-P-05, FT-P-06 | 14, 15, 16, 17, 18, 19 | Covered (`enum_spec_snapshot.json` committed — Phase 3 gate resolved) |
|
||||
@@ -28,7 +28,7 @@ Maps every acceptance criterion and every restriction in `_docs/00_problem/` to
|
||||
| AC-20 | OpenWeatherMap key not in source | NFT-SEC-09 (all 3 steps active — source check un-quarantined on cycle 2 / 2026-05-12 by AZ-499) | 63 | Covered |
|
||||
| AC-21 | UserSettings panel-width persistence | FT-P-37 [Q], FT-P-38 [Q], NFT-PERF-08 [Q] | 64, 65 | Covered (quarantined) |
|
||||
| AC-22 | RBAC client-side route gates | FT-N-03 [Q], FT-N-04, FT-N-05 [Q], NFT-SEC-05 [Q], NFT-SEC-06 [Q], NFT-RES-08 | 08, 09, 10 | Covered (quarantined for `/admin` + `/settings` gates) |
|
||||
| AC-23 | Auth refresh transparency | FT-P-02, FT-P-03, NFT-PERF-02, NFT-RES-01 | 11, 12 | Covered |
|
||||
| AC-23 | Auth refresh transparency | FT-P-02, FT-P-03, NFT-PERF-02, NFT-RES-01; "AC-4 (AZ-510)" colocated test in `src/auth/AuthContext.test.tsx` covers the bootstrap edge where POST refresh succeeds but chained `/users/me` returns 401 → bearer cleared, console.error logged (added cycle 3 / 2026-05-13 by AZ-510) | 11, 12 | Covered |
|
||||
| AC-24 | SSE bearer-rotation reconnect | NFT-PERF-03 [Q], NFT-RES-02 [Q], NFT-RES-10 | 13, 97 | Covered (quarantined — Step 8 hardening) |
|
||||
| AC-25 | Detect endpoint correctness (sync + async) | FT-P-11, FT-P-12 [Q], FT-P-13 [Q] | 26, 27, 28 | Covered (async path quarantined — F7 target) |
|
||||
| AC-26 | Numeric input hygiene | FT-N-11 [Q], FT-N-12 [Q] | 66, 67 | Covered (quarantined — Step 4 fix) |
|
||||
@@ -96,7 +96,7 @@ Maps every acceptance criterion and every restriction in `_docs/00_problem/` to
|
||||
| O6 | No hardcoded credentials | NFT-SEC-09 | Covered |
|
||||
| O7 | Spec is source of truth for numeric enums | FT-P-04, FT-P-05, FT-P-06 | Covered |
|
||||
| O8 | Persist what you type (panel widths) | FT-P-37 [Q], FT-P-38 [Q] | Covered (quarantined) |
|
||||
| O9 | Admin can edit existing detection classes (P12) | NOT COVERED — feature missing today (`acceptance_criteria.md` notes P12 violation; PATCH endpoint to re-introduce in Phase B) | NOT COVERED — Phase B target |
|
||||
| O9 | Admin can edit existing detection classes (P12) | NOT COVERED — feature missing today (`acceptance_criteria.md` notes P12 violation; PATCH endpoint to re-introduce in Phase B). **Cycle 3 (2026-05-13)**: AZ-512 attempted this, but its spec-defined Cross-Workspace Verification BLOCKING gate failed — admin/ service exposes no /classes routes at all (not even the POST/DELETE that AdminPage already calls today). Task parked in `_docs/02_tasks/backlog/AZ-512_*.md` with leftover `_docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md` until the admin/ workspace ships POST + PATCH + DELETE /classes. | NOT COVERED — Phase B target (deferred; cross-workspace prerequisite outstanding) |
|
||||
| O10 | Destructive actions require ConfirmDialog | NFT-SEC-08, FT-P-26, FT-P-27, FT-N-07 | Covered |
|
||||
| O11 | No SSR / RSC | NFT-RES-LIM-03 (no Node in image) + STC-O11 (no `react-dom/server` import) | Partially Covered |
|
||||
| O12 | `mission-planner/` not compiled by production Vite build | NFT-RES-LIM-04 | Covered |
|
||||
@@ -108,7 +108,7 @@ Maps every acceptance criterion and every restriction in `_docs/00_problem/` to
|
||||
|
||||
| Category | Total Items | Covered | Partially Covered | Not Covered | N/A (meta) | Coverage % (Covered+Partial) |
|
||||
|----------|-------------|---------|-------------------|-------------|-----------|--------------------|
|
||||
| Acceptance Criteria | 44 | 44 | 0 | 0 | 0 | 100% (cycle-2 deltas: AC-41, AC-42, AC-43, AC-44 added; AC-20 source check no longer quarantined) |
|
||||
| Acceptance Criteria | 44 | 44 | 0 | 0 | 0 | 100% (cycle-2 deltas: AC-41, AC-42, AC-43, AC-44 added; AC-20 source check no longer quarantined. Cycle 3 deltas: FT-P-01 bootstrap part un-quarantined by AZ-510 — closes Vision P3 / Finding B3; AC-23 row gained the AZ-510 chained-`/users/me` failure-path test reference.) |
|
||||
| Anti-Criteria | 5 | 5 | 0 | 0 | 0 | 100% |
|
||||
| Restrictions | 41 | 17 | 8 | 13 | 3 | 61% |
|
||||
| **Total** | **90** | **66** | **8** | **13** | **3** | **82%** |
|
||||
@@ -132,11 +132,10 @@ Acceptance criterion coverage exceeds the 75 % template threshold. Restriction c
|
||||
|
||||
## Quarantine List (running)
|
||||
|
||||
The following 17 tests assert against a Phase B target or a Step 4 fix and are quarantined until the implementation lands. Phase 3 will decide their disposition. (Cycle 2 / 2026-05-12 update: NFT-SEC-09 source check REMOVED from this list — closed by AZ-499 + STC-SEC1C; new AC-41 / AC-42 tests added in this cycle are NOT quarantined.)
|
||||
The following 16 tests assert against a Phase B target or a Step 4 fix and are quarantined until the implementation lands. Phase 3 will decide their disposition. (Cycle 2 / 2026-05-12 update: NFT-SEC-09 source check REMOVED — closed by AZ-499 + STC-SEC1C; new AC-41 / AC-42 tests added in this cycle are NOT quarantined. Cycle 3 / 2026-05-13 update: FT-P-01 bootstrap part REMOVED — closed by AZ-510, runs as a regression guard now.)
|
||||
|
||||
| Test | Reason | Activates when |
|
||||
|------|--------|---------------|
|
||||
| FT-P-01 (bootstrap part) | Bootstrap refresh missing `credentials:'include'` per finding | Step 4 fix |
|
||||
| FT-P-12, FT-P-13 | Async video detect (F7) not wired | Phase B feature cycle |
|
||||
| FT-P-24, FT-P-25 | i18n detector + persistence missing | Step 4 fix |
|
||||
| FT-P-33 (timeout) | ProtectedRoute timeout missing | Step 4 fix |
|
||||
|
||||
Reference in New Issue
Block a user