/** * surface-card-gate.ts, refuse card-shaped content arriving on any remote * messaging channel (docs/inbound-email.md §11.0). * * **Provenance, stated plainly: §11.0 is a coordinator ruling, not an owner * quote.** Design rule it enforces: card details are entered only at a local * terminal or in the webui, never over a remote messaging channel. This is the * daemon-side enforcement of that. * * ## The distinction that must not be collapsed * * Approvals and vetoes for purchases **do** work over remote channels. That is * the owner's explicit ruling and it stays. Remote surfaces have authority to * **say yes or no about a purchase**; they have **no path for entering the * instrument**. Authority over a decision is not a channel for a secret. A * later reader will be tempted to unify the two, "if the owner can approve a * payment from Telegram, why not enter the card there", and must not. * * This is also why the refusal reply is delivered rather than dropped silently: * the message being refused may itself have BEEN a veto, and an unheard * objection inside a veto window elapses into a completed purchase. Silence is * the one response here that can cost money. * * ## Why this lives on the shared ingress hook and not in the adapters * * `authorizeSurfaceIngress` is the single hook all nineteen remote adapter call * sites already pass through, the same reason work-proposal and approval-reply * consumption live there. The payments round learned the alternative firsthand: * a fix applied per-adapter leaves the other seventeen open. * * ## Why it runs FIRST * * Before `evaluateIngress`, before proposal-reply resolution, before * approval-reply resolution. This is not stylistic. `evaluateIngress` writes * `input.text.slice(0, 200)` into the channel policy audit trail and schedules * that trail to disk; running the gate second would persist the digits it * exists to keep off disk. Everything downstream may store, log or transcribe, * so the check must precede all of it. */ import type { ChannelIngressPolicyInput, ChannelPolicyDecision, ChannelPolicyManager } from '../channels/index.js'; import { type ConversationGateDeps } from './surface-conversation-gate.js'; export interface SurfaceCardGateDeps extends Pick { /** Read-only. The gate needs a policy record for the decision it returns and must NOT evaluate ingress to get one. */ readonly channelPolicy: Pick; } /** Decision reason prefix. The suffix names the matched shape KINDS, never the digits. */ export declare const CARD_SHAPES_REFUSED_REASON = "card-shapes-refused"; /** * Inspect one inbound message for card shapes. * * Returns `null` when there is nothing to refuse, which is the overwhelming * majority of messages and the only path that continues to policy evaluation. * Returns a not-allowed `ChannelPolicyDecision` when card shapes are present, * having first put a refusal on the same channel the message arrived on. * * Nothing derived from `input.text` reaches a log line, a decision reason, a * notice body or any store: every outward string here is built from * `CardShapeFinding.kind`, which is all a finding carries. */ export declare function refuseCardShapedIngress(deps: SurfaceCardGateDeps, input: ChannelIngressPolicyInput): Promise; //# sourceMappingURL=surface-card-gate.d.ts.map