export type StrayKind = "unattributed-d1" | "unattributed-r2" | "account-wide-token"; export interface Stray { kind: StrayKind; detail: string; } /** CONCURRENCY CONTRACT — read before implementing these callbacks. * * reconcileStorage invokes all five through ONE Promise.all. They overlap, * including the two registeredNames calls, which run at the same time as each * other. So no callback may share, with any other callback or with a second * invocation of itself, a resource that admits one operation at a time. * * The resource that has actually caused this is a Neo4j session: it rejects a * query issued while one of its own is in flight. A caller that opened one * session in the enclosing scope and used it in registeredNames made the * second read reject, which left the standing storage audit reporting nothing * for 802 consecutive ticks on maxy-code and 295 on sitedesk-code (Task 1813). * Open the session inside the callback, one per invocation, and close it * there; driver sessions come from a pool, so this is cheap. * * The contract is stated here rather than on reconcileStorage because this * interface is what a caller writing an implementation is looking at, and the * natural thing to write is the thing that breaks. */ export interface AuditDeps { cfD1(): Promise; cfR2(): Promise; /** Called twice, concurrently, once per kind. See the contract above. */ registeredNames(kind: "d1" | "r2"): Promise; accountSecretsFiles(): Promise>; } export declare function reconcileStorage(deps: AuditDeps): Promise<{ strays: Stray[]; }>; /** Same concurrency contract as AuditDeps above: reconcilePages invokes both * callbacks through one Promise.all, so neither may share a single-operation * resource with the other. * * That registeredProjects happens to issue exactly one query today is not the * property that makes a shared session safe here, and relying on it would put * the 1813 defect one added read away. */ export interface PagesAuditDeps { cfProjects(): Promise; registeredProjects(): Promise; } export interface PagesReconcile { projects: number; registered: number; orphans: string[]; phantoms: string[]; } export declare function reconcilePages(deps: PagesAuditDeps): Promise; export interface DataPortalObject { key: string; } export interface DataPortalRow { objectKey: string; ingested: number; uploadedAt: string; /** Where the bytes landed on the device, set only after a verified write * (Task 1910). Its presence IS the proof the move happened, which is what * separates a healthy moved row from a phantom. Absent on a portal whose * hand-run ALTER has not happened yet, which reads as "never moved". */ devicePath?: string; /** 1 when the client has withdrawn an already-moved file and the device has * not yet removed it. */ deleted?: number; } export interface DataPortalReconcile { /** DISTINCT keys, which is not the same quantity as the length of the list * the read returned. The caller logs the lengths — "how many did I see" — * and these are "how many are distinct". They agree on a healthy store * (manifest.objectKey is UNIQUE and R2 keys are unique by construction), and * giving them one name would conflate two facts that diverge exactly when * the store is unhealthy, which is the case this audit is for. */ distinctObjects: number; distinctRows: number; orphanObjects: string[]; phantomRows: string[]; /** Null when nothing is uningested, or when no uningested row carries a * parseable timestamp. Rendered `na` by the caller. */ oldestUningestedHrs: number | null; /** Every row at `ingested = 0`, including rows whose `uploadedAt` does not * parse. Deliberately NOT the size of the age sample below: that sample * excludes unparseable timestamps, so a backlog made entirely of them would * report a null age beside a zero count and read as an empty queue. */ uningestedRows: number; /** Objects still in R2 whose row says the bytes already reached the device * (Task 1910). The move deletes the object after the claim commits, so a * survivor is a leak. It emits nothing at request time — the pull has * already returned and no later request touches the key — which is why it * is counted here rather than logged somewhere. */ unmovedObjects: string[]; /** Withdrawals the device has not carried out yet. Healthy is 0; a number * that persists across cycles is a withdrawal that will never complete. */ deletePendingRows: number; /** Age of the oldest pending withdrawal, so a stuck one is dateable rather * than merely present. Null when none are pending. */ oldestDeletePendingHrs: number | null; /** Files routed into an exposed folder that the published index does not * carry. The client uploaded into a folder, the file landed, and the tree * still does not show it. * * NULL when the index was not supplied, never 0: an unread index is "not * measured", and rendering it as zero would let a failed read present as a * clean bill of health. */ unpublishedRouted: number | null; } /** Pure over two lists and an injected clock, so the age arithmetic is * deterministic under test rather than dependent on when the suite runs. * * Unlike its two siblings this takes lists rather than callbacks: both sides * are read per account by a caller that must catch each independently, so * there is no fan-out here to carry a concurrency contract. */ export declare function reconcileDataPortal(objects: DataPortalObject[], rows: DataPortalRow[], nowMs: number, publishedPaths?: Set): DataPortalReconcile; //# sourceMappingURL=audit.d.ts.map