Privacy-Preserving CRM & Least-Data Customer Intelligence
The Adaptive Experience Architecture (AEA) avoids centralized Personally Identifiable Information (PII) honeypots. Traditional CRMs store customer names, emails, phones, and addresses indefinitely. AEA satisfies operator order overview and customer relationship needs through a 3-Layer Privacy-Preserving CRM Pattern (ADR-020).
In Plain English: Traditional customer management systems (CRMs) collect and hoard customer names, phone numbers, home addresses, and credit card histories in a central database forever. When that database gets hacked, everyone's private life is exposed. AEA uses a zero-PII, least-data approach: the store tracks shopping patterns with anonymous tokens, customer favorites stay encrypted on their own phones, and physical delivery addresses are automatically shredded from the database 14 days after flowers arrive.
1. The 3-Layer CRM Pattern
Layers stack from operator view (top) down to where raw PII may briefly live (bottom). Plain list (no blockquote orphans) so every layer wraps cleanly on a phone:
- Operator Insights — Aggregated intelligence · behavior buckets · order fulfillment. Reads pseudonymous projections from Layer 2 (never raw PII).
- Platform Pseudonymous Subject Intelligence — Opaque subject tokens (
subject_reference = HMAC(client_id)); transaction aggregates (order count, lifetime spend bands); occasion vector (e.g. 70% Birthday / Mother, 30% Anniversary); preferred channel (Companion AppvsWeb Browser). - Client-Side Edge Wallet (3A) — Companion on-device DB: full local order receipts; favorite recipient records; custom occasion reminders. Presents a zero-PII claim up into Layer 2.
- Ephemeral Fulfillment Vault (3B) — 14-day auto-shred: raw delivery street address; unlocked only during packing; KMS-encrypted and purged after TTL. Tokenized destination pointer up into Layer 2.
2. Core Pillars
- Pseudonymous Subject Profiles: Platform tables represent returning customers via deterministic salted hashes (
sub_9b179aea00e...). Repeat orders, spend bands ($50–$100), and occasion types are tracked without storing names or emails. - Client-Side Edge Wallet (Android Companion): Detailed transaction history and recipient labels (e.g. "Mom") stay on the user's physical device in platform-backed encrypted storage. After a confirmed order, the phone can later send back only an opaque product/order token — not a name or address. The save path is live. The Need-screen reorder control landed on
mainin !459 (#404). Treat write and tap as two facts; a saved receipt is not proof that the button had always been there. - 14-Day Ephemeral Address Shredding: Physical street addresses and delivery phone numbers exist solely in an isolated KMS-encrypted table with an automated 14-day database-level TTL purge.
3. Honest Implementation Status
Every capability is classified by its verified code and hardware probe status:
| Layer / Feature | Status | Implementation Evidence |
|---|---|---|
| Layer 1: Occasion Memory & Pull Reminders | Live on main / Tested | Migration 018_engagement_crm.sql. Order-triggered occasion capture records categorical event month/day and relation. Workspace projection exposes the reminders facet. Path B Need (#need-reminder) shows the soonest item on a fresh session — pull only, not ADR-019 push. Card reminder_text is AI-authored on the existing LiteLLM path when healthy (#425); timeout/error fail-closes to the deterministic template. |
| Layer 1: Reminder Outbox Dry-Run | Code on this slice (#428) | Migration 028_crm_reminder_outbox.sql. Upcoming occasion memory enqueues least-data status=dry_run / not_sent rows (categorical occasion/relation/days + AI or template copy). GET /api/v1/operator/reminder-outbox and /florist #reminder-outbox list counts without contact fields. Stub POST .../send returns not_implemented and never delivers. Parent #35 stays open for a live outbound channel. |
| Layer 1: Operator Engagement Aggregates | Code on this slice (#421 / #427 / #430) | GET /api/v1/operator/engagement and /florist #engagement show zero-PII counts: memory_count, unique_browsers, upcoming_within_days, subject_count, occasion / relation / event-month cohorts, and lifetime spend-band cohorts (band_0_50 … band_250_plus from completed-order subject_profile). GET /api/v1/operator/engagement/export?format=csv or json downloads the same allowlisted counts and categorical keys (fail-closed florist gate). No hashes, names, addresses, or subject list. Parent #36 stays open for ML and per-customer browse. |
| Layer 1: Pseudonymous Subject Profiles | Live on main / Tested | Migrations 024_operator_crm_subject_profile.sql and 026_crm_lifetime_spend.sql. Cumulative running spend bands (band_50_100, band_250_plus), order counter, preferred channel. 274 integration tests pass. |
| Layer 1: Privacy Lifecycle & Retention Purge | Live on main / Tested | Migration 025_crm_retention_indexes.sql. Customer erasure (DELETE /api/v1/crm/occasions / forget), 400-day annual retention purge job (purge_crm_retention.py), and idempotent deletions. |
| Layer 2: Client-Side Edge Wallet | *Save live / Need code on main (!459). Two Play facts (do not collapse them)* | Pure Kotlin on Android companion (ADR-020, FR-008 thin path; FR-008 stays Future in the workbook). Receipt write on Confirm probed. Need-screen one-tap reorder control is on main (#404) and shows only when a device-held receipt exists. Empty-wallet Need on Play Internal v8 (5 Sep 2026, ROG and A36) showed no CTA. Receipt-present tap on Play Internal v8 (9 Sep 2026, named ROG) is recorded on the companion ledger. Empty-wallet ABSENT was not re-probed on 9 Sep. Device serials stay on that page. The #404 vault note is a sideload walk, not Play-install honesty. |
| Layer 3: Ephemeral Fulfillment Shredding | Live on main / Tested | orchestration.ephemeral_fulfillment with 14-day expires_at. Automated database-level TTL purge via PsycopgCrmStore.purge_expired_fulfillment. |
| Layer 3: KMS Address Encryption Write Path | Sponsor-Gated (Unbuilt) | Database schema and retention shredder are in place. The live write path and AWS KMS Customer Managed Key (CMK) provisioning remain sponsor-gated for key lifecycle governance. |
4. Pull Reminders (Not Outbound AI Push)
Reminders on the Adaptive Workspace are strictly in-session pull signals:
- They trigger solely when a shopper initiates a session and the session's browser hash matches a previously recorded delivery anniversary.
- Path B Need (
#need-reminder) renders the soonest item when Need is still fresh (no chat, no occasion). Shop this occasion → posts a categorical conversation message. - Card copy may be AI-authored from categorical fields only (
occasion_type,recipient_relation,days_until_event) on the same LiteLLM path as intent. Provider timeout or invalid output fail-closes to the deterministic template so the card still works. - No unsolicited push notifications, marketing emails, SMS blasts, or third-party ad retargeting pixels. Dry-run outbox (#428) is operator-visible and never delivers. A live outbound channel remains leftover on parent #35.
- The reminder payload carries least-data fields only (
occasion_type,days_until_event,reminder_text,recipient_relation) without customer names or delivery addresses.
5. Related Documentation & Architecture Decisions
- Mobile Companion App — Client-side edge wallet and local encryption.
- System Stack — How domain services and databases connect securely.
- Architecture Blueprint — Core formula and the 6 outer harness layers.
- Architecture Glossary — Plain-English definitions of least-data terms.
- Framework Home — Return to the overview.
- Repository ADR references:
ADR-020(Privacy CRM),ADR-013(Confirmation-Driven),NFR-017(Zero-PII Perimeter), andFR-008(Reorder Hint).