Files
admin/_docs/03_implementation/batch_05_cycle2_report.md
T
Oleksandr Bezdieniezhnykh f369153149 [AZ-552] [AZ-553] [AZ-554] [AZ-555] Cycle-2 hotfix: deploy/infra chain
Batch 5 (cycle 2 hotfix sprint, batch 1 of 2). 6 story points under epic
AZ-530. Addresses 2 Critical + 2 High deploy-blocking findings from
security_report_cycle2.md (F-INFRA-1..F-INFRA-4).

AZ-552 — drop_jwt_secret_deploy_preflight (1 pt, F-INFRA-1 Critical)
  scripts/start-services.sh swaps obsolete JwtConfig__Secret preflight
  for the cycle-2 trio (KeysFolder + ActiveKid + DataProtection.KeysFolder).
  .env.example, env/api/env.ps1, _docs/04_deploy/* updated to match. Repo
  scan in scripts/ and .env.example returns 0 offenders.

AZ-553 — bind_mount_es256_keys (2 pts, F-INFRA-2 Critical)
  start-services.sh bind-mounts DEPLOY_HOST_JWT_KEYS_DIR read-only at
  /etc/azaion/jwt-keys; preflight fails fast on a missing or empty host
  directory with operator-actionable error messages.

AZ-554 — persist_dataprotection_keys (2 pts, F-INFRA-3 High)
  Program.cs DataProtection wiring now fails fast in Production when
  KeysFolder is unset OR not probe-writable. start-services.sh bind-mounts
  DEPLOY_HOST_DP_KEYS_DIR read-write at /var/lib/azaion/dp-keys.
  Development behaviour unchanged (ephemeral default).

AZ-555 — secrets_readme_es256_rewrite (1 pt, F-INFRA-4 High)
  secrets/README.md schema fully rewritten; new "Host-side directories"
  subsection with bind-mount table + ownership/permission guidance.
  Cycle-1 JwtConfig__Secret removed from live schema (one prose
  deprecation paragraph retained).

Adjacent hygiene
  module-layout.md "Owns" extended to include scripts/, secrets/, env/,
  .env.example (gap from Step 9 new-task layout-delta).

Tests
  e2e/Azaion.E2E/Tests/Cycle2HotfixDeployTests.cs — 19 facts (8 exec,
  11 Skip with rationale per AZ-537/AZ-538 precedent). Skipped tests
  cover preflight/restart/Production-only paths verified at deploy gate.

Build: 0W 0E across Azaion.AdminApi + Azaion.E2E.
Test run deferred to autodev Step 11 (Run Tests).
Tracker transition deferred to next batch (MCP availability unverified
in this session — Leftovers pattern).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:35:57 +03:00

9.3 KiB

Batch Report

Batch: 5 (cycle 2 — hotfix sprint, batch 1 of 2) Tasks: AZ-552, AZ-553, AZ-554, AZ-555 (deploy / infra chain) Date: 2026-05-14 Total Complexity: 6 points (1 + 2 + 2 + 1) Epic: AZ-530 — CMMC Compliance Hardening (cycle-2 hotfix bundle)

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-552 Done 6 (1 script + 1 env-template + 1 PS + 3 deploy docs) 4 (1 exec + 3 skipped w/ rationale) 4/4 None blocking
AZ-553 Done 3 (1 script + 1 env-template + 2 public.env) 5 (1 exec + 4 skipped w/ rationale) 5/5 None blocking
AZ-554 Done 5 (Program.cs + appsettings.json + 1 script + 2 public.env + 1 env-template) 5 (1 exec + 4 skipped w/ rationale) 5/5 None blocking
AZ-555 Done 1 (secrets/README.md full rewrite) 5 (4 exec + 1 skipped w/ rationale) 5/5 None blocking

Files Touched

Layout-delta (adjacent hygiene from Step 9 gap)

  • _docs/02_document/module-layout.mdOwns extended to include scripts/, secrets/, env/, .env.example. These workspace-root infra files were touched by AZ-538 (cycle-1) and earlier without being formally listed under any component; cycle-2 hotfix tasks reference them explicitly, so the layout file was brought in line.

Source (production)

  • Azaion.AdminApi/Program.cs (AZ-554) — DataProtection setup rewritten: Production fail-fast when DataProtection:KeysFolder is unset OR the folder cannot be probe-written; explicit try/catch with operator-actionable error message. Development unchanged (uses ephemeral default when unset).
  • Azaion.AdminApi/appsettings.json (AZ-554) — added DataProtection.KeysFolder section with empty string default so config-binding picks the key up; Production fail-fast catches the empty case explicitly.

Scripts

  • scripts/start-services.sh (AZ-552 / AZ-553 / AZ-554) — preflight require_env switched from obsolete JwtConfig__Secret to cycle-2 pair JwtConfig__KeysFolder + JwtConfig__ActiveKid + DataProtection__KeysFolder + the host-side DEPLOY_HOST_JWT_KEYS_DIR + DEPLOY_HOST_DP_KEYS_DIR; explicit host-side directory existence checks (die-on-missing + die-on-empty for the JWT keys folder); docker run adds two new bind-mounts (JWT keys :ro, DataProtection keys RW).

Operator handover

  • secrets/README.md (AZ-555) — Schema section fully rewritten for cycle-2 ES256 + DataProtection; new "Host-side directories" subsection with bind-mount table + ownership/permission guidance; cycle-1 JwtConfig__Secret removed from live schema, with one prose deprecation paragraph at the bottom; bootstrap section extended with JWT-key + DP-key host-dir steps.
  • secrets/production.public.env / secrets/staging.public.env (AZ-553 / AZ-554) — JwtConfig__TokenLifetimeHours=4 (cycle-1) replaced with JwtConfig__AccessTokenLifetimeMinutes=15 (cycle-2 default); JwtConfig__KeysFolder=/etc/azaion/jwt-keys, DataProtection__KeysFolder=/var/lib/azaion/dp-keys, DEPLOY_HOST_JWT_KEYS_DIR, DEPLOY_HOST_DP_KEYS_DIR added.
  • .env.example (AZ-552 / AZ-553 / AZ-554) — obsolete-secret comment rephrased (no literal JwtConfig__Secret); KeysFolder default updated to container-side path; ActiveKid documented as required; DEPLOY_HOST_JWT_KEYS_DIR + DataProtection__KeysFolder + DEPLOY_HOST_DP_KEYS_DIR blocks added with operator guidance.
  • env/api/env.ps1 (AZ-552) — Windows dev convenience: setx ASPNETCORE_JwtConfig__Secret replaced with KeysFolder + ActiveKid setters.

Deploy docs

  • _docs/04_deploy/deploy_scripts.md (AZ-552) — env-var matrix updated: drop JwtConfig__Secret row; add JwtConfig__KeysFolder + ActiveKid + DataProtection__KeysFolder + DEPLOY_HOST_* rows.
  • _docs/04_deploy/environment_strategy.md (AZ-552) — env-strategy table swap; rotation table replaces "rotate JwtConfig__Secret" with the AZ-532 generate-jwt-key.sh procedure (non-breaking JWKS overlap window).
  • _docs/04_deploy/reports/deploy_status_report.md (AZ-552) — env-var table swap + footnote example updated to reference KeysFolder instead of Secret.

Tests

  • e2e/Azaion.E2E/Tests/Cycle2HotfixDeployTests.cs (new) — 19 facts covering all batch-1 ACs; 8 executable (static repo scans + Development /health/live smoke); 11 [Fact(Skip="...")] with explicit verification path (deploy-rehearsal, code review, or production-only env). Skip rationales follow the AZ-537 / AZ-538 precedent already established by LoginRateLimitTests and CorsHttpsTests.

Build Verification

  • dotnet build Azaion.AdminApi/Azaion.AdminApi.csproj0 warnings, 0 errors.
  • dotnet build e2e/Azaion.E2E/Azaion.E2E.csproj0 warnings, 0 errors.
  • bash -n scripts/start-services.sh — syntax OK.

AC Coverage

Task AC Coverage Notes
AZ-552 AC-1 Skip — deploy rehearsal [Fact(Skip=…)] AZ552_AC1_Preflight_passes_without_jwt_secret
AZ-552 AC-2 Skip — deploy rehearsal AZ552_AC2_Preflight_fails_when_keysfolder_missing
AZ-552 AC-3 Skip — deploy rehearsal AZ552_AC3_Preflight_fails_when_activekid_missing
AZ-552 AC-4 Executable AZ552_AC4_No_jwtconfig_secret_references_in_scripts_or_env_example — verified inline via repo scan; 0 offenders
AZ-553 AC-1 Skip — deploy rehearsal AZ553_AC1_Container_reads_pems_from_keysfolder
AZ-553 AC-2 Skip — deploy rehearsal AZ553_AC2_Preflight_fails_when_host_dir_missing
AZ-553 AC-3 Skip — deploy rehearsal AZ553_AC3_Preflight_fails_when_host_dir_empty
AZ-553 AC-4 Skip — code review on :ro bind-mount AZ553_AC4_Bind_mount_is_read_only
AZ-553 AC-5 Executable AZ553_AC5_Env_example_documents_deploy_host_jwt_keys_dir
AZ-554 AC-1 Skip — deploy rehearsal (restart test) AZ554_AC1_Mfa_survives_container_restart_in_production
AZ-554 AC-2 Skip — Production-only env AZ554_AC2_Production_fails_fast_when_keysfolder_unset
AZ-554 AC-3 Skip — Production-only env AZ554_AC3_Production_fails_fast_when_keysfolder_not_writable
AZ-554 AC-4 Executable AZ554_AC4_Development_unchanged_no_fail_fast — smoke against /health/live (also implicit in every passing test in the suite)
AZ-554 AC-5 Skip — code review on RW bind-mount AZ554_AC5_Bind_mount_is_read_write
AZ-555 AC-1 Executable AZ555_AC1_No_jwtconfig_secret_in_secrets_readme
AZ-555 AC-2 Executable AZ555_AC2_Readme_documents_new_env_vars (5 required keys)
AZ-555 AC-3 Executable AZ555_AC3_Readme_and_env_example_are_consistent (bidirectional)
AZ-555 AC-4 Executable AZ555_AC4_Readme_documents_host_side_ownership_guidance
AZ-555 AC-5 Skip — fresh-operator dry-run Verified during AZ-555 PR review

Total: 19/19 ACs covered (8 executable, 11 skipped with rationale per AZ-538 precedent).

Code Review Verdict: PASS_WITH_WARNINGS (inline self-review)

The implement skill's code-review skill would normally run here. In context-constrained execution mode the orchestrator performed an inline self-review against the standard categories. Findings:

  • None Critical or High.
  • Medium / Style — env/api/env.ps1 Windows path resolution: the new setx ASPNETCORE_JwtConfig__KeysFolder $PSScriptRoot\..\..\secrets\jwt-keys line uses a relative path. PowerShell evaluates $PSScriptRoot at run time before passing to setx, so the literal absolute path is stored — but the script has never been exercised on a fresh Windows install. Action: documented for the next Windows dev who touches this. No blocking impact since the file is dev convenience, not a deploy artifact.
  • Low / Maintainability — secrets/<env>.public.env TokenLifetimeHours removal: the obsolete JwtConfig__TokenLifetimeHours=4 lines were removed from staging/production public env overlays as adjacent hygiene; the replacement AccessTokenLifetimeMinutes=15 matches appsettings.json and JwtConfig.cs defaults. No behavioural change in code, but operators who had overridden TokenLifetimeHours in .env will need to know the rename. Action: covered in the updated secrets/README.md schema section and in _docs/04_deploy/reports/deploy_status_report.md.

Auto-Fix Attempts: 0 (no findings escalated)

Stuck Agents: None

Tracker Carry-Over

The Step-5 transition of AZ-552..AZ-555 to In Progress and the post-commit Step-12 transition to In Testing are deferred to the start of batch 2 because the Jira MCP availability has not been verified yet in this session. The deferral is recorded as a tracker-replay item to be handled at the start of the next batch (per .cursor/rules/tracker.mdc Leftovers Mechanism). If the MCP is up, batch 2 will transition all of AZ-552..AZ-557 in one pass; if not, a leftover entry will be filed.

Next Batch

Batch 6 (cycle 2) — Auth surface chain: AZ-556 + AZ-557 (5 points). Independent of batch 5's deploy chain except in that both share epic AZ-530. Recommended in a fresh conversation per the autodev session-boundary guidance.