Verify production support and legal queues
This commit is contained in:
parent
cd9a21af85
commit
ccbef9cd4f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
2
mix.lock
2
mix.lock
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user