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:
2026-05-12 15:09:58 +00:00
parent 34925310cf
commit 772bb6e194
11 changed files with 124 additions and 9 deletions

View File

@@ -40,7 +40,7 @@ MANIFEST = AgentManifest(
},
},
context_schema=["google-health.steps", "google-health.sleep", "google-health.activity", "google-health.heart_rate"],
required_consents=["data:core", "data:google-health", "agent:health-vitals"],
required_consents=["data:core", "data:google-health"],
output_contract={"type": "snippet", "format": "free_text"},
ttl_sec=1800, # refresh every 30 min — health data changes during the day
silenced_in_contexts=[],

View File

@@ -121,7 +121,7 @@ MANIFEST = AgentManifest(
},
},
context_schema=["profile.features"],
required_consents=["data:core", "agent:momentum"],
required_consents=["data:core"],
output_contract={"type": "snippet", "format": "free_text"},
ttl_sec=21_600,
inferred_params=[

View File

@@ -70,7 +70,7 @@ MANIFEST = AgentManifest(
},
},
context_schema=["todoist.tasks"],
required_consents=["data:core", "data:todoist", "agent:overdue-task"],
required_consents=["data:core", "data:todoist"],
output_contract={"type": "snippet", "format": "free_text"},
ttl_sec=3600,
silenced_in_contexts=["vacation"],

View File

@@ -131,7 +131,7 @@ MANIFEST = AgentManifest(
},
},
context_schema=["tip_feedback", "profile.features"],
required_consents=["data:core", "agent:recent-patterns"],
required_consents=["data:core"],
output_contract={"type": "snippet", "format": "free_text"},
ttl_sec=86_400,
inferred_params=[

View File

@@ -45,6 +45,7 @@ def test_manifest_required_fields(agent_id: str):
assert isinstance(m.pref_schema, dict) and m.pref_schema.get("type") == "object"
assert isinstance(m.required_consents, list) and m.required_consents
assert "data:core" in m.required_consents, "every agent should require data:core"
assert all(c.startswith("data:") for c in m.required_consents), "only data: consents allowed; agent: consents have been removed"
assert m.ttl_sec == get_agent(agent_id).ttl_seconds, "ttl divergence"

View File

@@ -126,7 +126,7 @@ MANIFEST = AgentManifest(
},
},
context_schema=["profile.features"],
required_consents=["data:core", "agent:time-of-day"],
required_consents=["data:core"],
output_contract={"type": "snippet", "format": "free_text"},
ttl_sec=900,
inferred_params=[