# Batch Report **Batch**: 02 **Tasks**: AZ-457 (auth & token handling), AZ-459 (wire-contract enums), AZ-465 (i18n), AZ-481 (CI image labels) **Date**: 2026-05-11 **Cycle**: Phase A baseline, Step 6 — Implement Tests **Total complexity**: 12 pts (5 + 2 + 3 + 2) ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|---------------|-------|-------------|--------| | AZ-457_test_auth_token_handling | Done | 4 created (3 fast + 1 e2e) | 16 fast PASS, 4 e2e gated by suite stack | 4 / 4 ACs covered | 1 documented drift (FT-P-01 `it.fails()` until Step 4 fix) | | AZ-459_test_wire_contract_enums | Done | 2 created (1 fast + 1 e2e) | 11 fast (2 skipped per `verification_pending`), 1 e2e (drift-gated) | 4 / 4 ACs covered | 3 documented drifts (`AnnotationStatus`, `MediaStatus`, `Affiliation` — Step 4 .NET inspection pending) | | AZ-465_test_i18n | Done | 4 created (1 fast + 2 static helpers + 1 allowlist) | 4 fast (2 quarantined), 2 static checks PASS | 4 / 4 ACs covered | 2 quarantined (FT-P-24/25 — detector + persistence not yet implemented in production) | | AZ-481_test_ci_image_labels | Done | 1 created (static check), wired into runner | 6 static findings (5 PASS, 1 DRIFT for `image.title`) | 3 / 3 ACs covered | 1 documented drift (`org.opencontainers.image.title` missing — foundation/CI-CD follow-up) | ## AC Test Coverage: All covered ### AZ-457 — Auth & token handling (11 scenarios, 4 ACs) | Scenario | Where | Profile | Status (this run) | |----------|-------|---------|-------------------| | FT-P-01 (row 02) bootstrap refresh `credentials:'include'` | `src/auth/AuthContext.test.tsx` | fast | PASS via `it.fails()` (drift documented; flips when Step 4 fix lands) | | FT-P-02 (rows 03, 12) 401→refresh→retry | `src/api/client.test.ts` (fast) + `e2e/tests/auth.e2e.ts` (e2e) | fast + e2e | fast PASS; e2e gated by suite stack | | FT-P-03 (row 11) refresh transparency | `src/auth/AuthContext.test.tsx` | fast | PASS | | FT-N-04 (row 09) unauthenticated `/admin` → `/login` | `src/auth/ProtectedRoute.test.tsx` | fast | PASS | | NFT-SEC-01 bearer never in browser storage | `src/auth/AuthContext.test.tsx` (fast) + e2e companion | fast + e2e | fast PASS; e2e gated | | NFT-SEC-02 refresh cookie not in `document.cookie` | `src/auth/AuthContext.test.tsx` (fast) + e2e companion | fast + e2e | fast PASS; e2e gated | | NFT-SEC-03 refresh cookie `Secure; HttpOnly; SameSite=Strict` | `e2e/tests/auth.e2e.ts` | e2e only | gated | | NFT-SEC-04 `credentials:'include'` on every authed fetch | `src/api/client.test.ts` | fast | one assertion PASS, broader claim `it.fails()` (Step 4 quarantine) | | NFT-PERF-02 exactly one refresh per cycle | `src/api/client.test.ts` | fast | PASS | | NFT-RES-01 transparent recovery | `src/api/client.test.ts` | fast | PASS | | NFT-RES-08 expired refresh → `/login` | `src/api/client.test.ts` + `src/auth/ProtectedRoute.test.tsx` | fast | PASS | ### AZ-459 — Wire-contract enums (4 scenarios, 4 ACs) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | FT-P-04 (row 14) AnnotationStatus | `tests/wire_contract.test.ts` | fast | PASS via `it.fails()` (drift documented) | | FT-P-05 (rows 15-17) MediaStatus / Affiliation / CombatReadiness | `tests/wire_contract.test.ts` | fast | MediaStatus + Affiliation `it.fails()` (drift); CombatReadiness `it.skip` (`verification_pending`) | | FT-P-06 (rows 18, 19) detection wire payload | `tests/wire_contract.test.ts` (fast control) + `e2e/tests/wire_contract.e2e.ts` (`@drift`) | fast + e2e | fast PASS; e2e gated by `AZAION_RUN_DRIFT_E2E=1` | | FT-N-15 MediaType magic-literal hygiene | `scripts/run-tests.sh` (`STC-FN15`, static) + `tests/wire_contract.test.ts` (fast) | static + fast | static PASS; fast PASS for typed-shape, `it.skip` for value-set (`verification_pending`) | ### AZ-465 — i18n (4 scenarios, 4 ACs) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | FT-P-22 (row 45) en↔ua key parity | `scripts/check-i18n-coverage.mjs` via `STC-FP22` | static | PASS | | FT-P-23 (row 46) no raw user strings outside `t()` | `scripts/check-i18n-coverage.mjs --coverage-only` via `STC-FP23` + `tests/i18n-allowlist.json` | static | PASS (allow-list seeded with current pre-existing raw strings; CI gates growth) | | FT-P-24 (row 47) detector path on first boot | `tests/i18n.test.tsx` | fast + e2e | `it.skip` (QUARANTINE: detector not wired in `src/i18n/i18n.ts` today) + control test asserts the gap | | FT-P-25 (row 48) persistence across reload | `tests/i18n.test.tsx` | fast + e2e | `it.skip` (QUARANTINE: no persistence adapter today) + control test asserts the gap | ### AZ-481 — CI image labels (3 scenarios, 3 ACs) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | NFT-RES-LIM-11 tag scheme `${branch}-arm` | `scripts/check-ci-image-labels.mjs` via `STC-CI11` (parses `.woodpecker/build-arm.yml`) | static | PASS | | NFT-RES-LIM-12 OCI labels present | same | static | revision/created/source PASS; `org.opencontainers.image.title` reported DRIFT (lifting the drift is a follow-up CI hygiene task — not in scope here) | | NFT-RES-LIM-13 revision label = `$CI_COMMIT_SHA` | same | static | PASS | | (e2e portion against pushed image) | not run on host | requires-ci | gated; `requires-ci` per `_dependencies_table.md` | ## Code Review Verdict: PASS_WITH_WARNINGS Self-review (4-task batch, sequential per `.cursor/rules/no-subagents.mdc`). Phases 1–7 of `code-review/SKILL.md` walked inline: - **Phase 1 (Context)**: each task spec re-read; `_docs/02_document/module-layout.md` Blackbox Tests envelope respected; `_docs/00_problem/input_data/enum_spec_snapshot.json` is the contract pin for AZ-459; `.woodpecker/build-arm.yml` is the SUT for AZ-481. - **Phase 2 (Spec compliance)**: every AC across the four task specs has at least one test (running, `it.fails()`, or `it.skip` with quarantine reason). Drift handling (AZ-459 §AC-2 "Drift surfaces, not silently passes" + verification_pending markers) implemented uniformly via `it.fails()` for documented UI drift and `it.skip` for `verification_pending` enums; AZ-481's static analogue uses a `DRIFT` finding category to surface the missing `image.title` label without gating CI. - **Phase 3 (Code quality)**: helper functions `compareEnum`, `describeDrift`, `probeLabel`, `instrumentStorage` each carry one responsibility; no bare `catch`; meaningful messages; arrange/act/assert respected; tests do not import `` internals or `` internals (only `setToken` / `getToken` / `setNavigateToLogin` accessors per AZ-457 AC-2 and the autodev Step 4 testability accessors). - **Phase 4 (Security)**: no new secrets in test fixtures (re-uses AZ-456's placeholder argon2 hashes via the seed helpers); no use of `eval` / `shell=True`; static checks tightened to exclude `*.test.{ts,tsx,spec.ts,spec.tsx}` from production grep so test prose can mention forbidden tokens (e.g. `document.cookie`) without false positives. - **Phase 5 (Performance)**: fast suite ~3s wall-clock for 38 + 4-skipped tests (well under 5-min budget); static profile ~13s including `vite build` and `tsc --noEmit (test)`. - **Phase 6 (Cross-task consistency)**: the four tasks touch **disjoint** subsystems (auth vs enums vs i18n vs CI config); no contract collisions, no duplicate symbols. The shared `tests/helpers/{auth,navigate,render}.ts` (landed in batch 1) is the only cross-task surface and is consumed read-only. - **Phase 7 (Architecture compliance)**: - Test files only import the public accessors of `01_api-transport` (`api`, `setToken`, `getToken`) and `02_auth` (`ProtectedRoute` default export — the public boundary), plus `00_foundation/i18n` for AZ-465's reflective control test. No imports of `` internals, no imports of `_internal/` or `*.internal.*` files. Per the batch-1 finding (Low / Architecture / Interpretation), this is the same "test setup helper imports public accessors" pattern, now extended to the test bodies via the `tests/helpers/` indirection. - No new cyclic module dependencies introduced (test files are leaves in the import graph). ### Findings 1. **Low / Maintainability** — `tests/i18n-allowlist.json` was seeded with the **current** set of raw strings in `src/` so that `FT-P-23` (no raw user strings outside `t()`) passes today. This is per AZ-465 §Constraints ("Allow-list file lives at `tests/i18n-allowlist.json`; CI enforces it must not grow without a code-review reason") — the allow-list is a snapshot, not a permanent exemption. The static check enforces "must not grow without code review" because the JSON file is committed and any growth would be visible in PR diffs. Recommendation: a follow-up i18n-cleanup task (out of scope here) should drain the allow-list as keys are migrated to `t(...)`. 2. **Low / Style / Drift** — `scripts/check-ci-image-labels.mjs` introduces a `DRIFT` finding category (parallels AZ-459's `it.fails()`) for the missing `org.opencontainers.image.title` OCI label in `.woodpecker/build-arm.yml`. The script reports `DRIFT` to stdout but does not exit non-zero. Rationale: lifting the drift requires editing CI config (foundation/CI-CD ownership envelope), which is out of scope for a test-only batch. Recommendation: file a follow-up CI hygiene task to add the `--label org.opencontainers.image.title=azaion-ui` clause; once landed, the `DRIFT` flips to `PASS` with no test change required. 3. **Low / Architecture / Interpretation (carried over from batch 1)** — same issue as batch 1: tests rely on `tests/helpers/{render,auth,navigate}.ts` which import production accessors. Reaffirmed here that "Black-box discipline applies to test bodies, not to test setup helpers / composition-root wrappers". The batch-1 recommendation (clarify the layout rule) still stands. ## Auto-Fix Attempts: 0 ## Stuck Agents: None ## Files Changed (12) ### Created — `src/` (3) ``` src/api/client.test.ts # AZ-457 fast — 9 tests src/auth/AuthContext.test.tsx # AZ-457 fast — 4 tests src/auth/ProtectedRoute.test.tsx # AZ-457 fast — 3 tests ``` ### Created — `tests/` (3) ``` tests/wire_contract.test.ts # AZ-459 fast — 11 tests (2 skipped) tests/i18n.test.tsx # AZ-465 fast — 4 tests (2 skipped) tests/i18n-allowlist.json # AZ-465 raw-string allow-list (seed) ``` ### Created — `e2e/tests/` (2) ``` e2e/tests/auth.e2e.ts # AZ-457 e2e — 4 scenarios (gated by suite stack) e2e/tests/wire_contract.e2e.ts # AZ-459 e2e — drift-gated annotation save body ``` ### Created — `scripts/` (2) ``` scripts/check-i18n-coverage.mjs # AZ-465 STC-FP22 + STC-FP23 scripts/check-ci-image-labels.mjs # AZ-481 STC-CI11 ``` ### Modified (3) ``` scripts/run-tests.sh # +6 static checks (STC-FN15, STC-SEC4 refinement, STC-FP22, STC-FP23, STC-CI11) + src_grep test-file exclusion tsconfig.json # +exclude src/**/*.{test,spec}.{ts,tsx} from production tsc -b _docs/_autodev_state.md # batch 2 sub_step pointer ``` ## Verification Run (host) ``` $ bun run test:fast ✓ mission-planner/src/test/jsonImport.test.ts (6 tests) 7ms ✓ tests/wire_contract.test.ts (11 tests | 2 skipped) 9ms ✓ tests/infrastructure.test.ts (5 tests) 35ms ✓ tests/i18n.test.tsx (4 tests | 2 skipped) 3ms ✓ src/api/client.test.ts (9 tests) 58ms ✓ src/auth/ProtectedRoute.test.tsx (3 tests) 76ms ✓ src/auth/AuthContext.test.tsx (4 tests) 241ms Test Files 7 passed (7) Tests 38 passed | 4 skipped (42) $ ./scripts/run-tests.sh --static-only [run-tests] static profile PASSED — 19/19 checks (was 13 in batch 1; +6 from batch 2) $ ./scripts/run-tests.sh [run-tests] static profile : ran (PASS) [run-tests] fast profile : ran (PASS) [run-tests] e2e profile : skipped (host) [run-tests] exit code : 0 ``` E2E profile not exercised in this batch — same Risk 4 as batch 1 (requires `docker compose -f e2e/docker-compose.suite-e2e.yml up -d` plus parent-suite `:test` images). Per AZ-457 e2e companion, NFT-SEC-03 specifically requires Playwright's `context.cookies()` against the real `admin/` service. ## Next Batch Remaining: 22 test-implementation tasks in `_docs/02_tasks/todo/` (AZ-458, AZ-460..AZ-464, AZ-466..AZ-480, AZ-482). All carry **Component**: `Blackbox Tests` and **Dependencies**: `AZ-456` (✓ done) — soft cross-deps: - AZ-473 depends on AZ-472 (DetectionClasses fixtures) - AZ-458 (SSE bearer rotation) consumes the auth helpers landed by AZ-457 (✓ now done) - AZ-467 (ProtectedRoute spinner + RBAC) consumes the same helpers (✓) - AZ-468 (Header dropdown — uses authed page) consumes the same helpers (✓) Suggested next batch (4 tasks, ~10 pts): AZ-458 (SSE lifecycle, 5pts), AZ-467 (ProtectedRoute spinner + RBAC, 4pts), AZ-468 (Header flight dropdown, 2pts), and one small parallel — for example AZ-482 (secrets/banned-libs, 3pts) so the four tasks remain dependency-disjoint at the file level. Recommendation: continue in a new conversation. Batch 2 added 12 new files and 6 new static checks; the next batch will load distinct task specs and SSE / RBAC subsystems.