# Decision brief — DR-15 and DR-48

**For:** Sky · **Prepared:** 21 August 2026 · **Blocks:** M2 prompt C, all of M3, all of M7
**Decide together.** DR-31 says so explicitly, and DR-48 resolves the opposite way to DR-15's own
recommendation. Answering them in sequence produces a table that gets renamed in M7.

> **Decided — 21 August 2026.** Sales activities belong to Commerce. Calls, meetings, emails, and
> follow-up commitments are actions performed as part of the commercial relationship and therefore
> use `com_activities` and `com_activity_links`. They may reference Customer & Location records
> through typed cross-domain foreign keys. Other domains own their respective operational actions
> and history; unified record timelines are composed through reporting read models.
>
> This supersedes the `cl_` recommendation in §3 below and overrides DR-48 in the other direction —
> see §3a for the reasoning, §3b for DR-48's full pipeline-stage breakdown, and §9 for why the
> original tiebreak test undersold `com_`. §10 carries the same reasoning into a decision for
> DR-54, closed alongside DR-15 and DR-48: no duplicate activity per captured message, and the
> `email` activity type is renamed `email_task` to keep it distinct from a captured message.

---

## 1. What is actually being decided

One question, asked twice: **which domain prefix owns the activity family** — activities, the
four-way activity link table, notes, tags, and (via DR-48) logged email messages and their links.

The candidates are `com_` (CRM/Commerce) and `cl_` (Customer & Location). A third option, a new
`act_` prefix, is an architecture change: the prefix registry is locked and the delivery plan
cannot grant one.

**One part is already binding and is not up for decision.** The four-way many-to-many link table
stays a real link table. It is not flattened into a polymorphic `entity_type` / `entity_id` pair,
and not into a single nullable column. DR-15 calls this "one of the firmest structural findings"
and says it binds *whichever domain wins*. The prototype's `notes` table is exactly the flattened
shape being rejected, and its four `task_*` link tables are exactly the shape being kept.

---

## 2. Why it is not already answered

DR-15's tiebreak is the architecture's standard one: **the domain that performs the write owns the
table**, measured by counting activities by link target.

- On the prototype's data, that count is expected to favour **`com_`** — most activity volume is
  opportunity- and quote-side. This is why DR-15 recommends `com_activities`.
- DR-48 then points out the count is measured on a system **with no email logging in it**. Once
  M7 lands, captured mail attaches overwhelmingly to **contacts**, and contacts are `cl_`. DR-48
  calls this dominant "by a wide margin" and recommends `cl_`, explicitly *as an override* of
  DR-15 rather than as agreement with it.

So the two entries disagree because they are measuring the same thing at two different dates.

---

## 3. Original recommendation — `cl_`, recorded as an override of DR-15 (superseded, see §3a)

Reasoning, in order of weight:

1. **Decide for the end state, not the interim.** M7 is in phase 1 and ships before cutover. A
   decision made on pre-email volumes has to be reversed inside the same phase. DR-48 spells out
   the cost of getting it wrong: rename every `cl_email_*` table to `com_email_*`, or the reverse.
   Cheap on paper, but it lands after M3 has built against the original prefix.
2. **Minimise automatic cross-prefix traffic, not screen-level traffic.** Either answer makes some
   code cross a boundary — an opportunity screen creating an activity, or a contact screen doing
   the same. Those are human-rate writes through a service client, and they are fine. The email
   capture worker is different: it is high-volume, continuous, and machine-driven. Put it where it
   does not cross. That is `cl_`.
3. **DR-48 must not be resolved separately.** Messages and activities have to share a prefix so one
   worker writes both without crossing. Once you accept that, DR-48's evidence is DR-15's evidence,
   and it points at `cl_`.

**The honest counter-argument.** `cl_` becomes a large domain — companies, brands, contacts,
locations, activities, notes, tags, email. "Customer & Location" stops describing it, and the
name will mislead someone in six months. DR-15's own tiebreak, read strictly on today's data,
says `com_`. If you take that view, the answer is `com_` for everything including email, DR-48 is
overridden instead, and every `cl_email_*` name in the M7 prompt is renamed now rather than later.
Either answer is defensible. **What is not defensible is answering them separately.**

---

## 3a. Revised recommendation — `com_`, superseding §3

Calls, meetings, emails, and sales follow-ups are Commerce/CRM behaviours. Companies and contacts
are their subjects, but `cl_` does not perform the work — it owns who the customer is and where
they operate, not the actions taken toward them.

Reasoning, in order of weight:

1. **The capability responsible for the behaviour owns the table, not the record most often
   pointed at.** §2's tiebreak — count activities by link target — answers a different question
   than the one that matters. A contact receiving most activity links doesn't make Customer &
   Location the writer, just as a job referencing a location doesn't make the job a `cl_` record.
   Ownership follows who performs the behaviour, not who is most referenced by it. See §9.
2. **Activities are domain-specific; timelines are cross-domain.** There is no single universal
   activity table for the whole ConnectIQ platform. Commerce gets one activity model for the
   sales/customer-engagement workflow — `com_activities`, `com_activity_links`. If Operations later
   needs an "activity," it models the actual operational concept (a visit, a work log, a job
   update) inside `ops_`, rather than writing into `com_activities`. Unifying the *record*, not the
   *table*, is what a reporting read model is for.
3. **DR-48's question is resolved together with DR-15, both landing on `com_`.** Logged email
   messages are Commerce behaviour for the same reason activities are: `com_email_messages` and
   `com_email_message_links`. DR-48's original recommendation of `cl_` is what gets overridden
   here — not DR-15's. The override direction in §2/§3 is reversed by this decision.
4. **Typed cross-domain references stay exactly as DR-15 already required.** Typed links may
   reference `cl_companies`, `cl_contacts`, `com_opportunities`, and `com_quotes` — the four-way
   link table is unchanged in shape, only in prefix. Customer timelines read Commerce activity
   through a service client or an `rpt_` view; they do not need activity rows to live under `cl_`
   to display them.
5. **Operational domains keep their own history.** Job visits, maintenance actions, installation
   updates, support interactions, and so on remain distinct records in their owning domain. None of
   them become `com_activities` rows, and Commerce activities never become their record either.

**Revised DR-15 wording, for the register:**

> Decided — sales activities belong to Commerce. Calls, meetings, emails, and follow-up commitments
> are actions performed as part of the commercial relationship and therefore use `com_activities`
> and `com_activity_links`. They may reference Customer & Location records through typed
> cross-domain foreign keys. Other domains own their respective operational actions and history;
> unified record timelines are composed through reporting read models.

---

## 3b. DR-48, decided in full — ownership by pipeline stage, not just by table

DR-48 is not one table, it is a pipeline, and the `com_` decision only applies to the stage that
is Commerce behaviour. The other stages were never in dispute and stay where they already were:

| Table | Owns | Prefix |
|---|---|---|
| `plat_mail_accounts` | Mailbox connections, credentials, sync configuration | `plat_` — unchanged |
| `int_mail_messages_raw` | Raw provider payloads and sync state, before interpretation | `int_` — unchanged |
| `com_email_messages` | Interpreted CRM correspondence | `com_` — decided here |
| `com_email_message_links` | Links to companies, contacts, opportunities, and quotes | `com_` — decided here |
| `com_email_attachments` | Attachment metadata | `com_` — decided here |
| `com_email_templates` | Sales email templates | `com_` — decided here |

The reasoning is the same as DR-15's: an email may target a `cl_contact`, but it represents
commercial communication performed by the sales/CRM capability. **The recipient does not determine
ownership.** `plat_` and `int_` were never candidates for `cl_` or `com_` in the first place —
connection infrastructure and raw ingestion sit outside the domain layer entirely, which is why
this decision only touches the four `com_` rows above.

**DR-48 wording, for the register:**

> Decided — logged messages belong to Commerce. Connected-email infrastructure remains in `plat_`,
> and raw provider ingestion remains in `int_`. Once interpreted as business correspondence,
> messages, attachments, templates, and their record links are owned by `com_`. Customer and
> contact screens access correspondence through the Commerce service client or an authorised `rpt_`
> timeline view.

This explicitly overrides DR-48's original `cl_` recommendation. Its "contacts receive most links"
argument no longer applies, for the reason §9 gives in full: link volume is not the ownership rule;
business behaviour is.

---

## 4. The measurement, if you want it before deciding (moot — see §9)

DR-15 asks for one count. The prototype already holds it — `task_companies`, `task_contacts`,
`task_opportunities`, `task_quotes`, all four-way, all present in its Supabase project. Run against
the prototype database, as service role, read-only:

```sql
select 'company'     as target, count(*) from public.task_companies
union all select 'contact',     count(*) from public.task_contacts
union all select 'opportunity', count(*) from public.task_opportunities
union all select 'quote',       count(*) from public.task_quotes
order by 2 desc;
```

Read it knowing what it cannot tell you: it counts a world without email. If `cl_` targets
(company + contact) already win, the decision is settled outright. If `com_` targets win, the
question becomes whether M7's email volume flips it — and DR-48 has already argued that it does.

This count no longer decides anything (§9): it measures which record an activity most often points
at, not which capability performs the activity. It can still be run for reporting curiosity, but
it is not load-bearing for DR-15 or DR-48 anymore.

---

## 5. A sub-question the register leaves open — the shape for notes

DR-15 is described as owning "the polymorphic-link shape, **notes included**", but notes are not
the four-way case. A note attaches to **exactly one** record of any type, so the argument against
flattening does not apply to it in the same way. What does apply is rule 14: cross-prefix foreign
keys exist for referential integrity, `ON DELETE RESTRICT`. A polymorphic `entity_type` /
`entity_id` pair has no foreign key at all, in any direction.

**Recommended:** four nullable typed target columns with a check constraint enforcing exactly one
non-null — the same shape M7 already specifies for `email_message_links`. It keeps referential
integrity, it is consistent with the link table beside it, and it does not require a second
pattern to be learned. The prototype's `notes(entity_type, entity_id)` is not the model to copy.

---

## 6. Consequences of the decided answer (`com_`)

| | `cl_` (original §3, not taken) | `com_` (decided, §3a) |
|---|---|---|
| Activities | `cl_activities`, `cl_activity_links` | `com_activities`, `com_activity_links` |
| Notes and tags | `cl_notes`, `cl_tags` | `com_notes`, `com_tags` |
| Email (M7) | `cl_email_*` as written — no change | `com_email_messages`, `com_email_message_links` |
| Register | DR-48 stands; DR-15's recommendation overridden | **DR-15 stands (as revised in §3a); DR-48's original `cl_` recommendation is overridden instead** |
| Timeline on a contact | no prefix crossed | crosses to `com_` service client or `rpt_` view |
| Timeline on an opportunity | crosses to `cl_` service client | no prefix crossed |
| Email capture worker | no prefix crossed | crosses on every captured message, via service client |
| Operational activity (ops) | n/a | never `com_activities` — modeled as its own `ops_` record (visit, work log, job update) |

---

## 7. One inconsistency to fix in the plan either way (now resolved)

`M2-accounts.md` and the M2 tracker both name **`com_notes`** and **`com_tags`** as settled fact.
Section 14 of the delivery plan puts those same two tables in the `com_` group marked **"activity
domain open"**, governed by DR-15. The prompt reads as decided; the data model said it was not.

With `com_` decided, `M2-accounts.md` and the M2 tracker were already correct — no rename needed
for notes and tags. Section 14 of the delivery plan should have its "activity domain open" marker
on `com_notes`/`com_tags` cleared to closed, referencing this decision.

---

## 8. What I need from you (closed — decision recorded above)

1. ~~**`cl_` or `com_`** for the activity family — and confirmation that DR-48 rides with it.~~
   **`com_`, and DR-48 rides with it.**
2. ~~Whether you want the prototype count run first, or whether DR-48's argument settles it.~~
   **Moot — see §9, the count was never the right test.**
3. Sign-off on **typed nullable columns + check constraint** for notes, rather than a polymorphic
   pair. **Still open** — §3a does not resolve §5's notes-shape question; it only settles the
   prefix. Sign-off still needed.

With that, M2 prompt C is unblocked and M3's `DR-15` blocker clears. The M7 email table names
(`com_email_messages`, `com_email_message_links`) are final, not provisional — no rename pending.

---

## 9. Why the original tiebreak test was the wrong test

DR-15's original tiebreak — count activities by link target, let the busiest target's domain win —
conflates two different questions: *who is acted upon* and *who performs the action*. A contact
receiving the most activity links describes where the volume points, not who writes the row. The
same confusion would say a job belongs to `cl_` because it references a location most often, or
that an invoice belongs to `cl_` because it references a contact. Reference frequency is not an
ownership signal; it is a foreign-key popularity count.

The correct test is the one DR-15 stated in words but didn't apply: **the domain that performs the
write owns the table** — not the domain most frequently pointed at by the write. Calls, meetings,
emails, and follow-ups are Commerce behaviours regardless of whether they point at a contact, a
company, an opportunity, or a quote most often. That is why `com_` was right independent of the
count in §4, and why running that count — before or after M7's email volume lands — was never
going to settle the question correctly.

---

## 10. DR-54, decided alongside DR-15 and DR-48 — do not duplicate the captured message as an activity

DR-54 asks whether a captured email should also write a `com_activities` row, because M2's
last-contact-date is derived from activities and would otherwise go stale every time a rep emails
someone without logging a separate call or meeting. §3b's `com_` decision for email makes one of
the three original options clearly the right one, and settles it:

**Do not create a duplicate activity for every captured email.** A message is already a first-class
row in `com_email_messages`; writing a second row into `com_activities` for the same event is two
records of one thing, which is the failure mode DR-54's own recommendation already warned against.

**Derive last-contact-date from both sources directly:**

- completed `com_activities`, and
- sent or received `com_email_messages`.

Both tables are `com_` after this decision, so the derivation is a same-domain read, not a
cross-prefix one — §3a's decision is what makes this option cheap where it would not have been
under the original `cl_` recommendation for email.

**Rename the activity type.** With messages living in their own table, the `email` value on
`com_activities.type` no longer means "an email happened" — it now means *a planned or manual
email follow-up a rep chose to log as a task*, distinct from the captured message itself.
**Recommended: rename it to `email_task`.** The old name invites exactly the confusion DR-54
exists to resolve — a build session reading "email" on both an activity type and a message table
has no way to tell, from the name alone, which one a given row is.
