> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notes

> After this ships, what an agent learned on a call survives the call.

## Context

The smallest feature in the module and the one people use most. A note is where the knowledge that
does not fit any field goes: "prefers to be called after 6pm", "complained about the March
delivery", "owns the franchise on Avenida Providencia".

It is also the least predictable personal data in the platform, because it is free text a human
wrote about an identified person. That drives two rules: notes are **audit-logged like any state
change**, and they are **deleted on erasure with no anonymized remnant** — unlike a ledger row, a
note has no accounting value that outlives the person it describes.

## Scope *(normative)*

* `crm.notes` on a contact, with a typed author.
* Rich text, bounded in length.
* Pinning a note to the top of a contact.
* Editing with a preserved history of edits.
* Full-text search across a tenant's notes.

## Non-scope *(normative)*

* Attachments — F1b. Files bring virus scanning, storage quotas and their own erasure path.
* Mentions and internal notifications — F1b.
* Notes on anything other than a contact. If an activity needs a note, it has a description field.
* Templates or canned responses. That is a ticketing feature.

## Behaviour *(normative)*

1. A note belongs to exactly one contact and records a **typed author** — user, api\_key or system.
   A note without an author is a defect: the value is knowing who observed it.
2. Content is rich text, sanitized on write. Stored HTML is escaped on render; a note is not a
   script injection vector into a support agent's browser.
3. Length is bounded at a documented maximum. A note is not a document store.
4. Editing preserves the **previous versions**, visible to anyone who can read the note. A note
   quietly rewritten after an incident is the failure this prevents.
5. Deletion is a **soft delete** and audit-logged. Erasure of the contact is a **hard delete** of
   the note and its history (DEC-J3).
6. Pinning is per contact with a bounded number of pins, so pinning stays meaningful.
7. Search is scoped to the tenant and to the reader's permissions.
8. Every mutation writes to `core.audit_log`.

## Data *(normative)*

| Table       | Key invariants                                                                                                                                                                                 |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `crm.notes` | `tenant_id`, FK `contact_id`; `author_type`, `author_id`; `content` sanitized, bounded; `is_pinned`; `edit_history` JSONB append-only; `deleted_at` nullable; full-text index scoped by tenant |

## API *(normative)*

| Endpoint                           | Class      | Permission         | Budget    |
| ---------------------------------- | ---------- | ------------------ | --------- |
| `GET /v1/crm/contacts/{id}/notes`  | Management | `crm.notes.read`   | p95 \<1 s |
| `POST /v1/crm/contacts/{id}/notes` | Management | `crm.notes.create` | p95 \<1 s |
| `PATCH /v1/crm/notes/{id}`         | Management | `crm.notes.update` | p95 \<1 s |
| `DELETE /v1/crm/notes/{id}`        | Management | `crm.notes.delete` | p95 \<1 s |
| `GET /v1/crm/notes/search`         | Management | `crm.notes.read`   | p95 \<1 s |

## Events *(normative)*

`crm.note.created`. Not offered as an outgoing webhook: note content is the least predictable
personal data we hold, and pushing it to an arbitrary endpoint by default is not a decision to make
on a tenant's behalf.

## Acceptance criteria *(normative)*

1. A note records a typed author, and creation without one is impossible.
2. HTML in content is sanitized on write and escaped on render — proven with an injection fixture.
3. Editing preserves the previous version and both are readable.
4. Soft delete hides the note and keeps it recoverable; erasure of the contact removes it entirely,
   including its edit history.
5. Search returns only the tenant's own notes, and only those the reader may see.
6. Pin count is bounded per contact.
7. Every mutation writes an audit row.
8. **Negative:** no note references a contact of another tenant, and none is reachable without
   `crm.notes.read`.

## Execution

Single slice, synchronous command. Full-text search uses Postgres `tsvector` scoped by tenant.

## Open questions

| # | Question                                                                                                                                    | Decides | By              |
| - | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- |
| 1 | Can a note be edited at all, or only appended to? Editing is friendlier; append-only is more defensible in a dispute. (PRD open question 1) | Daniel  | before approval |
| 2 | Maximum note length?                                                                                                                        | Daniel  | before approval |

## Changelog

| Version | Date       | Change        | Why | Author                 |
| ------- | ---------- | ------------- | --- | ---------------------- |
| 0.1.0   | 2026-08-17 | Initial draft | —   | daniel + claude-opus-5 |

## Delivery record

*Not implemented yet.*
