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

@@ -70,8 +70,8 @@ const CONSENT_HELPERS = [
"assertConsentForChain",
"chainRequiresServerManagedConsent",
"hasActiveConsentForSurface",
// Document-class, pick-shaped.
"assertDocumentEgressConsent",
// Document-class, pick-shaped (the receipt itself is read by the capability
// egress re-check below).
"isExternalDocumentEgress",
] as const;
@@ -29,9 +29,13 @@ const SRC = join(process.cwd(), "src");
const EGRESS_CALL =
/\.generateCompletion(?:Stream)?\s*\(|\brunRawCompletionWithFallback\s*\(|\brunWithFallback\s*\(/;
/** A capability read in the same file. */
/**
* A capability read in the same file: one of the gates, or the wire re-check
* (`aiEgressRefusal` / `assertAiEgress`), which also answers the consent a
* picked provider needs.
*/
const CAPABILITY_CHECK =
/\b(?:aiCapabilityForJob|aiCapabilityForRecord|requireAiCapability|getAiCapability)\s*\(/;
/\b(?:aiCapabilityForJob|aiCapabilityForRecord|requireAiCapability|getAiCapability|aiEgressRefusal|assertAiEgress)\s*\(/;
/** Comments name the helpers too; only code counts. */
function stripComments(source: string): string {
@@ -71,7 +75,7 @@ const EGRESS_SITES: Record<string, EgressSite> = {
"lib/jobs/reaction-line.ts": {
recheck: true,
reason:
"Reaction lines call the first chain entry directly; the worker resolves `reactionLines` before the digest or the chain.",
"Reaction lines call the first chain entry directly; the worker resolves `reactionLines` before the digest and re-checks the chain at the wire (`aiEgressRefusal`).",
},
"lib/jobs/coach-nudge-ai.ts": {
recheck: true,