Files
stack/docs/requirements/custody-schema.md
T
2026-08-26 19:10:12 -05:00

172 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 17 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.