docs: custody pointer and consent schema contract (S2 contract 7)
ci/woodpecker/pr/ci Pipeline is running
ci/woodpecker/pr/ci Pipeline is running
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user