refactor(ai): re-check every chokepoint through the one egress helper

The status chokepoint, the comprehensive briefing, reaction lines and
AI-composed nudges now ask `aiEgressRefusal` about exactly the chain they
are about to send to, instead of each pairing the operator-key check
with a surface receipt lookup of its own. The consent kinds come from the
capability table, so the wire and the published capability answer from
the same rule.

The document summary, its catch-up pass and the index backfill stop on a
closed switch or module before anything is picked, and leave "no
provider" and "consent needed" to the document pick, which re-checks the
wire for the provider it chose. A closed switch still only heals PENDING
to NONE; a withheld pick records UNAVAILABLE. The per-pick document
consent helper is removed, since nothing calls it any more.

The auto-read toggle mints the shared `AI_EXTRACTION_CONSENT_KIND`, and
the web grant helper goes back to minting the master grant only.

Refs #975
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 09:29:00 +02:00
1 parent f158f01f18
commit ce72fe226e
33 files changed
+628 -637

No files matched your search

+31 -9
View File
@@ -12772,6 +12772,7 @@ paths:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
description: Narrow to one consent kind. Omit for the full keyspace.
responses:
"200":
@@ -12783,8 +12784,9 @@ paths:
- $ref: "#/components/schemas/ConsentLatestForKindEnvelope"
- $ref: "#/components/schemas/ConsentLatestByKindEnvelope"
"400": &a22
description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`. Multi-issue envelope;
nothing was read or revoked. The consent family answers 400 here where the rest of the API answers 422."
description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`, `ai_extraction`.
Multi-issue envelope; nothing was read or revoked. The consent family answers 400 here where the rest of the
API answers 422."
content:
application/json:
schema:
@@ -12807,9 +12809,18 @@ paths:
each revocation is audit-logged.
Revoking `ai_full` does NOT switch off `documentsAutoAiRead`; that flag is written through PATCH
/api/auth/me/documents-auto-ai-read and turning it on mints a fresh receipt. A client offering both controls
should show them as the two separate things they are.
Revoking `ai_full` or `ai_extraction` does NOT switch off `documentsAutoAiRead`, but it does stop document
reading: the document gate reads the receipt, not the switch, so a revocation wins over a switch left on.
Turning the switch on again mints a fresh `ai_extraction` receipt through PATCH
/api/auth/me/documents-auto-ai-read. A client offering both controls should show them as the two separate things
they are.
When a revocation leaves no receipt covering the analysis (`ai_insights_only` or `ai_full`), the same
transaction deletes the text a model wrote under it that can be written again: per-metric status notes, the
cached briefing, model-written period narratives, reaction lines and workout paragraphs. Coach conversations,
facts and plans and document summaries are the person's records and stay. A deletion is audit-logged with counts
only (`consent.ai.purge`).
parameters:
- in: query
name: kind
@@ -12820,6 +12831,7 @@ paths:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
description: Revoke one kind. Omit to revoke the latest active receipt across every kind.
responses:
"200":
@@ -23990,8 +24002,8 @@ components:
type: boolean
required:
- documentsAutoAiRead
description: Turn automatic AI reading of newly uploaded documents on or off. Turning it on also mints an `ai_full`
consent receipt and schedules a catch-up over the existing vault.
description: Turn automatic AI reading of newly uploaded documents on or off. Turning it on also mints an
`ai_extraction` consent receipt and schedules a catch-up over the existing vault.
InjectionSitePrefsPatchRequest:
type: object
properties:
@@ -26149,6 +26161,7 @@ components:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
artefact:
type: string
minLength: 1
@@ -58163,6 +58176,7 @@ components:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
signedAt:
type: string
revokedAt:
@@ -58259,6 +58273,7 @@ components:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
receipt:
anyOf:
- $ref: "#/components/schemas/SerialisedConsentReceipt"
@@ -58281,6 +58296,7 @@ components:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
signedAt:
type: string
format: date-time
@@ -58337,13 +58353,18 @@ components:
anyOf:
- $ref: "#/components/schemas/SerialisedConsentReceipt"
- type: "null"
ai_extraction:
anyOf:
- $ref: "#/components/schemas/SerialisedConsentReceipt"
- type: "null"
required:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
additionalProperties: false
description: "The latest active receipt for every kind. All three keys are always present: a null value means the kind
is not granted, so a client never has to read absence as ambiguity."
description: "The latest active receipt for every kind. Every key is always present: a null value means the kind is not
granted, so a client never has to read absence as ambiguity."
ConsentRevokeForKindEnvelope:
type: object
properties:
@@ -58392,6 +58413,7 @@ components:
- ai_full
- ai_insights_only
- ai_coach
- ai_extraction
receipt:
$ref: "#/components/schemas/SerialisedConsentReceipt"
required: