# Who Need Help — local hardening completion plan

This plan deliberately excludes buying a domain, renting another server, and
real third-party credentials. The existing local Docker/kind environments and
the temporary `whoneedhelp.imalto.site` staging origin are sufficient for every
item below unless the evidence column explicitly describes a local mock.

| Area | Observed baseline | Local completion evidence |
| --- | --- | --- |
| Browser E2E | Manual headed-Chrome scenarios exist; no committed browser suite | A fresh uniquely named Compose project runs two-user urgent help, Activity, moderation, privacy, and error paths; traces are retained on failure; its exact volume is removed |
| Android UI | Two JVM unit-test files; no `androidTest` source set | Emulator instrumentation covers deep links, permissions, foreground tracking, notification Stop, lifecycle, and network failure |
| CI and quality | No tracked CI workflow or static/security analysis dependencies | The same containerized gates pass locally and are represented in a validated CI workflow |
| Localization and accessibility | Completed locally: product copy and custom validation messages are extracted; EN/UK/RU catalogs and localized category descriptions/structured values are implemented | 508 default and 40 error messages are current; RU/UK have no empty/fuzzy entries; 174 backend tests and all 30 browser scenarios pass across Chromium, Firefox and WebKit, including locale persistence, keyboard, axe, themes, responsive widths, and reconnect |
| Database scale | Core discovery/chat/moderation lists call unbounded `Repo.all()` | Cursor-bounded queries pass behavior tests and measured `EXPLAIN ANALYZE` checks on an isolated generated dataset |
| Load and resilience | Public/readiness/heartbeat k6 profile exists | Authenticated writes, chat, tracking, reconnect, rolling replacement, and worker retry profiles pass without touching staging data |
| Observability | Completed locally: protected per-process metrics feed a pinned Prometheus/Grafana/Alertmanager profile | All 3 direct web targets are up before/after the drill; a verified replica stop delivers firing and resolved webhooks; Grafana datasource/dashboard and an empty DB-count diff are retained |
| Backup | Completed locally: a plaintext-free Restic stream is retained in pinned local MinIO | The encrypted repository passes full-data checking and fresh-database restore; corrupted configuration and an interrupted upload fail closed, leave no snapshot, and their temporary buckets are removed |
| External boundaries | Completed locally: real Assent/Req, Swoosh/gen_smtp, and the provider-neutral push adapter run against an internal protocol mock | Success, rejection, retry, replay, timeout, PKCE/state, one-time-code, and idempotency paths pass without host ports or retained secrets; acceptance/chat product events pass through an ephemeral PostGIS database and two Oban workers |
| Final regression | 174 Phoenix tests plus reproducible browser and Android device suites | Browser, Android, API, DB, WebSocket, backup, monitoring, failure, cleanup, docs, and clean Git are verified from the final commits |

The goal remains open while any row lacks reproducible local evidence.

## Verified progress

- The isolated browser harness now generates independent local secrets, starts
  two web and two worker replicas behind Traefik, and removes only its uniquely
  named Compose project and PostGIS volume.
- The browser suite passes public/authentication boundaries and the complete
  two-user medicine flow: Mailpit confirmation, category-driven request,
  matching, bidirectional realtime chat, handover verification, bilateral
  completion, and double-blind review reveal.
- A separate browser scenario passes Activity creation, participant approval,
  bidirectional private group chat, a report scoped to the selected message,
  block/unblock, inherited location privacy, manual social-link removal,
  category-proposal rejection, report resolution, account restriction, and
  enforcement of that restriction. Its administrator is created in the
  isolated database through the existing audited one-time bootstrap command.
- Browser console errors, page errors, and unexpected failed requests are
  test failures. Playwright traces, screenshots, video, JSON/HTML reports, and
  Compose logs are retained in ignored output on failure.
- The browser gate also passes axe checks on the home, safety, login, and
  registration pages in both light and dark themes, keyboard skip navigation,
  and 360/768/1440-pixel overflow checks. Its local raster-tile endpoint exists
  only in the E2E-compiled image, and map scenarios wait for MapLibre `idle`
  before navigation.
- A forced LiveSocket transport loss while Chromium is offline exposes the
  reconnect alert, and restoring the network clears it and reconnects the
  authenticated LiveView.
- The isolated API 30/34/37 Android matrix passes seven device tests per API:
  missing and granted location permission, same-origin deep-link routing across
  Activity recreation, native foreground location upload, notification Stop
  with remote cleanup, a disconnected Stop request with visible retry state,
  continued uploads after Home and Activity destruction, and a forced
  main-frame disconnect followed by native Retry and a rendered DOM. A separate probe
  externally kills the instrumentation process and verifies that the
  `START_NOT_STICKY` service and notification do not return. The suite uses an
  in-process loopback fixture, runs each emulator without external networking,
  retains diagnostics on failure, and removes its exact container and image.
- The containerized `scripts/quality.sh` gate passes ShellCheck, Hadolint,
  actionlint, all Compose renders, Helm lint, a Trivy scan of tracked source and
  the rendered Kubernetes manifest, compiler/xref/Credo/Sobelow/Dialyzer/Hex
  checks, 174 Phoenix tests, both npm audits, and Trivy scans of the backup,
  MinIO server/client, external-mock, and production release images. It creates
  random one-run database credentials and removes its exact volume, networks,
  images, and source snapshot.
- The checked-in GitHub Actions workflow runs the same isolated backend/security
  gate, browser E2E suite, and Android unit/lint/APK build. The workflow itself
  passes actionlint and uses a commit-pinned checkout action without persisted
  credentials.
- The hardened local kind manifests also passed a runtime rollout: PostGIS,
  Mailpit, both web replicas, and both worker replicas became Ready as non-root
  containers with read-only root filesystems, dropped capabilities, and
  RuntimeDefault seccomp where configured. The cross-node PubSub probe passed.
- EN/UK/RU extraction and catalogs are now complete for current product UI and
  custom validation messages. Locale selection persists for public pages and
  authenticated LiveViews; category descriptions and structured values follow
  that browser locale. The localization browser specs run in the same isolated
  two-web/two-worker gate as the accessibility checks.
- The isolated three-web/two-worker load profile now uses real password login,
  authenticated LiveView joins, tracking start, HTTP position writes, private
  chat writes, and explicit tracking stop. A measured 30-second run completed
  all 1,765 authenticated chains and 240 heartbeat sockets with no functional
  failure, retained no current positions after stop, passed cross-node PubSub
  and readiness, and restored every tracked table count after exact fixture
  cleanup.
- The same isolated profile now passes deliberate web/worker BEAM crashes,
  sequential replacement of every replica, exact five-node cluster/PubSub
  checks, and an Oban job that records one failure before succeeding on its
  second attempt. A 725-sample readiness probe observed no HTTP failure
  and the probe job/domain fixtures were absent afterward.
- The project-owned kind cluster also passes a full web/worker rolling restart:
  all four pod UIDs changed, all replacements became Ready with zero restarts,
  the four-node cluster/PubSub probe passed, and database counts were unchanged.
  The local single-node NodePort needed two one-retry samples across 395
  ultimately successful samples; this is recorded rather than presented as
  raw transport continuity.
- The isolated observability profile scrapes all three web containers by direct
  target with a file-based Bearer credential, provisions a four-panel Grafana
  dashboard, and routes the `up == 0` rule through Alertmanager. The canonical
  drill observed both firing and resolved webhooks for the exact stopped
  replica, restored all three targets, and left database counts unchanged.
- The encrypted backup profile streams PostgreSQL custom format through Restic
  directly into local MinIO without a host plaintext archive. The canonical
  drill restored 23 public tables with all 10 migrations and PostGIS 3.6.4,
  rejected an intentionally corrupted repository, stopped a scoped upload
  after an encrypted object reached MinIO, published no interrupted snapshot,
  pruned the unreferenced packs, removed both temporary buckets and the
  temporary restore database, and left source table counts unchanged.
- The isolated external-service drill uses the release's configured Assent/Req
  and Swoosh/gen_smtp clients against an internal-only protocol mock. Its
  latest run passed OAuth success, PKCE/state rejection, provider denial,
  one-time-code replay, temporary failure and timeout; SMTP success, permanent
  rejection, one temporary retry, timeout and repeated submission; and the HTTP
  push boundary's disabled default, retry, rejection, replay, and ambiguous
  timeout paths. An ephemeral PostGIS database plus two worker replicas also
  delivered real acceptance and chat events, observed an Oban retry on attempt
  2, rejected replay before transport, and excluded private chat content. It
  retained no credentials, published no host ports, and removed its exact
  project, volume, network, and one-run images. FCM/APNs device registration is
  not claimed.
- The final regression repeated the 174-test quality/security gate, isolated
  browser 1/1 bootstrap plus 30/30 application scenarios across Chromium,
  Firefox and WebKit, the public 3/3 full workflow replay and 1/1 PWA replay,
  Android debug/staging builds and 7/7 device tests on API 30, 34, and 37, the
  50,000-row database benchmark, authenticated HTTP/LiveView/WebSocket/tracking
  load, Compose crash/replacement and kind rolling drills, alert
  firing/resolution, encrypted backup/restore failure paths, and the external
  protocol boundary. Scoped fixtures and one-run projects were removed,
  retained evidence is ignored and non-secret, and Git was clean at the tested
  application commit before this documentation update.
- The public-facing database backup also passes a full isolated upgrade
  rehearsal against application commit `af9018f`: the restored copy advanced
  from 8 to all 10 migrations, exposed all 11 valid cursor indexes, ran two web
  and two worker replicas in a four-node cluster, passed cross-node PubSub,
  production HTTPS redirects, trusted-proxy pages, and both health endpoints,
  and retained an empty application-table count diff. The scoped project,
  volume, networks, and one-run image were removed; the ordinary Compose
  project stayed on its original image and migration level.
- After explicit approval for the development rollout, a fresh backup passed
  its restore drill and the ordinary Compose project was upgraded from 8 to all
  10 migrations at application commit `143e159`. Its full application-table
  snapshot had an empty before/after diff. The current two-web/two-worker
  release then passed local health, four-node cluster/PubSub, public HTTPS/TLS,
  WebSocket Upgrade, post-rollout log scanning, and headed-Chrome Russian
  localization and authorization-boundary checks. Evidence is retained under
  ignored `output/staging-rollout/20260719093528-3223196/`.
