Third commit on the critique-fix arc. Three independent improvements
bundled because they are small, all backed by tests, and naturally
land together (RBAC affordances on the three remaining buttons + the
matching server-side error-sanitisation hardening + the deploy
script's permission-stability preflight).
Start / Stop / Delete cluster buttons in the AKS cluster pulse
are now disabled with the documented "you need X" tooltip for
users without Contributor (or equivalent) on the cluster scope
(critique #6 / continuation).
Run BLAST button in both submit surfaces (mobile footer +
desktop summary rail) is disabled with the tooltip for users
without Storage Blob Data Contributor + Contributor at the
cluster scope (#6).
ACR Build button (both bulk and per-image) is disabled with
the tooltip for users without Contributor at the ACR's RG
(#6).
BLAST submit 4xx error bodies no longer leak SAS tokens, sig=
params, or subscription ids through str(exc) paths. Every
exception message is now run through
api.services.sanitise.sanitise before truncation (critique #7).
scripts/dev/quick-deploy.sh now runs an ARM read-access
preflight on the resource group / ACR / Container App before any
destructive step, so a permission gap surfaces with a clear
remediation message (with the exact az role assignment list
command) instead of after a 30-90 s build (critique #8).
New _safe_exc_message(exc) helper centralising sanitise + truncate; 4 str(exc)[:_EXCEPTION_DETAIL_MAX_CHARS] sites + the summary[:…] site replaced with sanitised equivalents (#7)
Wires usePermissions(sub, rg, cluster); Start/Stop wrapped under can_start_stop, Delete under can_delete; permissionDeniedTooltip merged into each button's title and disabled (#6)
Computes submitPermissions at the page level; effectiveCanSubmit = validation.canSubmit && !submitPermissionDenied; handleSubmit early-returns with a toast when denied (#6)
New preflight_permission_check() runs after confirm_deploy_target for both the all and per-sidecar paths; probes az group/acr/containerapp show and fails fast with the exact role + az role assignment list command needed. Skippable via ELB_QUICK_DEPLOY_SKIP_PREFLIGHT=1 for CI (#8)
$ uv run pytest -q api/tests
............................................................... [100%]
1906 passed, 3 skipped in 44.66s
$ cd web && npm test -- --run
Test Files 56 passed (56)
Tests 433 passed (433)
$ uv run ruff check api
All checks passed!
$ cd web && npm run build
✓ built in 7.98s
$ bash -n scripts/dev/quick-deploy.sh
(no output — syntax OK)
Consumer search for usePermissions confirmed exactly four call
sites land in this PR (AutoStopPanel from the prior commit +
PulseActions + BlastSubmit + AcrCard). Each scopes the permission
query to the smallest meaningful Azure scope (cluster for AKS
actions; RG for ACR build).
Consumer search for str(exc)[:_EXCEPTION_DETAIL_MAX_CHARS] is
now zero in api/routes/blast/submit.py; the remaining instances
in the workspace are all in other route files and out-of-scope
for #7 (followup: roll the helper out to those).
The effectiveCanSubmit derived flag is plumbed through both
submit surfaces; handleSubmit carries a defence-in-depth early
return so a keyboard / programmatic activation cannot slip
through.
permissionDeniedTooltip is the same import in all three new
consumers, so a future change to the wording propagates
automatically.
preflight_permission_check uses az ... show -o none so a
successful probe stays silent; failures die with both a clear
reason and the exact diagnostic command.
Wire _safe_exc_message (or a sibling helper) to the other
api/routes/blast/* modules that still embed raw str(exc)
(jobs.py, results.py, …) — out of scope this round.
Add a useEffect in BlastSubmit.tsx to invalidate the
permission query when the user picks a different cluster — the
default staleTime: 60s plus the natural unmount/remount on
navigation is acceptable for now.
The AcrCard per-image build button stays visually enabled but
no-ops when denied (the page-level Build button is the canonical
affordance). A future round can promote the per-row buttons to
the same disabled-with-tooltip treatment.