# Privacy Center — Improvement & Growth Plan

> Package: capell-app/privacy-center · Kind: package · Tier: premium · Product group: Capell Operations · Bundle: operations · Status: Draft

## 1. Snapshot

Privacy Center is an admin + console package providing a package-owned compliance ledger: consent records, policy versions/acceptances, subject (DSAR) requests, retention rules, plus export/anonymize Actions. It ships 5 models/tables (`privacy_consent_policies`, `privacy_consent_records`, `privacy_policy_acceptances`, `privacy_retention_rules`, `privacy_requests`), 11 Actions (`src/Actions`), 5 Filament resources + 1 stats widget (`src/Filament`), 8 Data objects, 8 enums, and a per-subject SHA-256 hashing helper (`src/Support/PrivacyIdentifier.php`). Surfaces declared: `admin`, `console`. Deps: requires `capell-app/admin` + `capell-app/core`; supports `access-gate`, `contacts`, `insights`, `newsletter`. The only live cross-package integration today is `insights` calling `RecordConsentAction` via `packages/insights/src/Actions/MirrorInsightsConsentToPrivacyCenterAction.php`.

Current marketplace summary (verbatim): _"Privacy Center provides the compliance workflow foundation for consent, retention, privacy requests, exports, deletion, and policy acceptance."_ Screenshot count: **0** (`marketplace.screenshots: []`) — an optional `docs/screenshots.json` contract now documents the intended request queue, workflow action, retention-rule, and dashboard-widget captures, but the current screenshot runner app does not require `capell-app/privacy-center`, so valid package routes cannot be captured until that runner requirement/install gap is closed. `README.md` and `CHANGELOG.md` are **absent from disk** (only `docs/overview.md` exists), despite `composer.json` advertising a docs URL.

## 2. Improvements (existing functionality)

- **Shipped 2026-06-05: DSAR edit workflow actions stamp request state through Actions.** `PrivacyRequestResource` exposes translated "Mark verified", "Mark fulfilled", and "Reject" actions that delegate to `MarkPrivacyRequestVerifiedAction`, `MarkPrivacyRequestFulfilledAction`, and `RejectPrivacyRequestAction`. Evidence: `PrivacyCenterAdminSurfaceTest` now exercises the edit-page action objects and proves `verified_at`, `fulfilled_at`, `rejected_at`, and `rejection_reason` are written. — `src/Filament/Resources/PrivacyRequests/PrivacyRequestResource.php`, `tests/Unit/PrivacyCenterAdminSurfaceTest.php`
- **Shipped 2026-06-06: retention rules can be dry-run or executed from admin.** `RetentionRuleResource` now exposes translated per-row `Dry run` and `Run now` actions. Dry-run delegates to `ApplyRetentionRuleAction` with `dryRun: true` and reports matched records without mutating data; run-now applies the rule after confirmation and reports matched/affected counts through Filament notifications. — `src/Actions/ApplyRetentionRuleAction.php`, `src/Filament/Resources/RetentionRules/RetentionRuleResource.php`
- **Shipped 2026-06-06: record table filters added.** `ConsentRecordResource` now filters by category, decision, and jurisdiction; `PrivacyRequestResource` filters by type, status, and overdue due dates; `PolicyAcceptanceResource` filters by policy type and context. — `src/Filament/Resources/*/*Resource.php` — S
- **Index/guard against orphaned `policy_id` after retention anonymize** — `ApplyRetentionRuleAction::anonymize()` and `AnonymizePrivacySubjectAction` null out subject/evidence but leave `policy_id`; fine, but `RegisterConsentPolicyAction` + `ConsentPolicy` deletion uses `nullOnDelete` — confirm anonymized rows remain queryable by `category`/`decided_at` (they do via existing indexes). Low-effort: add an explicit "anonymized" marker in `metadata` so audit reports can distinguish anonymized vs never-linked rows. — `src/Actions/ApplyRetentionRuleAction.php` — S
- **Shipped 2026-06-05: `PrivacyIdentifier::hashSecret()` fails loudly instead of using a hard-coded literal salt.** If neither `CAPELL_PRIVACY_CENTER_HASH_SECRET` nor `app.key` is set, `PrivacyIdentifier` now throws before hashing consent evidence. Evidence: `src/Support/PrivacyIdentifier.php`; `tests/Unit/PrivacyIdentifierTest.php`. — S
- **Shipped 2026-06-06: overview widget stats are cached behind the `privacy-center` tag.** `BuildPrivacyCenterOverviewStatsAction` now wraps the four dashboard counts in `PrivacyCenterOverviewStatsCache`, with a configurable 300-second TTL and tag fallback handling. `ConsentRecord`, `PrivacyRequest`, and `RetentionRule` model events flush the cached stats when records affecting the widget are saved or deleted. — `src/Actions/BuildPrivacyCenterOverviewStatsAction.php`, `src/Support/PrivacyCenterOverviewStatsCache.php`, `src/Models/*`
- **`KeyValue` editing of `workflow_payload`/`metadata` on the request form is free-text and unvalidated** — operators can corrupt structured workflow payloads. Make these read-only or render via a structured infolist. — `src/Filament/Resources/PrivacyRequests/PrivacyRequestResource.php` — S

## 3. Missing Features (gaps)

Mapped to `capabilities[]`:

- **Done/Shipped: public cookie-category consent surface.** Privacy Center now ships `/privacy/consent`, a public preference center, plus an embeddable banner partial. The form submits category handles only, grants essential cookies automatically, records optional categories as granted/denied through `RecordConsentAction`, sets a saved-preferences cookie, and keeps policy model IDs, admin URLs, hashed evidence, package internals, and editor state out of anonymous HTML. `capell.json` now restores `privacy-center-cookie-categories`, declares a public surface, and sets a 40ms frontend render budget for the cache-safe public preference center. — `routes/web.php`, `src/Http/Controllers/*ConsentPreferencesController.php`, `resources/views/consent/*`, `tests/Feature/PublicConsentPreferenceCenterTest.php`
- **Shipped 2026-06-05: `privacy-center-retention-execution` is now reachable.** `ApplyRetentionRulesAction` is exposed through `privacy:apply-retention`, `PrivacyRetentionScheduleContribution` declares the command and daily frequency in `capell.json`, and `PrivacyCenterServiceProvider` schedules the command daily. Evidence: `src/Console/Commands/ApplyRetentionRulesCommand.php`; `src/Providers/PrivacyCenterServiceProvider.php`; `capell.json`.
- **No public DSAR intake (subject request) form.** `OpenPrivacyRequestAction` exists, but only `insights`/code can call it. GDPR Art. 12/15-22 + CCPA require a _consumer-facing_ way to submit access/export/delete requests. No public route, no verification (email confirmation) flow — `verified_at` exists on the model but nothing populates it. — table-stakes.
- **DSAR export/erasure is single-package only (completeness gap).** `BuildPrivacyExportAction` and `AnonymizePrivacySubjectAction` only touch Privacy Center's own 3 tables. There is **no cross-package erasure/export registry** — no contract a package implements to contribute its data to a subject export or to be erased. `docs/overview.md` explicitly defers this ("operate on Privacy Center records first"), but a "Privacy Center" that can't fulfil a real right-to-erasure across `contacts`/`insights`/`newsletter` is incomplete. — **key differentiator** if built: a `ContributesSubjectData` / `ErasesSubjectData` contract that other packages register against.
- **Shipped 2026-06-05: Insights-mirrored consent now carries a subject link.** `RecordConsentAction` now infers a loaded `subject` or `visit` model from the source when no explicit subject is passed, matching the existing Insights mirror call shape where the consent source is loaded with `visit`. Mirrored records now receive `subject_type`/`subject_id`, so `BuildPrivacyExportAction` and `AnonymizePrivacySubjectAction` can find them. Evidence: `src/Actions/RecordConsentAction.php`; `tests/Feature/PrivacyCenterFoundationTest.php`.
- **No consent proof export / certificate.** Records exist but there's no "produce signed proof of consent at time T" output (CSV/PDF), which is the artefact regulators/auditors actually ask for. — differentiator.
- **No geo/jurisdiction-aware policy selection.** `jurisdiction` is a free string column on consent; nothing maps a visitor region → which policy/categories apply (GDPR vs CCPA vs nothing). — differentiator.
- **No consent expiry/refresh job.** `expires_at` exists on `ConsentRecord` and `ConsentDecision::Expired` exists, but nothing transitions granted→expired or re-prompts. — table-stakes for "valid consent" claims.
- **No policy publication workflow.** `ConsentPolicy` has `published_at`/`effective_at`/`retired_at` but the resource is plain CRUD — no "publish"/"retire" action, no enforcement that acceptances reference a published version.

## 4. Issues / Risks

- **Shipped 2026-06-05: Manifest capability overclaims reconciled for the obvious public-surface gap.** `privacy-center-retention-execution` now has a `privacy:apply-retention` command and daily scheduled-job metadata; `privacy-center-cookie-categories` was removed from `capell.json` until a public banner/preference centre ships. Evidence: `capell.json`; `src/Console/Commands/ApplyRetentionRulesCommand.php`; `tests/Unit/ManifestRequirementsTest.php`.
- **Shipped 2026-06-05: Health check now verifies real package discoverability.** `PrivacyCenterHealthCheck` verifies required storage tables, morph-map model aliases, the loaded service provider, and identity hash configuration. Evidence: `src/Health/PrivacyCenterHealthCheck.php`; `tests/Unit/PrivacyCenterHealthCheckTest.php`.
- **All four `Manifest/*Contribution` classes are behaviourless markers.** `ConsentPolicyResourceContribution`, `PrivacyCenterOverviewWidgetContribution`, `PrivacyCenterModelsContribution`, `PrivacyRetentionScheduleContribution` only return a version string. They pass `ManifestRequirementsTest` (which asserts the marker interface is implemented) but encode no real contribution beyond what `capell.json` strings declare. Acceptable per core's design, but it means the test suite proves _declaration_, not _behaviour_.
- **Test gaps:**
    - No test exercises the **scheduled job actually running** (because nothing wires it). The retention tests call `ApplyRetentionRulesAction::run()` directly — they would pass even though production never triggers it.
    - Shipped 2026-06-05: focused Filament edit action-object coverage proves verifying, fulfilling, and rejecting privacy requests stamps the expected workflow fields. Full Livewire page render/save interaction coverage remains open.
    - Shipped 2026-06-05: `PrivacyIdentifier` hash determinism, secret variance, and fail-loud missing-secret behavior are covered in `tests/Unit/PrivacyIdentifierTest.php`. Anonymization irreversibility still needs direct coverage.
    - No public-output cache-safety test, despite the skill mandating one for any package with public surface — currently moot because there _is_ no public surface, but the moment a banner ships this becomes required.
    - No assertion that the insights mirror produces records discoverable by export (it doesn't — see §3).
- **Consent-record integrity / proof.** Only `ip_hash`/`user_agent_hash` + free-form `evidence` JSON are stored. There is no immutable/append-only guarantee and no tamper-evidence (e.g. hash chaining), so the ledger's value as legal proof is weak. `evidence` is nulled on anonymize, which is correct, but there's no retained "consent happened" skeleton record after subject erasure.
- **Cache-safety posture is declared but untested.** `performance.cacheSafety` correctly sets `cacheable:false, sensitiveOutput:true, variesBy:[site,subject]`. Good. But the admin tables/widget pull hashed identifiers (`email_hash`, `ip_hash`) into the UI; ensure these never leak to non-admin/log surfaces. `adminQueryBudget: 40` is declared but nothing measures it; the widget's 4 counts + 5 resource list queries could exceed it on heavy pages.
- **`frontendRenderBudgetMs: 0`** correctly signals "no frontend render" — but it will need a real budget the moment a consent banner is added; flag now.
- **i18n.** Enum labels and admin strings are fully translated (`resources/lang/en/privacy.php`) — good. But there is exactly one locale (`en`), and the (future) public banner/DSAR form copy isn't covered at all.
- **Docs.** `README.md` and `CHANGELOG.md` do not exist on disk; `composer.json` advertises `docs: https://docs.capell.app/packages/privacy-center`. Only `docs/overview.md` (which is candid that admin/retention surfaces are deferred). For a paid package this is thin.

## 5. Marketplace & Selling

**Critique.** The current `summary` ("...compliance workflow _foundation_ for consent, retention, privacy requests, exports, deletion, and policy acceptance") and composer `description` ("Privacy, consent, retention, and subject request _foundation_ for Capell CMS") both lean on the word "foundation," which is honest (it _is_ a ledger of Actions) but undersells nothing and oversells the cookie-banner/retention-execution capabilities that aren't reachable. Buyers reading "consent" + "cookie categories" will expect a banner; there is none. Zero screenshots for a premium paid SKU is a conversion killer.

**Improved 1-sentence summary:**

> The compliance backbone for Capell — one auditable ledger for cookie consent, policy acceptances, retention rules, and GDPR/CCPA subject requests, with Actions your other packages plug straight into.

**Improved 3–4 sentence description:**

> Privacy Center gives every Capell site a single, queryable system of record for privacy obligations: granular cookie-category consent, versioned policy acceptances, retention rules, and access/export/delete subject requests. Other Capell packages — Insights, Contacts, Newsletter — record consent and contribute subject data through stable Actions, so exports and erasure stay complete across the stack. Hashed request evidence (IP, user-agent) and immutable consent records give you defensible proof, while scheduled retention keeps data minimised automatically. Admin operators get resources and an at-a-glance compliance dashboard; nothing sensitive ever leaks to public output.

(Note: the second and third sentences describe the _target_ state — the cross-package contract and scheduled retention are gaps in §3 and must ship before this copy is truthful.)

**Screenshot/media gaps:** need at minimum (1) the privacy-requests queue with status badges, (2) retention rules list, (3) the overview dashboard widget, (4) a request detail with fulfil/reject actions, and — once built — (5) the public cookie banner / preference centre. Currently `screenshots: []`; the runner contract is present but blocked until the runner app requires `capell-app/privacy-center`.

**Pricing/tier/bundle positioning.** Premium tier in the `operations` bundle is right — privacy is a horizontal, regulatory must-have, not a nice-to-have, and bundling with other Operations packages suits agencies/regulated buyers. The strongest commercial wedge is the **Extension Suite cross-sell**: it `supports` `insights`, `contacts`, `newsletter`, `access-gate`. Lead with "buy Insights _and_ Privacy Center so analytics consent is captured and erasable," and "Contacts + Privacy Center = real right-to-erasure for your CRM." The inbound `insights` mirror already demonstrates the pattern; make the outbound export/erasure contract the headline suite feature.

**Differentiators / value props / target buyer.** Target buyer: agencies and in-house teams running EU/UK/CA-facing Capell sites who need defensible compliance without bolting on a third-party CMP. Value props: (1) consent + DSAR live _inside_ the CMS, not a separate SaaS; (2) one ledger across all Capell packages; (3) auditable, hashed proof; (4) automated retention. Differentiator vs table-stakes: a generic cookie banner is table-stakes (and currently missing); the **cross-package subject-data contract** (one erasure call wipes contacts + analytics + newsletter consent) is the genuine differentiator no standalone CMP offers.

**Keywords/tags (8–12):** `gdpr`, `ccpa`, `cookie-consent`, `consent-management`, `dsar`, `subject-access-request`, `right-to-erasure`, `data-retention`, `privacy`, `compliance`, `audit-log`, `policy-acceptance`.

## 6. Prioritized Roadmap

| Item                                                                                                                                                                                                                                                                                                                                       | Bucket | Effort | Impact | Section ref                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Shipped 2026-06-05: Wire retention execution with `privacy:apply-retention` console command + `command`/`frequency` in `capell.json` scheduled-job block. Evidence: `src/Console/Commands/ApplyRetentionRulesCommand.php`, `src/Providers/PrivacyCenterServiceProvider.php`, `capell.json`                                                 | Done   | S      | High   | §3, §4                                                                                                                                                                                                                    |
| Shipped 2026-06-05: Make `PrivacyCenterHealthCheck` verify tables/models/provider discoverable. Evidence: `src/Health/PrivacyCenterHealthCheck.php`, `tests/Unit/PrivacyCenterHealthCheckTest.php`                                                                                                                                         | Done   | S      | High   | §4                                                                                                                                                                                                                        |
| Shipped 2026-06-05: Fix insights mirror subject resolution so mirrored consent is exportable/erasable. Evidence: `src/Actions/RecordConsentAction.php`; `tests/Feature/PrivacyCenterFoundationTest.php`                                                                                                                                    | Done   | S      | High   | §3                                                                                                                                                                                                                        |
| Shipped 2026-06-05: Add fulfil/verify/reject UI actions on request edit page (delegate to Actions, stamp timestamps). Evidence: `src/Filament/Resources/PrivacyRequests/PrivacyRequestResource.php`, `tests/Unit/PrivacyCenterAdminSurfaceTest.php`                                                                                        | Done   | M      | High   | §2                                                                                                                                                                                                                        |
| Shipped 2026-06-05: Reconcile manifest capabilities with reality. Evidence: removed `privacy-center-cookie-categories`; retained scheduled retention command/frequency metadata in `capell.json`; covered by `tests/Unit/ManifestRequirementsTest.php`                                                                                     | Done   | S      | High   | §4                                                                                                                                                                                                                        |
| Shipped 2026-06-05: Expanded README/CHANGELOG/docs overview around shipped admin/console surfaces, DSAR export and erasure boundaries, consent mirroring, retention execution, install/config notes, and public-output safety limits. Evidence: `README.md`, `CHANGELOG.md`, `docs/overview.md`, `tests/Unit/ManifestRequirementsTest.php` | Done   | S      | Med    | §4                                                                                                                                                                                                                        |
| Shipped 2026-06-05: Fail-loud when `PrivacyIdentifier` would otherwise fall back to the literal hash salt. Evidence: `src/Support/PrivacyIdentifier.php`, `tests/Unit/PrivacyIdentifierTest.php`                                                                                                                                           | Done   | S      | Med    | §2, §4                                                                                                                                                                                                                    |
| Add table filters (status/overdue/category/decision/jurisdiction) to record resources                                                                                                                                                                                                                                                      | Done   | S      | Med    | §2, §3                                                                                                                                                                                                                    |
| Shipped 2026-06-06: Add "run now / dry-run" retention action surfacing matched/affected counts                                                                                                                                                                                                                                             | Done   | S      | Med    | §2                                                                                                                                                                                                                        |
| Shipped 2026-06-06: Cache the overview widget stats via the declared `privacy-center` tag + model-event invalidation                                                                                                                                                                                                                       | Done   | M      | Med    | §2, §4                                                                                                                                                                                                                    |
| Done/Shipped: Add Filament interaction tests (render + fulfil stamps `fulfilled_at`) and a scheduled-job-runs test. Evidence now covers privacy-request workflow action mounting/stamping, retention dry-run/run-now table actions, and scheduled retention command/manifest alignment.                                                    | Done   | M      | Med    | §4                                                                                                                                                                                                                        |
| Done/Shipped: Build public cookie consent banner / preference centre (cache-safe, theme-aware) writing to the ledger + cache-safety test                                                                                                                                                                                                   | Done   | L      | High   | §3 — `/privacy/consent` records granular category decisions via `RecordConsentAction`, the banner partial links to the preference center, and HTTP coverage proves anonymous public output avoids admin/internal leakage. |
| Cross-package `ContributesSubjectData` / `ErasesSubjectData` contract + registry (the suite differentiator)                                                                                                                                                                                                                                | Later  | L      | High   | §3, §5                                                                                                                                                                                                                    |
| Public DSAR intake form with email verification populating `verified_at`                                                                                                                                                                                                                                                                   | Later  | L      | High   | §3                                                                                                                                                                                                                        |
| Consent expiry/refresh job (granted→expired) + policy publish/retire workflow + signed consent-proof export                                                                                                                                                                                                                                | Later  | M      | Med    | §3                                                                                                                                                                                                                        |
| Geo/jurisdiction-aware policy & category selection (GDPR vs CCPA)                                                                                                                                                                                                                                                                          | Later  | L      | Med    | §3, §5                                                                                                                                                                                                                    |