# Data Custody Contract — Pointer and Consent Schema (D14) Status: DRAFT — awaiting ratification (webui-audit S2, contract 7 of 9). Authority: PRD D14/D6 (Part I §7) — sensitive profile categories live in the **user's own brain ONLY**; PostgreSQL holds structural data, consent records, and pointers — never the content; "user data does not leak" is enforced by architecture, not policy. The Standalone split is a MAY with a recommended-default (kept for conversion forward-compatibility, D3). PRD D4 (Part I §6) — profile answers feed `USER.md` and/or the user's data store subject to the custody rule; connectors carry granular agentic-access consent. PRD D6 — estate brains hold operational records; only product-relevant material migrates into repository docs. This contract binds the sensitive-category registry (§2), the custody placement rule (§3), the pointer schema (§4), the consent schema and its evaluation (§5), mode application (§6), and witnesses (§7). It defines schemas and placement; wizard step flow stays with contract 3, mode and conversion with contract 6 (`mode-conversion.md`), identity with `identity-lifecycle.md`, tool mapping with `tool-gateway-mapping.md`. ## 1. Definitions 1. **User brain**: the git-tracked per-user data store (in Standalone, the user-files region of the single mosaic-brain; in Enterprise, the user's own brain repository). 2. **Sensitive content**: any profile answer or derived text in a §2 sensitive category. 3. **Pointer**: a database record referencing sensitive content that lives in a user brain, carrying no content (§4). 4. **Consent record**: a database record granting a named grantee scope access to a category of a user's data (§5). ## 2. Sensitive-category registry 1. The registry is a closed, versioned list in the platform database; entries are added or reclassified by amendment to this contract, never ad hoc. 2. Initial registry, drawn from the D4 profile step and D14's "disabilities, family, communication style, and similar": | Category key | Covers | | --------------------- | ------------------------------------------------------------------- | | `disabilities` | disabilities including ADHD/autism/PDA/vision | | `family-social` | family, pets, friends | | `communication-style` | desired agent communication style, voice-matching interview product | | `personal-interests` | hobbies, likes/dislikes | | `connector-content` | email and drive content reached through user connectors | 3. **Fail-closed classification.** A profile category not in the registry is treated as sensitive until an amendment classifies it. Non-sensitive by classification (not by default) are structural fields the platform needs relationally: e.g. professional background/education summaries used for agent configuration MAY be classified non-sensitive by the ruling below; account identity fields (email, name, credentials) are identity-contract data, not profile custody data. ## 3. Custody placement rule 1. Sensitive content is written to the owning user's brain ONLY. PostgreSQL tables MUST NOT store sensitive content in any column — not as text, not as excerpts or previews, not as embeddings or other derived representations that reconstruct content. 2. The database MAY hold, about sensitive content: the pointer records of §4, the consent records of §5, and the category registry of §2. Nothing else. 3. Every write path for profile answers routes by category: sensitive → brain write + pointer upsert; non-sensitive → its declared platform table. The routing decision is made server-side from the registry, never by the client. 4. D6 boundary: operational records stay in estate brains and are linked, not migrated. This contract governs user-profile custody only and creates no new obligation on estate brains. ## 4. Pointer schema A pointer row carries exactly: 1. `id`, `user_id` (the owning user), `category_key` (§2 registry FK), `brain_ref` (an opaque locator — repository-relative path or key in the owning user's brain), `content_hash` (integrity check of the referenced content), `created_at`/`updated_at`, and audit linkage. 2. **Opacity.** `brain_ref` and every other pointer column MUST NOT embed content or content-derived text (no titles, snippets, or free-text descriptions). A locator is structural (category + path discipline), not descriptive. 3. One pointer per (user, category, brain_ref); pointers are deleted when their content is deleted (dangling pointers are repaired toward deletion, never toward re-creating content in the DB). ## 5. Consent schema and evaluation 1. A consent row carries exactly: `id`, `user_id` (the data subject), `grantee_scope` (a typed reference: an enrolled agent, a connector, or a platform feature — closed enum of grantee types), `category_key` (§2 FK), `state` (`granted` | `revoked`), `granted_at`/`revoked_at`, `actor` (who recorded the choice), and audit linkage. 2. **Default deny.** Absence of a `granted` consent row for (user, grantee scope, category) means no access. There are no implicit grants, no platform-admin bypass, and no mode in which default-deny is suspended. 3. **Granularity.** Consent is per grantee scope × category (the D4 "granular agentic-access consent"). A grant to one agent or connector confers nothing on another. 4. **Revocation.** Revocation is effective for every access evaluated after the revoking write commits; revoked rows are retained as history (state flip, not row deletion). 5. **Evaluation placement.** Access to sensitive content is mediated by the platform (Gateway/tooling) evaluating consent before any brain read on behalf of a grantee; the evaluation fails closed (`rbac-grant-model.md` §3.5 pattern). The user reading their own data is not a grantee and needs no consent row. 6. Consent records govern agentic/feature access to user data. They are distinct from hierarchy grants (contract 2) and confer no platform authorization. ## 6. Mode application 1. The §3–§5 schemas are mode-independent: Standalone and Enterprise use the same tables and the same routing rule. 2. In Standalone, D14 makes the physical split a MAY. This contract keeps it a MAY and binds the recommended default: a fresh v1 Standalone install routes sensitive content per §3 from the start, so the Enterprise conversion precondition (`mode-conversion.md` §4.2, brains) is already satisfied. An operator electing not to keep the split accepts the resulting conversion-time partitioning work; the election is recorded. 3. In Enterprise, the split is mandatory (D3 table); no-leakage between users is enforced by §3 placement plus §5 default-deny — there is no cross-user read path to sensitive content through the database, because the database has no content to serve. ## 7. Verification requirements Binding on the implementing PRs: 1. **No-content witness:** a column-allowlist assertion (contract 1 §6.3 style) that the pointer and consent tables' column sets are exactly §4.1/§5.1, and that no platform table outside the declared non-sensitive profile tables carries profile answer content. 2. **Routing witness:** a sensitive-category answer submitted through the profile surface results in a brain write plus a pointer row and zero content bytes in the database (asserted by content-hash presence in the brain and absence of the plaintext in any DB column); a non-sensitive answer lands in its declared table. 3. **Fail-closed classification witness:** an answer in an unregistered category routes as sensitive. 4. **Opacity witness:** pointer rows for seeded sensitive fixtures contain no fixture text in any column. 5. **Default-deny witness:** an agent grantee with no consent row is refused; with a `granted` row for category A only, access to category B is refused. 6. **Revocation witness:** after revocation commits, the next access evaluation refuses; the revoked row persists as history. 7. **Self-access witness:** the data subject reads their own content without consent rows. 8. **Deletion witness:** deleting sensitive content removes its pointer; no path re-materializes content into the database. ## Ruling request Ratify sections 1–7 as written, with one decision embedded: - Decision (§2): the initial sensitive-category registry is the five rows of §2.2, with fail-closed classification for anything unregistered, and with professional background/education classified **non-sensitive** (they exist to configure agents and reporting and are stored relationally). Alternative if rejected: classify background/education sensitive too — safe, but it moves data the platform legitimately queries into pointer-indirected storage and that cost should be chosen deliberately, not defaulted into.