/** * card-shapes.ts, find card-shaped content in untrusted text WITHOUT ever * handing the matched characters back (docs/inbound-email.md §11.0). * * Two consumers, two different answers to the same detection: * * - **Remote messaging channels** (`daemon/surface-card-gate.ts`), a message * carrying card shapes is REFUSED at `authorizeSurfaceIngress`, before * anything downstream can store, log or transcribe it. Design rule this * enforces: card details are entered at a local terminal or in the webui, * never over a remote messaging channel. * - **Inbound mail** (`email/inbound/record-store.ts`), mail is REDACTED, not * refused. Order confirmations legitimately carry long digit runs and they * are the consumer the inbound-mail capability exists to serve; refusing * them would break it. Only the digits fail to reach disk. * * The distinction a later reader will try to collapse, written in the terms * §11.0 asks for: 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. These two must not be unified. * * ## Why the finding carries no text * * This is docs/inbound-email.md §7.3 applied to a result type rather than to a * constructor: `CardShapeFinding` carries POSITION and KIND, never the matched * characters. A refusal message composed from findings is therefore * *structurally* incapable of quoting a card number, the digits are not * something a caller must remember not to log, they are not reachable from the * result at all. The `@ts-expect-error` guards at the bottom of this file exist * so that adding a value-bearing field fails the build rather than passing * silently. * * ## Refusal rule * * `detectCardShapes` only ever emits a `security-code` or `expiry` finding when * it is already in card context, so the refusal rule collapses to the simplest * form there is: **refuse if the finding list is non-empty**. See * `hasRefusableCardShapes`. */ /** What a finding matched. Never the characters it matched. */ export type CardShapeKind = 'pan' | 'security-code' | 'expiry'; /** * One card-shaped span, located but not quoted. * * `startIndex` and `length` index the ORIGINAL text (separators included), so * `text.slice(startIndex, startIndex + length)` reconstructs the span for a * caller that has the text anyway, for example the redactor below, which is * the only caller in the codebase that needs to. Nothing is carried across a * boundary that did not already hold the text. */ export interface CardShapeFinding { readonly kind: CardShapeKind; readonly startIndex: number; readonly length: number; } /** The Luhn check digit algorithm. The sole discriminator for a `pan`. */ export declare function passesLuhn(digits: string): boolean; /** * Locate card-shaped spans in `text`. * * `security-code` and `expiry` are never emitted on shape alone: three or four * bare digits and a bare `MM/YY` are far too common, and refusing them would * make the channel unusable. They are emitted only in card context, one of the * §11.0 keywords is present, or a `pan` was already found in the same text. * * Findings are returned in ascending `startIndex` order and never overlap. */ export declare function detectCardShapes(text: string): readonly CardShapeFinding[]; /** * Whether these findings refuse the message. Non-empty IS the rule: a * `security-code` or `expiry` finding only exists when it was already in card * context, so there is no second condition to forget. */ export declare function hasRefusableCardShapes(findings: readonly CardShapeFinding[]): boolean; /** Stable, human-readable names for the distinct shapes matched, shapes only. */ export declare function describeCardShapes(findings: readonly CardShapeFinding[]): readonly string[]; /** The distinct kinds matched, in a stable order, for a decision reason string. */ export declare function cardShapeKinds(findings: readonly CardShapeFinding[]): readonly CardShapeKind[]; /** * The reply the owner gets on the channel they sent from. * * Composed entirely from `describeCardShapes`, which reads only `kind`, so it * cannot quote the digits even if someone later edits this string carelessly. * It contains no numerals at all, deliberately, so that "does the reply leak * any part of the card" is answerable by looking at it. * * It says the veto still works, because the message being refused may have BEEN * a veto (§11.0): silence here is the one silence that does harm, since an * unheard objection inside a veto window elapses into a completed purchase. */ export declare function renderCardShapeRefusal(findings: readonly CardShapeFinding[]): string; /** * Replace every card-shaped span with a marker naming only its kind. * * Applied back-to-front so earlier spans keep their indices. The replacement is * longer than a three-digit security code, so callers with a length budget must * bound the result AFTER redacting, not before, see `record-store.ts`, which * redacts a slightly over-long window and then trims, so that a card number * straddling the excerpt boundary is redacted whole rather than truncated into * a still-readable prefix. */ export declare function redactCardShapes(text: string): string; //# sourceMappingURL=card-shapes.d.ts.map