برنامهریزی، تحلیل و عملیات پلتفرم · فصل ۱ — Message، Identity، Contact، Opportunity و Obligation · 192
Unified Inbox and Channel-Normalized Messages
Design a channel-normalization contract that preserves raw-source references while mapping heterogeneous messages into a stable envelope with explicit channel, external message ID, occurrence/receipt time, participant handles, content and attachment references, normalization version, provenance pointer, dedupe key and typed parse state; normalization must not silently perform identity merge. میتواند برای «Unified Inbox و Channel-normalized Messages» model، signal و guardrail لازم را در یک workflow محدود تعریف و ارزیابی کند.
Lesson summary
Design a channel-normalization contract that preserves raw-source references while mapping heterogeneous messages into a stable envelope with explicit channel, external message ID, occurrence/receipt time, participant handles, content and attachment references, normalization version, provenance pointer, dedupe key and typed parse state; normalization must not silently perform identity merge.
میتواند برای «Unified Inbox و Channel-normalized Messages» model، signal و guardrail لازم را در یک workflow محدود تعریف و ارزیابی کند.
This lesson produces
میتواند برای «Unified Inbox و Channel-normalized Messages» model، signal و guardrail لازم را در یک workflow محدود تعریف و ارزیابی کند.
Who it is for
Foundation
Use a guided lesson-specific evidence matrix and one worked negative case.
Repair
Re-run only the failed owned dimension with a smaller deterministic synthetic fixture.
Experienced
Start from mixed authority/policy/evidence records; require explicit trade-offs and provenance.
Prerequisites
1. The real problem: a unified inbox is unsafe if it loses source truth
An email, web form, and chat event may discuss the same purchase while carrying incompatible identifiers, timestamps, thread semantics, and attachments. This lesson builds an auditable Normalized Message, not a merged person identity. Every record must remain traceable to its raw event, and every dedupe or parse decision must be explainable from visible evidence.
2. Lesson ownership boundary
PLN-001 owns channel normalization only: convert a raw channel event into a stable envelope while preserving provenance, dedupe evidence, and typed parse state. Deciding whether two handles belong to one person is PLN-002. Preserve participant_handles; do not manufacture a canonical_person_id here.
3. What CloudEvents teaches us
CloudEvents defines a vendor-neutral event format with four required context attributes: id, source, specversion, and type. It also makes source + id a useful duplicate boundary inside the event context. We borrow the envelope idea without claiming that MAKNOW inbox semantics are CloudEvents requirements.
4. Provenance must survive UI changes
W3C PROV provides a model for representing and exchanging provenance across systems. Here, provenance_pointer identifies the raw event, adapter, and normalization version that produced an envelope. A UI redesign must not erase that lineage.
5. Start the stable envelope with required fields
The practical field set for this lesson includes channel, raw_message_ref, external_message_id, source_system, occurred_at, received_at, participant_handles, body_ref, attachment_refs, normalization_version, provenance_pointer, dedupe_key, parse_state, unsupported_fields, and ingest_decision. thread_hint may come from different sources and must never be fabricated.
6. occurred_at is not received_at
occurred_at is when the source says the event happened; received_at is when the inbox received it. A retry can have a new received_at while still being the same underlying event. Dropping either timestamp weakens delay, replay, and ordering analysis.
7. Preserve both raw reference and external message ID
external_message_id comes from the provider or source system. raw_message_ref points to the retrievable raw payload or snapshot. They are complementary: the external ID helps correlation and dedupe, while the raw reference supports audit, replay, and future re-normalization.
8. Make the dedupe key channel- and context-scoped
A useful dedupe key must preserve source context. The lab uses workspace_scope + channel + source_system + external_message_id. That formula is a synthetic lab policy, not universal production guidance. The principle is that an equal external ID in a different workspace must not collide blindly with the first record.
9. Do not confuse replay with a new message
If the provider retries the same event with a later received_at, the envelope may be rebuilt, but dedupe_state=REPLAY should route the delivery action to SUPPRESS_REPLAY. A later receipt timestamp alone does not prove a distinct message.
10. Thread hint is evidence, not canonical truth
Some channels provide a native thread ID; email may provide only reply headers; a form may have no thread semantics. thread_hint must carry its source. If the provider thread ID disappears while a valid reply header remains, derive the hint from that header rather than inventing a provider identifier.
11. Parse state must preserve partial and unsupported evidence
An attachment with an unknown content type should not automatically erase a valid message body. parse_state=PARTIAL_UNSUPPORTED and unsupported_fields let the message remain normalized while the unsupported part is visible for later handling. Missing critical fields such as the raw reference or provenance can instead force QUARANTINED.
12. Normalization is not identity merge authorization
Seeing email, chat, and form records together does not prove their handles represent one person. If an adapter injects a field such as resolved_person_id or a definitive merge instruction, the normalizer must recognize that as an out-of-scope assertion rather than silently canonizing it.
13. Worked example: email with an attachment
Suppose an email arrives with external ID m-204, a stable raw reference, occurrence and receipt times, participant handles, a body reference, and an attachment reference. With complete provenance and a supported content type, the envelope is NORMALIZED, its dedupe key is derived from source context, and identity remains unresolved.
14. Deterministic failure probes
Run four probes: an exact event replay with a later received_at must be recognized as replay; missing provider thread ID with a valid reply header must not create a fake ID; the same external ID in another workspace must not collide; and an unknown attachment type must produce a typed unsupported state. Missing raw reference or provenance must leave the happy path.
15. Transfer: provider thread ID removed, reply header retained
In the transfer case, the provider no longer emits a thread ID, but the email reply header and all source references remain. Rebuild the envelope from changed evidence: derive thread_hint with an explicit source, recompute the dedupe key from the actual context, and keep identity unresolved. Never copy the baseline result as an oracle.
Assessment
There is no answer key in this lesson pack; answer it yourself and mark honestly. "Needs review" sends the lesson to your review queue.
F1 retrieval
F2 diagnosis
S1 application
S2 diagnosis
S3 decision
S4 transfer
Assessment rubric
| Dimension | Sufficient evidence | Blocking failure |
|---|---|---|
| Boundary | Channel normalization is owned; person identity stays unresolved | Normalization silently merges identity |
| Source fidelity | Raw reference, external ID, source and provenance remain visible | Canonical envelope destroys source traceability |
| Dedupe/replay | Dedupe key is context-scoped and replay is explicit | Later received_at is treated as a new message without evidence |
| Parse uncertainty | Unsupported/partial states remain typed | Unknown attachment or missing evidence is coerced into success |
| Transfer | Thread hint and dedupe evidence are recomputed from changed input | Baseline result or provider thread ID is copied/fabricated |
References & freshness
Case studies
Lesson visuals
Lab files
Use only the supplied synthetic records. Implement normalize(case) in starter.py. Preserve source references and provenance, derive a context-scoped dedupe key, keep replay/unsupported states explicit, and never perform cross-channel identity merge.
Run python run_checks.py. The learner checker validates output shape and boundary safety only; expected labels and reference logic remain outside learner-publishable paths.