From caa5b04096bfb04e1f60e096e6e0fcbe9c18fcda Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:10:12 -0500 Subject: [PATCH] docs: custody pointer and consent schema contract (S2 contract 7) --- docs/requirements/custody-schema.md | 171 ++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/requirements/custody-schema.md diff --git a/docs/requirements/custody-schema.md b/docs/requirements/custody-schema.md new file mode 100644 index 00000000..a36d446b --- /dev/null +++ b/docs/requirements/custody-schema.md @@ -0,0 +1,171 @@ +# 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.