/** * Correlate native ledger rows with live external items - the join that has to exist * before any cross-store claim. * * The native ledger and the external board are separate stores with no shared key: * a row carries `source_channel` and `source_event_id`, never an external item id. * Left to itself a model joins on titles, which produces false, missed and ambiguous * matches and then reports them as fact. This module resolves the join the only way * that is deterministic - through recorded provenance - and labels everything it * cannot resolve so the caller can refuse to make a claim about it. * * `source_event_id` is NOT a foreign key. It is optional free-form text that a model * supplies on task_create, so every step validates rather than assumes: exact index * lookup, connector must match, external identifiers come from STRUCTURED metadata * (composite-id parsing only as a legacy fallback), and a disagreement between the * two is ambiguity, not a tie to break. * * H0b, the rule that costs the most when forgotten: an item missing from the live * snapshot proves NOTHING. The poller reads only open items and emits no tombstone, * so disappearance is archive, deletion, permission loss, a board move, or a partial * read - never evidence of completion. Absence resolves to `historical_only`, and no * outcome in this module ever means "done". */ export type CorrelationOutcome = 'matched' | 'unmatched' | 'ambiguous' | 'historical_only' | 'not_applicable'; export type CorrelationReason = /** not_applicable */ 'no_source' | 'other_connector' /** unmatched */ | 'no_provenance' | 'provenance_not_indexed' | 'provenance_connector_mismatch' | 'external_ref_unresolvable' /** ambiguous */ | 'provenance_conflict' | 'multiple_rows_one_item' /** historical_only */ | 'absent_from_live_snapshot' | 'live_snapshot_incomplete' /** matched */ | 'live_item'; export interface CorrelationLedgerRow { id: number; sourceChannel: string | null; sourceEventId: string | null; } /** A connector_event_index row, reduced to what correlation needs. */ export interface ProvenanceRecord { sourceConnector: string; /** Connector-written composite identity, e.g. `::`. */ sourceId: string; metadata: Record | null; } export interface LiveExternalItem { itemId: string; board: string; list: string; } export interface ExternalRef { boardId: string; itemId: string; } export interface CorrelationInput { /** Connector whose items are being correlated, e.g. 'trello'. */ connector: string; rows: readonly CorrelationLedgerRow[]; lookupProvenance: (eventIndexId: string) => ProvenanceRecord | null; liveItems: readonly LiveExternalItem[]; /** * Whether the live snapshot covered everything it claims to. * When false, absence cannot even be called historical: a partial read is * indistinguishable from a vanished item, so nothing resolves to matched-by-absence. */ liveSnapshotComplete: boolean; } export interface TaskCorrelation { taskId: number; outcome: CorrelationOutcome; reason: CorrelationReason; externalRef: ExternalRef | null; /** Present only for `matched`: the item's CURRENT position on the live board. */ live: { board: string; list: string; } | null; } export type CorrelationCoverage = Record & { total: number; }; export interface CorrelationResult { correlations: TaskCorrelation[]; coverage: CorrelationCoverage; } /** * Resolve every row, then demote collisions: when several rows resolve to ONE external * item, no row among them can carry a factual claim about it, because which row the * item's state belongs to is exactly what is unknown. */ export declare function correlateTasksWithExternalItems(input: CorrelationInput): CorrelationResult; //# sourceMappingURL=external-correlation.d.ts.map