QA Report — PR #1432
Generated: 2026-05-09 04:58 UTC
PR: #1432 — fix(console): deploy-pipeline backend — run_id backfill, audit viewer, heroku_app
Issues closed: #1409 · #1410 · #1411 (backend half only)
Mode: thorough
Verdict: qa:partial-pending-deploy — code-level ACs all pass; live-prod re-verification required after merge + Heroku release.
Auth identity: raxx-ops-bot (token-only static checks; no live mutation).
Summary
PR #1432 lands three independent backend fixes. All 21 new tests pass. All 96 existing deploy-related tests pass clean (no regressions). Static review confirms the ACs are correctly addressed at the code level. Because the bug fixes operate against a live state machine that only reaches the broken path on a real GitHub Actions dispatch + callback round-trip, the runtime ACs (#1409 run_id backfill, #1411 heroku_app in API response) cannot be verified until the PR merges and raxx-console-prod ships the new release. Post-deploy re-run procedure documented below.
| AC | Source | Code-level | Live-prod | Status |
|---|---|---|---|---|
#1409 — apply_callback back-fills run_id when ASYNC flag OFF |
tests + diff | PASS | NOT YET DEPLOYED | partial-pending-deploy |
#1410 — GET /console/audit returns 200 with paginated table |
tests + diff | PASS | NOT YET DEPLOYED | partial-pending-deploy |
| #1410 — RBAC ops + superadmin only | tests | PASS | n/a | pass |
#1410 — flag-gated 404 when FLAG_CONSOLE_DEPLOY_UI off |
tests | PASS | n/a | pass |
#1410 — own access audited as console.audit.viewed |
tests | PASS | n/a | pass |
| #1410 — pagination + action_type filter + date range | tests | PASS | n/a | pass |
#1411 (backend) — heroku_app in _deploy_to_dict() |
tests + diff | PASS | NOT YET DEPLOYED | partial-pending-deploy |
| #1411 (backend) — null for CF Pages surfaces | tests | PASS | n/a | pass |
Pre-conditions verified
- Current
raxx-console-prodrelease: v74 (Set FLAG_CONSOLE_HEROKU_LOG_DRAIN_ALERTING config vars); last code release v71 (Deploy 85d2d178) — pre-PR. FLAG_CONSOLE_DEPLOY_UI=trueonraxx-console-prod(audit viewer route will be live immediately on next release).FLAG_CONSOLE_DEPLOY_AUDIT_INGEST=on,FLAG_CONSOLE_DEPLOY_RESUME=1,FLAG_CONSOLE_DEPLOY_VIEW_LOGS=1— all related deploy-flow flags are on.- Bug #1409 confirmed live in prod: the 5 most recent
console_deploysrows are alltimed_outwithgithub_run_id=None;failure_reason=run_id_not_found_within_window. Matches the original issue exactly.
Per-AC verification
AC #1409 — run_id back-fill from callback regardless of flag
Code review (PASS):
The fix in console/app/services/deploys.py:864-882 removes the wrong _async_flag_enabled() and ... guard. New guard is the single correct invariant: if deploy.github_run_id is None. The diff comment also explains why the original guard was incorrect (sync poll can miss the run due to per_page or GH latency; callback is the reliable fallback in both modes). This is a textbook root-cause fix — surgical, narrow, well-explained.
Test coverage (PASS):
console/tests/test_deploy_run_id_backfill_1409.py — 5 tests, all green:
- test_backfill_when_async_flag_off_and_run_id_null — the regression case
- test_no_overwrite_when_async_flag_off_and_run_id_already_set — invariant preserved
- test_callback_without_run_id_leaves_null_when_flag_off — defensive
- test_backfill_when_async_flag_on_and_run_id_null — pre-existing behaviour preserved
- test_no_overwrite_when_async_flag_on_and_run_id_already_set
Live re-verification (PENDING): see "Re-run procedure" below.
AC #1410 — GET /console/audit operator-facing audit log viewer
Code review (PASS):
console/app/blueprints/audit_viewer.py(new, 253 lines) — clean blueprint with proper RBAC decorator (@require_role("ops", "superadmin")), flag gate (FLAG_CONSOLE_DEPLOY_UI), HTML escaping at the row-serialisation layer, parameterized SQL filters via SQLAlchemy, LIKE escape for action_type prefix matches.console/app/templates/audit/index.html(new, 205 lines) — extendsbase.html, filter form with action_type + from/to UTC date pickers, paginated table.console/app/templates/security/index.html:16— fixes hard-coded/audithref tourl_for('audit_viewer.audit_index'). Important touch.console/app/__init__.py:90,115—audit_viewer_bpregistered.- Action name
console.audit.viewedfollows the establishedconsole.<feature>.<action>pattern (verified against existing names in the codebase:console.deploy.intent,console.flag.flip, etc.).
Test coverage (PASS):
console/tests/test_audit_viewer_1410.py — 13 tests, all green:
- RBAC: superadmin/ops pass, unauthenticated/readonly redirected
- Flag-gate: flag off returns 404
- Rendering: rows appear in HTML, empty-state shown when no rows
- Filtering: action_type narrows correctly
- Error handling: bad date param shows error banner (not 500)
- Audit-of-audit: access writes a console.audit.viewed row
- Pagination: total page count reflected
- Page structure: filter form + title rendered
Live re-verification (PENDING): with FLAG_CONSOLE_DEPLOY_UI=true already on, the route should return 200 immediately upon release.
AC #1411 (backend) — heroku_app in deploy GET response
Code review (PASS):
console/app/services/deploys.py:72-82—SURFACE_HEROKU_APP_MAPdeclared with the 4 Heroku surfaces (api-prod,api-staging,console-prod,console-staging). CF Pages surfaces (getraxx,raxx-mockups) intentionally absent — they have no Heroku rollback.console/app/blueprints/deploys.py:285,299—_deploy_to_dict()now derivesheroku_appserver-side from the registry. Docstring explicitly tells clients to not derive from sessionStorage.
Test coverage (PASS):
3 tests in test_deploy_run_id_backfill_1409.py:
- test_heroku_app_present_for_api_prod_surface — heroku_app=raxx-api-prod
- test_heroku_app_present_for_console_prod_surface — heroku_app=raxx-console-prod
- test_heroku_app_none_for_cf_pages_surface — heroku_app=None for getraxx (CF Pages)
Live re-verification (PENDING): see procedure below.
Out of scope — JS-side _deploy_modal.html cleanup
The full #1411 issue describes a JS sessionStorage corruption bug that lives in console/app/templates/_deploy_modal.html:1411-1538. This PR delivers only the backend half (the heroku_app field that the JS layer should consume). The JS-side fix is on the in-flight PR #1433 / DONE-state polisher. This is correctly noted in the PR title ("backend") and the PR body. Not a blocker for #1432 merge.
Test execution log
$ cd console && PYTHONPATH=. python3 -m pytest tests/test_deploy_run_id_backfill_1409.py tests/test_audit_viewer_1410.py -v
============================ 21 passed in 1.60s ============================
$ python3 -m pytest tests/test_deploy_hardening_904_905.py \
tests/test_deploys_callback_735.py \
tests/test_deploys_736.py
============================ 96 passed in 12.42s ============================
Re-run procedure (post-merge)
Once PR #1432 is merged to main and a new deploy-console.yml run targets production:
#1409 (run_id back-fill)
- Trigger a console-driven deploy via the in-console deploy modal — pick any non-prod surface or a low-risk prod redeploy.
- Within ~5 seconds of HMAC callback (the workflow's first heartbeat), query:
GET /api/internal/deploys?limit=1 - Confirm row's
github_run_idis non-null andgithub_run_urlmatches. - If still null, check Heroku log drain for
apply_callbackexecution traces — Sentry should also surface any exception.
#1410 (audit viewer)
- Authenticate to console as
kris@moosequest.net(superadmin) via CF Access. - Hit
https://console.raxx.app/console/audit— expect 200 with table render. - Apply
action_type=console.deployfilter — table narrows to deploy events. - Apply a stale
from/toUTC range that matches no rows — empty-state row renders. - Apply garbage to
from(e.g.from=garbage) — error banner renders, no 500. - Verify a
console.audit.viewedrow was just written by visiting the page (visible by reloading and seeing the new row).
#1411 (heroku_app)
- Find the most recent successful deploy id from
/api/internal/deploys?limit=5. GET /api/internal/deploys/<deploy_id>- Confirm
heroku_appisraxx-api-prodforapi-prod,raxx-console-prodforconsole-prod, etc. - Find a
getraxxdeploy id and confirmheroku_app=null.
Risk assessment
- Rollback path is clean. The
apply_callbackchange is guard removal only — additive behaviour, no data destroyed if rolled back. The audit viewer is gated byFLAG_CONSOLE_DEPLOY_UI; flipping that flag off is an instant rollback for #1410 without redeploy. - No db migrations — no schema risk.
- No client-facing copy — no broker/vendor naming concerns.
- All times in code use UTC (
datetime.now(timezone.utc)throughout). - No secrets in commits.
Recommendation
MERGE-READY, gated on:
- (1) author re-verifies the three live-prod ACs above after the next deploy-console.yml run
- (2) re-running this report's verification steps and updating the labels from qa:partial-pending-deploy to qa:passed
No blockers found. No new issues filed.