Skip to content

Privacy Center — Improvement & Growth Plan

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

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.

  • 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 anonymizeApplyRetentionRuleAction::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

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.
  • 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.

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.

ItemBucketEffortImpactSection 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.jsonDoneSHigh§3, §4
Shipped 2026-06-05: Make PrivacyCenterHealthCheck verify tables/models/provider discoverable. Evidence: src/Health/PrivacyCenterHealthCheck.php, tests/Unit/PrivacyCenterHealthCheckTest.phpDoneSHigh§4
Shipped 2026-06-05: Fix insights mirror subject resolution so mirrored consent is exportable/erasable. Evidence: src/Actions/RecordConsentAction.php; tests/Feature/PrivacyCenterFoundationTest.phpDoneSHigh§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.phpDoneMHigh§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.phpDoneSHigh§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.phpDoneSMed§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.phpDoneSMed§2, §4
Add table filters (status/overdue/category/decision/jurisdiction) to record resourcesDoneSMed§2, §3
Shipped 2026-06-06: Add “run now / dry-run” retention action surfacing matched/affected countsDoneSMed§2
Shipped 2026-06-06: Cache the overview widget stats via the declared privacy-center tag + model-event invalidationDoneMMed§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.DoneMMed§4
Done/Shipped: Build public cookie consent banner / preference centre (cache-safe, theme-aware) writing to the ledger + cache-safety testDoneLHigh§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)LaterLHigh§3, §5
Public DSAR intake form with email verification populating verified_atLaterLHigh§3
Consent expiry/refresh job (granted→expired) + policy publish/retire workflow + signed consent-proof exportLaterMMed§3
Geo/jurisdiction-aware policy & category selection (GDPR vs CCPA)LaterLMed§3, §5