import { type ConnectLink, type ConnectLinkConsume, type ScopeId, type TenantId } from '@substrat-run/contracts'; import type { RedactionSql } from './subject-redaction.js'; /** * The platform-held connect link (connections.md §3.5.4), as statements both adapters run * against their directory — the SQLite host over better-sqlite3, the hosted one inside * ControlPlaneDO — so the lifecycle is written once, the way the findings store is. * * A vertical mints one to be MAILED: a bookkeeping bureau's staff send it to a client * company's Fortnox administrator, who opens it days later. A signature alone cannot be * withdrawn and cannot be spent, which is what §3.5.3's in-session URL gets away with by * living minutes; a link living a week cannot. So the signed state names this row, and the * row decides: outstanding and unexpired opens, anything else refuses. * * Every read and write is keyed by (tenant, scope, id). A link of another scope or tenant * is not a forbidden row — it is an absent one, and answers exactly as `unknown` does. * * Expiry is judged against the `now` the caller passes: the pure host's clock, or the * coordinator's wall clock on the hosted one. ISO 8601 UTC text on both sides, so a string * comparison is a chronological one. */ export declare const CONNECT_LINKS_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_connect_links (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n vertical TEXT NOT NULL,\n provider TEXT NOT NULL,\n status TEXT NOT NULL,\n created_by TEXT NOT NULL,\n subject_ref TEXT,\n return_url TEXT,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n used_at TEXT,\n account_ref TEXT,\n account_label TEXT\n );\n CREATE INDEX IF NOT EXISTS _substrat_connect_links_scope\n ON _substrat_connect_links (tenant_id, scope_id, created_at);\n"; export interface ConnectLinkKeyRow { tenantId: string; scopeId: string; id: string; } /** * The admin-log row a link move hands back, for the caller to write in the SAME unit as the * move: a consume whose audit row failed would otherwise leave the link spent with no * connection and no record, and the retry refused as `used`. Shaped here, once, so both * adapters write the same row. A move that changed nothing hands back nothing. */ export interface ConnectLinkAudit { action: 'mintConnectLink' | 'revokeConnectLink' | 'consumeConnectLink' | 'restoreConnectLink'; target: { tenantId: TenantId; scopeId: ScopeId; vertical: string; }; before: unknown; after: unknown; } export declare function insertConnectLink(sql: RedactionSql, row: { id: string; tenantId: string; scopeId: string; vertical: string; provider: string; createdBy: string; subjectRef: string | null; returnUrl: string | null; createdAt: string; expiresAt: string; }, audit: (row: ConnectLinkAudit) => void): ConnectLink; export declare function readConnectLink(sql: RedactionSql, key: ConnectLinkKeyRow): ConnectLink | undefined; export declare function listConnectLinks(sql: RedactionSql, filter: { tenantId: string; scopeId?: string; ids?: readonly string[]; provider?: string; outstandingOnly?: boolean; }, now: string): ConnectLink[]; /** * Withdraw an outstanding link. Idempotent, and only an outstanding row moves: a used link * has already been spent (its connection is what to disconnect), and a revoked one stays * revoked. `undefined` when no such link exists in this scope; `changed` says whether this * call is the one that revoked it, so a no-op is not audited. */ export declare function revokeConnectLinkRow(sql: RedactionSql, key: ConnectLinkKeyRow, audit: (row: ConnectLinkAudit) => void): { link: ConnectLink; changed: boolean; } | undefined; /** * Spend a link — the callback's act, BEFORE it stores the credential, so of two racing * callbacks exactly one gets past it. Single-use is decided by the UPDATE's WHERE, not by * the read in front of it: the read only picks which refusal to answer with. */ export declare function consumeConnectLinkRow(sql: RedactionSql, input: ConnectLinkKeyRow & { provider: string; accountRef?: string; accountLabel?: string; }, now: string, audit: (row: ConnectLinkAudit) => void): ConnectLinkConsume; /** * The compensating half of consume: the callback spent the link, then failed to store the * credential, so nothing was connected and the link goes back to outstanding — a platform * hiccup costs a retry, not a fresh link mailed to a client. Only a `used` row moves (a * revocation is never undone by this), and only an unexpired one: a link that lapsed while * the store failed stays spent rather than coming back already dead. */ export declare function restoreConnectLinkRow(sql: RedactionSql, key: ConnectLinkKeyRow, now: string, audit: (row: ConnectLinkAudit) => void): ConnectLink | undefined; //# sourceMappingURL=connect-links.d.ts.map