feat(consents): auto-grant data:<provider> on connect; remove agent: consents (ADR-0015)
- integrations.ts: grant data:<provider> on OAuth callback, revoke on disconnect - Backfill migration: INSERT OR IGNORE data:<provider> for all active tokens - Agent manifests: drop agent:<id> from required_consents (momentum, time-of-day, overdue-task, recent-patterns, health-vitals) — per-agent control is a preference - eligibility.ts: update comment to reflect data:-only consent model - test_manifest.py: assert no agent: consents remain in any manifest - migrations.test.ts: backfill idempotency tests for issue #127 - Dockerfile.api: drop --offline flag (fixes ERR_PNPM_NO_OFFLINE_META) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
44
docs/adr/0015-data-source-consents.md
Normal file
44
docs/adr/0015-data-source-consents.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ADR-0015 — Data-source consents only; drop per-agent consent gate
|
||||
|
||||
**Date:** 2026-05-11
|
||||
**Status:** Accepted
|
||||
**Supersedes:** ADR-0014 §3 (consent model)
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0014 introduced `required_consents` on agent manifests. In practice two
|
||||
unrelated concepts were mixed into that field:
|
||||
|
||||
- `data:<source>` — which data source the agent reads.
|
||||
- `agent:<id>` — whether the user opted into this specific agent.
|
||||
|
||||
No UI ever granted `agent:<id>` consents, so the eligibility filter at
|
||||
`services/api/src/profile/eligibility.ts` dropped every agent for every real
|
||||
user. The symptom was confirmed by MLflow trace
|
||||
`tr-591449ea8a72af8e81b6a585234a86ab`: user `ODGp4Gkr7JWemMsqcMLMn` had five
|
||||
fresh `agent_outputs` rows but the orchestrator received `agent_ids: []`.
|
||||
|
||||
## Decision
|
||||
|
||||
Collapse to a single consent dimension: **data source**.
|
||||
|
||||
1. `required_consents` entries must all start with `data:`. Agent manifests no
|
||||
longer list `agent:<id>` entries.
|
||||
2. Connecting a data source via the OAuth flow automatically grants
|
||||
`data:<provider>` in `user_consents`. Disconnecting sets `revoked_at`.
|
||||
3. `data:core` continues to be auto-granted on signup.
|
||||
4. Per-agent control becomes a **preference** (`user_preferences[scope='agent:<id>', key='enabled']`), not a consent. The eligibility filter already honours this — the only change is removing the `agent:*` consent check that was always failing.
|
||||
5. Eligibility rule (final): an agent is eligible iff every `data:*` it
|
||||
declares is granted and not revoked, no active context is in
|
||||
`silenced_in_contexts`, and the `enabled` preference is not `false`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Agents that only require `data:core` (time-of-day, momentum, recent-patterns)
|
||||
become eligible immediately after signup.
|
||||
- Agents requiring `data:todoist` or `data:google-health` become eligible as
|
||||
soon as the user connects the integration — no extra consent step.
|
||||
- A backfill migration grants `data:<provider>` for every existing active
|
||||
`integration_tokens` row, unblocking users who connected before this change.
|
||||
- `ml/agents/tests/test_manifest.py` asserts all `required_consents` start
|
||||
with `data:`, preventing regression.
|
||||
Reference in New Issue
Block a user