diff --git a/docs/CAMPAIGN_HANDBOOK.md b/docs/CAMPAIGN_HANDBOOK.md index 7e80a05..6a0fc7b 100644 --- a/docs/CAMPAIGN_HANDBOOK.md +++ b/docs/CAMPAIGN_HANDBOOK.md @@ -144,10 +144,14 @@ paths, storage keys, worker claim tokens, and raw provider diagnostics require the dedicated diagnostic permission and must not leak through ordinary campaign, version, job, or report responses. -The current Campaign Report Web UI additionally requires recipient-read access -and does not yet hide every action control that the actor lacks. The server -still authorizes each action, but an aggregate-only reader UI remains open -work; do not promise that experience from `campaigns:report:read` alone. +Campaign now provides a separate aggregate **Reports** surface for readers with +`campaigns:report:read` and access to the campaign. It loads only the safe +aggregate projections, applies small-cell suppression, and offers no recipient +rows, drill-down, filtering, export, or delivery actions. The recipient-aware +**Campaign Report** still requires recipient-read access and does not yet hide +every action control that the actor lacks. The server authorizes each action, +but permission-aware action visibility on that detailed surface remains open +work; do not confuse it with the aggregate reader experience. ### Deliver and resolve outcomes diff --git a/tests/test_documentation.py b/tests/test_documentation.py index a61578a..712beb3 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import Path from types import SimpleNamespace from unittest.mock import patch @@ -364,6 +365,18 @@ def test_aggregate_report_task_never_implies_recipient_detail_or_export_authorit assert "export" in topic.metadata["verification"].lower() +def test_handbook_distinguishes_shipped_aggregate_reports_from_detailed_report_gaps() -> None: + handbook = " ".join( + ( + Path(__file__).resolve().parents[1] / "docs" / "CAMPAIGN_HANDBOOK.md" + ).read_text(encoding="utf-8").lower().split() + ) + + assert "aggregate-only reader ui remains open" not in handbook + assert "separate aggregate **reports** surface" in handbook + assert "permission-aware action visibility on that detailed surface remains open work" in handbook + + def test_static_campaign_handbook_has_unique_ids_help_contexts_and_no_planned_resend_claim() -> None: from govoplan_campaign.backend.manifest import get_manifest