12 — Customer-care / Email / Message — Forward Scope (design seam only)
12 — Customer-care / Email / Message — Forward Scope (design seam only)
DESIGN-ONLY SEAM. Cites Điều 45 §13.1, §13.2, §13.3. No concrete customer/email/message schema. No PII handling concrete. No body storage location chosen.
§1. Goal
Carve forward-compatible seams in the queue substrate so that, when customer-care / email / messaging domains land (per §13.2 forward-compatibility clause), they can be plugged in without redesign — and without ever violating §4 (signal-not-data) or §14 (NVSZ).
Specifically:
- Define the 7 job kinds that a customer interaction will likely need.
- Specify where the message body, conversation log, and attachments live (never in queue).
- Specify how PII boundary holds in
safe_payloadCHECK. - Specify approval-before-send invariant.
- Specify escalation seam.
This document does not define customer_* / email_* tables. Those belong in their own sub-design pack when product scope is decided.
§2. Current state
| Concern | Live |
|---|---|
| Customer table | ❌ none in PG |
| Conversation log | ❌ none in PG |
| Email ingest | ❌ no IMAP/SMTP pipeline |
| Inbound message webhook | ❌ none |
event_domain vocab room |
available: event_type_registry allows new (domain, type, stream, lane) rows via §6.4 ratification |
§3. Proposed seven seam-shaped job kinds
forward_job_kinds:
- customer_message_inbound # one inbound message captured; payload_ref → conversation store
- customer_message_classify # categorise (intent, priority, language)
- customer_message_draft # AI/agent writes a draft reply
- customer_message_approve # human approval step (review queue)
- customer_message_send # post the reply via channel
- customer_message_followup # scheduled follow-up touch
- customer_message_escalate # routes to higher-tier agency
Each is a job_queue.job_kind value. No new substrate beyond what DP2 already provides.
§4. Payload boundary (signal-not-data, mandatory)
NEVER in safe_payload: ALWAYS via payload_ref:
message body (any length) conversation_id (uuid)
message subject (treat as restricted) message_id (uuid)
customer name case_id
email address customer_ref (canonical_address)
phone number attachment uri (blob storage)
attachment binary
PII of any kind
safe_payload may carry:
language (BCP-47)
intent_code (from classify)
priority (smallint)
channel_kind ('email','sms','webchat',...)
routing_hint (agency:..., role:...)
confidence (numeric)
retry_reason_code
Existing safe_payload CHECK on event_outbox already forbids body|content|raw|vector|embedding|secret|token|password|ssn|personal_data. DP2 mirrors this CHECK to job_queue. Customer payload classes that could contain message text MUST go via payload_ref only.
§5. Storage seam (future)
When the customer-care domain lands, the message store will likely be:
customer_conversationtable (one per conversation thread).customer_messagetable (one per inbound/outbound message; body column subject to encryption-at-rest).- Attachments in blob storage;
attachment_refURI in PG.
These tables do NOT live in the queue substrate. The queue substrate carries refs to them via payload_ref. Cross-references are FK (customer_message_id foreign key referenced by job_queue.payload_ref typed as text, validated by trigger only at execution-time inside the executor).
§6. Approval seam (customer_message_send invariant)
Per §13.2 rule 1 + forward-compat principle, customer_message_send MUST NOT execute without an approved predecessor unless explicitly dispensed:
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY
claim filter for customer_message_send:
- require parent_job_id refers to a customer_message_approve in 'succeeded' status
- OR require dot_config.queue.customer.send_without_approve.enabled = true
AND a workflow_admin signed-off the customer_id (auditable trail)
Default: approve-required. Dispensation requires explicit Council-approved flag + per-customer signoff.
§7. Escalation seam
customer_message_escalate flips the target_ref to a higher-tier recipient (agency role) and re-enqueues the conversation handling:
escalation_pattern:
trigger:
- SLA breach (deadline_at passed)
- explicit operator decision
- classifier confidence below threshold
effect:
- INSERT job_queue (job_kind='customer_message_escalate',
target_ref=<higher-tier role>,
correlation_id=<conversation_id>,
parent_job_id=<originating job>)
- emit event customer.message.escalated (event_type to register)
§8. Lifecycle / status
All seven kinds use the §6.7 9-state machine via DP2 substrate. No custom lifecycle.
§9. Indexes / performance
Same partial indexes as DP2 (status='queued', lease_active, dead_letter). One additional candidate index when adoption begins:
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY
PARTIAL INDEX ix_job_queue_customer_followup
ON job_queue(process_after, job_kind)
WHERE job_kind='customer_message_followup' AND status IN ('queued','retry_waiting');
For SLA monitoring of customer interactions.
§10. Security / governance
- PII boundary is enforced at
safe_payloadCHECK; producers refused at INSERT if PII leaks. - Customer message store would have its own row-level security per agency.
customer_message_sendis the only externally-visible job; gated by approve invariant + executor whitelist.- Audit trail via
dot_iu_command_run+event_outboxevents.
§11. Rollback / disable
dot_config.queue.customer.enabled = false→ enqueue refused.- Per-kind:
dot_config.queue.customer.<kind>.enabledoverrides. - Send dispensation: independent flag (default off).
§12. Healthcheck / observability
v_queue_health(DP4) shows backlog percustomer_message_*job_kind.- Optional
v_customer_sla_pressureview aggregatesdeadline_atproximity. - DLQ rows by
customer_message_sendindicate channel outage.
§13. Compatibility with Điều 45 v1.0
| Clause | Compliance |
|---|---|
| §13.1 no customer substrate at law enactment | ✅ none added here |
| §13.2 forward-compat clause | ✅ vocab seam reserved; payload_ref pattern preserved |
| §13.3 not Phase 1 of Điều 45 | ✅ this design pack does not implement; it carves the seam |
| §4 signal-not-data | ✅ explicit PII boundary table in §4 |
| §6.4 inclusion criteria | ✅ each customer.* event_type will require ratification before live emission |
| §11.5 executor whitelist | ✅ all customer jobs assigned to whitelisted executors |
| §13.4 MOT clause | ✅ customer flows may be MOT-orchestrated; MOT remains non-executor |
§14. Implementation prerequisites (future Phase 7)
- DP2 (
job_queue) in production. - Customer message store (separate design pack).
- Channel adapters (Hermes / external_worker for SMTP/IMAP/webchat).
- Council ratification of
customer.*event_type vocab additions. - Privacy / retention policy ratification (separate pack).
§15. Open questions
| # | Question | Routed to |
|---|---|---|
| CM-Q1 | Approve the 7 seam-job_kinds vocabulary at Phase 1 (vocab reservation only)? | Council |
| CM-Q2 | Approve approve-required default + dispensation flag? | Council + Legal |
| CM-Q3 | Should customer_message_inbound create an event_outbox event before its job? |
Council |
| CM-Q4 | SLA deadline modelling: deadline_at on job_queue sufficient, or separate customer_sla table? |
Council |
| CM-Q5 | Encryption at rest for body — pgcrypto vs pg_tle vs application-layer? | Council |
| CM-Q6 | Multi-channel routing (email vs SMS vs webchat) — single executor per channel, or routing layer? | Council |
Customer/email/message forward scope. Seam only. No mutation. Authored 2026-05-26.