Verify production support and legal queues

This commit is contained in:
SimpleTest 2026-08-21 01:11:49 +03:00
parent cd9a21af85
commit ccbef9cd4f
8 changed files with 122 additions and 29 deletions

View File

@ -1274,8 +1274,9 @@ WNH_PRODUCTION_E2E_CONFIRM='VALUE_PRINTED_BY_PLAN' \
The run creates six uniquely prefixed synthetic users and only their associated
mutual-aid, activity, notification, audit, support, and legal fixture records.
The support and legal records are inserted directly without email jobs and are
only read through the staff UI; the browser does not submit or moderate them.
The support record, general-removal notice, and separate TAKE IT DOWN notice are
inserted directly without email jobs and are only read through the staff UI;
the browser does not submit or moderate them.
Cleanup uses the mode-`0600` manifest of exact IDs on success, failure, or
interrupt, refuses cross-fixture relationships, deletes only matching jobs and
records, and verifies that the run prefix is absent. It never resets the

View File

@ -197,11 +197,13 @@ as forward-only rather than receiving an invented database rollback.
approval, and reporting.
- [ ] Support, privacy/data, account-deletion, general content-removal, and
TAKE IT DOWN submissions reach the correct production operator queues.
A 2026-08-09 run-scoped check proved one authenticated support case and
one authenticated general content-removal notice, then removed only those
exact records. Privacy/data, account deletion, anonymous verification,
and TAKE IT DOWN still require an exact production smoke before this
combined item can be checked.
A 2026-08-21 run-scoped check proved authenticated support, privacy,
data-export and account-deletion submissions plus separate general
content-removal and TAKE IT DOWN records. Staff found every record in its
permission-scoped production queue, and exact cleanup left zero records
with the run prefix. Anonymous contact verification still requires a
controlled production mail smoke before this combined item can be
checked.
- [ ] Database, application, worker, email, push, backup, and edge monitoring
are visible to the responsible operator.

View File

@ -3265,3 +3265,26 @@ promoted.
`support_confirmation` deliveries, two successful `mail` queue jobs, and no
delivery exception in the selected metric set. These counters describe only
the current application process, not historical delivery volume.
# 2026-08-21 production support and legal queue coverage
- Temporary production run `codex-support-legal-20260821-r3` passed all three
Chromium scenarios against exact deployed revision
`bb7eb58c8f14d8936cae0e968b50ae721516d213`: activity moderation, the complete
two-person medicine-help flow, and support/legal queue coverage.
- The support/legal scenario submitted authenticated support, privacy,
data-export and account-deletion requests through the public UI. Staff found
the resulting records in the permission-scoped production queue. The same
staff session found and opened distinct run-scoped general-removal and TAKE
IT DOWN records without submitting an operator decision.
- The fixture manifest used schema version 3, recorded the exact support,
general-removal and TAKE IT DOWN UUIDs, and ended with
`cleanup_verified=true`. Post-run inspection found
`fixture_prefix_count=0`; no run-owned fixture user or relationship remained.
- Anonymous contact verification was not submitted in this run because it
intentionally enqueues a real verification email. That path remains an open
controlled production-mail check rather than an inferred success.
- Non-secret evidence is retained under
`output/production-full-e2e/codex-support-legal-20260821-r3/`. The frozen
hackathon test deployment, Caddy, public Git remote and production release
were not changed by this browser run.

View File

@ -55,6 +55,10 @@ test("run-scoped support intake and legal fixture reach production staff", async
const accountDeletionSubject = "Delete my Who Need Help account";
const removalExplanation =
"Run-scoped read-only browser fixture for the production legal review queue.";
const takeItDownExplanation =
"Run-scoped read-only browser fixture for the production TAKE IT DOWN queue.";
const removalReference = `REM-E2E-${runID.toUpperCase()}`;
const takeItDownReference = `REM-E2E-TID-${runID.toUpperCase()}`;
const requester = await loginWithPassword(
browser,
@ -133,14 +137,29 @@ test("run-scoped support intake and legal fixture reach production staff", async
await admin.page
.locator("#legal-case-filters")
.getByLabel("Search")
.fill(requesterEmail);
const legalRow = admin.page.locator("main tbody tr");
await expect(legalRow).toHaveCount(1);
await legalRow.getByRole("link", { name: "Open" }).click();
.fill(removalReference);
const generalLegalRow = admin.page.locator("main tbody tr");
await expect(generalLegalRow).toHaveCount(1);
await expect(generalLegalRow).toContainText("general");
await generalLegalRow.getByRole("link", { name: "Open" }).click();
await expect(
admin.page.getByText(removalExplanation, { exact: true }),
).toBeVisible();
await gotoLiveView(admin.page, "/support/operations?queue=legal");
await admin.page
.locator("#legal-case-filters")
.getByLabel("Search")
.fill(takeItDownReference);
const takeItDownRow = admin.page.locator("main tbody tr");
await expect(takeItDownRow).toHaveCount(1);
await expect(takeItDownRow).toContainText(/take it down/i);
await expect(takeItDownRow).toContainText("urgent review");
await takeItDownRow.getByRole("link", { name: "Open" }).click();
await expect(
admin.page.getByText(takeItDownExplanation, { exact: true }),
).toBeVisible();
assertRequesterClean();
assertAdminClean();
await requester.context.close();

View File

@ -142,7 +142,13 @@ defmodule Mix.Tasks.Wnh.StagingFullE2e do
password_hash = Bcrypt.hash_pwd_salt(password)
now = DateTime.utc_now(:second)
{:ok, %{users: users, support_request: support_request, removal_notice: removal_notice}} =
{:ok,
%{
users: users,
support_request: support_request,
removal_notice: removal_notice,
take_it_down_notice: take_it_down_notice
}} =
Repo.transaction(fn ->
users =
Map.new(@precreated_roles, fn role ->
@ -167,22 +173,25 @@ defmodule Mix.Tasks.Wnh.StagingFullE2e do
support_request = insert_support_fixture!(context, requester, now)
removal_notice = insert_removal_fixture!(context, requester, now)
take_it_down_notice = insert_take_it_down_fixture!(context, requester, now)
%{
users: users,
support_request: support_request,
removal_notice: removal_notice
removal_notice: removal_notice,
take_it_down_notice: take_it_down_notice
}
end)
manifest = %{
"schema_version" => 2,
"schema_version" => 3,
"run_id" => context.run_id,
"database" => context.database,
"precreated_users" => users,
"precreated_records" => %{
"support_request" => support_request.id,
"content_removal_notice" => removal_notice.id
"content_removal_notice" => removal_notice.id,
"take_it_down_notice" => take_it_down_notice.id
},
"allowed_emails" => context.emails |> Map.values() |> Enum.sort()
}
@ -468,15 +477,17 @@ defmodule Mix.Tasks.Wnh.StagingFullE2e do
case manifest["precreated_records"] do
%{
"support_request" => support_request_id,
"content_removal_notice" => removal_notice_id
"content_removal_notice" => removal_notice_id,
"take_it_down_notice" => take_it_down_notice_id
} ->
uuid?(support_request_id) and uuid?(removal_notice_id)
uuid?(support_request_id) and uuid?(removal_notice_id) and
uuid?(take_it_down_notice_id) and removal_notice_id != take_it_down_notice_id
_other ->
false
end
unless manifest["schema_version"] == 2 and manifest["run_id"] == context.run_id and
unless manifest["schema_version"] == 3 and manifest["run_id"] == context.run_id and
manifest["database"] == context.database and
manifest["allowed_emails"] == expected_emails and valid_precreated? and
valid_precreated_records? do
@ -499,7 +510,9 @@ defmodule Mix.Tasks.Wnh.StagingFullE2e do
records = manifest["precreated_records"]
unless records["support_request"] in support_request_ids and
records["content_removal_notice"] in removal_notice_ids do
records["content_removal_notice"] in removal_notice_ids and
records["take_it_down_notice"] in removal_notice_ids and
records["content_removal_notice"] != records["take_it_down_notice"] do
Mix.raise("full staging E2E precreated records do not match the manifest")
end
end
@ -810,6 +823,32 @@ defmodule Mix.Tasks.Wnh.StagingFullE2e do
|> Repo.insert!()
end
defp insert_take_it_down_fixture!(context, requester, now) do
%Notice{
reference: "REM-E2E-TID-#{String.upcase(context.run_id)}",
requester_id: requester["id"],
contact_verified_at: now,
regime: :take_it_down,
status: :urgent_review,
response_due_at: DateTime.add(now, 48, :hour)
}
|> Notice.submission_changeset(%{
"category" => "non_consensual_intimate_media",
"submitter_name" => "Production E2E Requester",
"contact_email" => requester["email"],
"relationship" => "self",
"content_locations" =>
"https://whoneedhelp.com/requests/production-e2e-take-it-down-#{context.run_id}",
"explanation" =>
"Run-scoped read-only browser fixture for the production TAKE IT DOWN queue.",
"legal_basis" => "Production E2E fixture only.",
"electronic_signature" => "Production E2E Requester",
"good_faith" => "true",
"accurate_complete" => "true"
})
|> Repo.insert!()
end
defp emails(run_id) do
(@precreated_roles ++ @registered_roles)
|> Map.new(fn role -> {role, "#{prefix(run_id)}#{role}@example.invalid"} end)

View File

@ -1,6 +1,6 @@
%{
"assent": {:hex, :assent, "0.3.1", "7e7b04f4ab5d07b497b80c04f009a0f1efc409787e68b8e086512b9098f3095b", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "3597b31f9eb556d97e64cf60c00d3451f7353d7b465a71d33530b870ebed1ff1"},
"bandit": {:hex, :bandit, "1.12.4", "10bbab488edf8162318d736c19c5837077b8fca2bf5d95b07b33830387124f62", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "84513318c5752a2a8017664450f889b47fae5d53d64698ddf1e4fb09a7449e8d"},
"bandit": {:hex, :bandit, "1.12.5", "af205a8e550f304caae09a97d29fd3c79a7f337526ea7cd772d2ff11d2f7c800", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "c5684ca062fa407cac115aec3256383f3e2ec9fdced7904d59cf5a7bb7ed6181"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},

View File

@ -209,8 +209,8 @@ Exact temporary mutation scope:
- their activity, participation, group chat, report and category proposal;
- one directly inserted support row plus three authenticated privacy,
data-export and account-deletion requests submitted through the public UI;
- one directly inserted legal row, read through the staff UI without
submitting or moderating a content-removal notice;
- one directly inserted general-removal row and one separate TAKE IT DOWN
row, read through the staff UI without submitting or moderating either;
- their notifications, audit events and associated Oban jobs;
- no database reset, migration, real-user role/status change, email delivery,
Caddy change, test-project change, payment, iOS, or KYC action.
@ -327,10 +327,10 @@ cleanup() {
.cleanup_verified == true and
(.cleanup_targets.users | length) >= 6 and
(.cleanup_targets.support_requests | length) >= 1 and
(.cleanup_targets.content_removal_notices | length) == 1 and
(.cleanup_targets.content_removal_notices | length) == 2 and
(.cleanup_deleted_counts.users | type) == "number" and
.cleanup_deleted_counts.support_requests == (.cleanup_targets.support_requests | length) and
.cleanup_deleted_counts.content_removal_notices == 1 and
.cleanup_deleted_counts.content_removal_notices == 2 and
.cleanup_verified_at != null
' "$output_dir/fixture.json" >/dev/null; then
echo "Production E2E cleanup manifest lacks exact run-scoped verification." >&2

View File

@ -53,6 +53,12 @@ defmodule WhoNeedHelp.StagingFullE2ECleanupTest do
manifest["precreated_records"]["content_removal_notice"]
)
prepared_take_it_down_notice =
Repo.get!(
WhoNeedHelp.ContentRemoval.Notice,
manifest["precreated_records"]["take_it_down_notice"]
)
browser_created_support_request =
%WhoNeedHelp.Support.SupportRequest{
reference: "SUP-BROWSER-#{String.upcase(run_id)}",
@ -69,11 +75,15 @@ defmodule WhoNeedHelp.StagingFullE2ECleanupTest do
})
|> Repo.insert!()
assert manifest["schema_version"] == 2
assert manifest["schema_version"] == 3
assert prepared_support_request.subject == "Production E2E support #{run_id}"
assert prepared_support_request.contact_verified_at
assert prepared_removal_notice.regime == :general
assert prepared_removal_notice.contact_verified_at
assert prepared_take_it_down_notice.regime == :take_it_down
assert prepared_take_it_down_notice.status == :urgent_review
assert prepared_take_it_down_notice.response_due_at
assert prepared_take_it_down_notice.contact_verified_at
refute_enqueued(
worker: WhoNeedHelp.Mail.SupportOperatorAlertWorker,
@ -108,12 +118,11 @@ defmodule WhoNeedHelp.StagingFullE2ECleanupTest do
assert Enum.sort(manifest["cleanup_targets"]["support_requests"]) ==
Enum.sort([prepared_support_request.id, browser_created_support_request.id])
assert manifest["cleanup_targets"]["content_removal_notices"] == [
prepared_removal_notice.id
]
assert Enum.sort(manifest["cleanup_targets"]["content_removal_notices"]) ==
Enum.sort([prepared_removal_notice.id, prepared_take_it_down_notice.id])
assert manifest["cleanup_deleted_counts"]["support_requests"] == 2
assert manifest["cleanup_deleted_counts"]["content_removal_notices"] == 1
assert manifest["cleanup_deleted_counts"]["content_removal_notices"] == 2
assert support_job.id in cleaned_job_ids
assert legal_job.id in cleaned_job_ids
refute unrelated_job.id in cleaned_job_ids