import { type DKGAgent, type CatchupPassDecisionReason, type DurableProgressSummary, type DurableSyncDiagnostics, type DurableSyncResult, type SwmSnapshotCoverage } from '@origintrail-official/dkg-agent'; export interface CatchupJobResult { connectedPeers: number; totalPeers?: number; selectedPeers?: number; syncCapablePeers: number; peersTried: number; /** * Subset of `peersTried` whose per-peer sync round reached a responder * and did not collapse into a transport failure. A responder can still * time out part-way through, deny access, or serve metadata-only rows; this * counter exists so daemon status mapping can distinguish "curator offline" * from "reachable peer answered but did not complete cleanly". */ peersResponded: number; /** * Subset of `peersTried` whose per-peer sync round finished without a * transport failure, timeout, or explicit ACL denial, and with either real * progress or a clean non-metadata-only empty completion. */ peersSucceeded: number; /** * Sync-capable peers this run deliberately never contacted because an earlier * wave already proved every requested plane. These are neither failures nor * successes; they exist so status mapping and operators can tell an * early-stopped run from a run where peers were unreachable. */ peersNotAttempted?: number; /** Context Graph phases deferred by this node's local sync scheduler. */ deferredBackpressure: number; dataSynced: number; sharedMemorySynced: number; denied: boolean; deniedPeers: number; /** * Per-plane evidence produced before peer results are aggregated. Aggregate * diagnostics intentionally retain every timeout/denial for observability, * but readiness must not let one bad peer mask another peer that completed * the same plane cleanly and stored verified data. */ cleanPlaneCompletions?: { /** Always carries `verifiedPrivateOnlyPeers`; only the durable plane can produce it. */ durable: CatchupPlaneCompletionEvidence & { verifiedPrivateOnlyPeers: number; }; sharedMemory: CatchupPlaneCompletionEvidence; }; diagnostics?: { noProtocolPeers: number; durable: { fetchedMetaTriples: number; fetchedDataTriples: number; insertedMetaTriples: number; insertedDataTriples: number; bytesReceived: number; resumedPhases: number; timedOutPhases: number; completedPhases: number; checkpointAdvances: number; emptyResponses: number; metaOnlyResponses: number; /** Cryptographically verified V2 responses whose public graph is intentionally empty. */ verifiedPrivateOnlyResponses: number; dataRejectedMissingMeta: number; rejectedKcs: number; failedPeers: number; failedPhases: number; deferredBackpressure: number; deniedPhases?: number; /** A resolvable curator never cleanly answered this plane; see * `catchupPlaneProvenByUnanimousEmpty`. */ authorityUnanswered?: boolean; }; sharedMemory: { fetchedMetaTriples: number; fetchedDataTriples: number; insertedMetaTriples: number; insertedDataTriples: number; bytesReceived: number; resumedPhases: number; timedOutPhases: number; completedPhases: number; checkpointAdvances: number; emptyResponses: number; droppedDataTriples: number; failedPeers: number; failedPhases: number; deferredBackpressure: number; deniedPhases?: number; /** A resolvable curator never cleanly answered this plane; see * `catchupPlaneProvenByUnanimousEmpty`. */ authorityUnanswered?: boolean; /** * Public-SWM snapshot coverage for this graph, selected WHOLE from one * peer round by `selectSwmSnapshotCoverage`. The counts, the peer they * are attributed to and the missing sample are never mixed across peers. */ swmCoverage?: SwmSnapshotCoverage; /** Snapshot phases that yielded on the local clock — see the agent-side field. */ snapshotPlaneIncomplete: number; /** Extra passes over the peer set beyond the first. */ continuationPasses: number; /** * Why the bounded repeat stopped, as the policy's own closed union — so a * new reason cannot reach the terminal message unnoticed. */ continuationStopReason?: CatchupPassDecisionReason; /** * `bytesReceived` split into its replay half (metadata + aggregate data, * which every pass re-fetches in full) and its useful half (snapshot * content), so the cost of repeating the walk stays measurable instead of * being merged into one scalar. The two sum to `bytesReceived`. */ replayPhaseBytesReceived: number; snapshotPhaseBytesReceived: number; }; }; } export interface CatchupRunRequest { contextGraphId: string; includeSharedMemory: boolean; } export interface CatchupPhaseProgress extends DurableProgressSummary { bytesReceived?: number; emptyResponses?: number; } export type DurableLegDiagnostics = DurableSyncDiagnostics & Pick; export interface DurableLegSummary { insertedTriples: number; diagnostics: DurableLegDiagnostics; complete: boolean; state: DurableCatchupLegState; failureReasons: DurableCatchupFailureReason[]; } export type DurableCatchupLegState = 'complete' | 'incomplete-progress' | 'failed' | 'indeterminate' | 'legacy'; export type DurableCatchupFailureCode = 'failedPeers' | 'failedPhases' | 'deniedPhases' | 'rejectedKcs' | 'dataRejectedMissingMeta' | 'incompleteWithoutProgress' | 'indeterminateSettlement'; export type DurableCatchupFailureReason = { code: DurableCatchupFailureCode; count: number; } | { code: 'exception'; message: string; }; /** The only agent capabilities required by the route-level durable leg. */ export interface DurableCatchupAgent { syncFromPeerDetailed?: OmitThisParameter; syncFromPeer?: OmitThisParameter; } export interface DurableCatchupLegResult { insertedTriples: number; state: DurableCatchupLegState; complete?: boolean; diagnostics?: DurableLegDiagnostics; failureReasons?: DurableCatchupFailureReason[]; } export interface DurableCatchupAttempt { durableState?: DurableCatchupLegState; durableComplete?: boolean; durableError?: string; error?: string; } export interface DurableCatchupRequestOutcome { attempts: DurableCatchupAttempt[]; perContextGraphCompletion: Array; complete?: boolean; allPeersFailed: boolean; noEligibleAttempts: boolean; incomplete: boolean; responseStatus: 200 | 503; errorBody: { errorCode: 'DURABLE_CATCHUP_ALL_PEERS_FAILED' | 'DURABLE_CATCHUP_NO_ELIGIBLE_PEERS' | 'DURABLE_CATCHUP_INCOMPLETE'; error: string; retryable: true; } | undefined; } /** * Adapt the agent's typed durable result for operator-facing catch-up APIs. * Whole-leg completion comes only from the explicit agent contract; phase * counters remain diagnostics and can describe safely committed prefixes. */ export declare function summarizeDurableLeg(result: DurableSyncResult): DurableLegSummary; /** Convert typed leg failures to the legacy operator-facing message at the HTTP boundary. */ export declare function formatDurableCatchupFailure(reasons: readonly DurableCatchupFailureReason[] | undefined): string | undefined; /** * Execute one durable route leg behind a typed capability boundary. Detailed * agents expose completion/diagnostics; older agents retain the legacy count. */ export declare function runDurableCatchupLeg(agent: DurableCatchupAgent, peerId: string, contextGraphId: string, overallTimeoutMs: number): Promise; export declare function durableCatchupCompletionFor(attempts: readonly DurableCatchupAttempt[]): boolean | undefined; /** * Aggregate completion across every requested CG. A complete subset must not * manufacture a complete whole-request verdict when another CG had no result. */ export declare function classifyDurableCatchupRequest(perContextGraphAttempts: ReadonlyArray, includeDurable: boolean, includeSharedMemory: boolean): DurableCatchupRequestOutcome; export declare function catchupPlaneCompletedWithoutFailure(progress: CatchupPhaseProgress | null | undefined, complete?: boolean): boolean; /** Per-plane clean-completion evidence accumulated across the peers this run contacted. */ export interface CatchupPlaneCompletionEvidence { verifiedDataPeers: number; /** Peers that cleanly verified one or more V2 KAs with no public triples. */ verifiedPrivateOnlyPeers?: number; emptyPeers: number; /** * The metadata-resolved curator cleanly completed this plane while hosting * the graph and carrying no data at all. See * {@link catchupPlaneProvenByUnanimousEmpty}. */ authorityEmptyPeers?: number; /** * Peers that ANSWERED this plane but whose round did not complete cleanly. * * Every other field here records what a peer proved. This one records what a * peer left unresolved, and it exists because the absence of a peer from the * positive counters is ambiguous: `catchupPeerPlaneEvidence` returns an * all-zero record for an incomplete round, so a peer that answered EMPTY but * did not finish paging is indistinguishable from a peer that was never * contacted. That ambiguity is invisible to the round diagnostics too — an * explicit `complete: false` is not a transport failure, so it never reaches * `failedPeers`. * * Without it, a round of one clean-empty peer plus one incomplete-empty peer * reads as unanimously empty. Pure transport failures are deliberately NOT * counted here: an unreachable stranger is already `failedPeers`, and folding * it in would pin legitimately empty graphs in a retry loop on a lossy * network. */ incompleteResponders?: number; } /** The aggregate per-plane counters a whole-round verdict is allowed to consult. */ export interface CatchupPlaneRoundDiagnostics { fetchedMetaTriples?: number; fetchedDataTriples?: number; emptyResponses?: number; /** Peers that returned `_meta` and no data; durable-only. */ metaOnlyResponses?: number; failedPeers?: number; failedPhases?: number; timedOutPhases?: number; deniedPhases?: number; deferredBackpressure?: number; /** Durable-only integrity rejections; the shared-memory plane never sets them. */ dataRejectedMissingMeta?: number; rejectedKcs?: number; /** * A metadata-resolved curator WAS selected for this walk and did not cleanly * answer this plane — it transport-failed, timed out, was denied, or never got * contacted. Distinct from `failedPeers`, which counts any unreachable peer. */ authorityUnanswered?: boolean; } /** * Reduce ONE peer's plane result to the evidence a round accumulates from it. * * This is the single definition of what a peer's round contributes, so the * walk's stop condition and the readiness classifier cannot drift: the walk * feeds one peer's evidence to {@link catchupPlaneProvenByData}, and readiness * feeds the summed evidence to the same predicate. Adding a new verified-content * signal therefore has exactly one place to change. * * A plane that did not complete cleanly contributes nothing at all. */ export declare function catchupPeerPlaneEvidence(plane: (CatchupPhaseProgress & { emptyResponses?: number; fetchedDataTriples?: number; }) | null | undefined, options: { /** * Which plane this result came from. REQUIRED, and deliberately not * defaulted: the strongest thing this function can say — hosted-empty * evidence — is true on the durable plane and false on shared memory, so a * defaulted `plane` would let a shared-memory call site silently take the * durable branch. Only a test would notice, and the whole point is that a * mistake here settles a plane nobody proved. */ plane: 'durable' | 'shared-memory'; /** Durable-only lifecycle state; the shared plane has no `complete` concept. */ complete?: boolean; fromAuthority?: boolean; }): CatchupPlaneCompletionEvidence; /** Fold one peer's evidence into the running per-plane totals. */ export declare function addCatchupPlaneEvidence(total: CatchupPlaneCompletionEvidence, peer: CatchupPlaneCompletionEvidence): void; /** * Positive proof: some peer cleanly completed this plane while carrying * cryptographically verified content. This is the only evidence strong enough * to stop contacting further peers mid-run, because it is the only evidence a * single peer can produce on its own. */ export declare function catchupPlaneProvenByData(completion: CatchupPlaneCompletionEvidence | undefined): boolean; /** * Proof mode 1 — the CURATOR hosts the graph and it holds nothing. * * A registered public graph that really is empty still carries definition * triples in its own `/_meta`, so the peer hosting it answers * metadata-only, never wire-empty, and could never satisfy the whole-round rule * below. Its curator saying so is the only evidence such a graph can produce. * * Scoped to the metadata-resolved curator and nothing else. Any OTHER peer's * metadata-only round is the commonest state on the network — a member that has * `_meta` but has not synced the data yet — and accepting it would resettle * issue #2006's exact failure as `done` with zero Knowledge Assets. * * Another peer merely failing part-way cannot contradict the curator; another * peer producing CONTENT can, and that is what {@link emptyVerdictContradicted} * checks — it means the curator's view is behind the network's. */ export declare function catchupPlaneProvenByAuthorityHostedEmpty(completion: CatchupPlaneCompletionEvidence | undefined, diagnostics: CatchupPlaneRoundDiagnostics | undefined, options: { isPrivate: boolean; }): boolean; /** * Proof mode 2 — a whole round in which nobody had anything. * * A peer that has never heard of a Context Graph and a peer that hosts an empty * one are byte-identical on the wire: an unknown CG has no access policy, so the * responder authorizes the request and its CG-scoped queries simply return zero * rows. The requester only reports `emptyResponses` when BOTH phase payloads are * empty (`sync-verify-worker-impl.ts`), so an empty response can never carry * hosting evidence — there is no per-peer signal that could distinguish the two. * * Emptiness is therefore a verdict over the whole round: some peer completed * cleanly empty, nobody delivered any graph CONTENT, and no peer engaged and * then failed part-way. That exact shape — 122,705 data triples fetched and * five failed phases, with five unrelated peers answering empty — is what * settled issue #2006's run as `done` with 1 KA out of 40, and either clause * kills it on its own. * * `metaOnlyResponses` also kills it. A non-curator that returned `_meta` and no * data is the ambiguous case this rule cannot resolve — the requester itself * logs "peer may have empty or pruned data graph" — and without the curator * present there is nothing to resolve it against. When the curator IS present, * proof mode 1 has already settled the plane, so voiding here costs the * legitimately-empty graph nothing. * * The verdict IS voided when the round had a resolvable curator that never * cleanly answered (`authorityUnanswered`). The peer best placed to know is the * one we failed to hear from, so "nobody had anything" is not established — the * round is incomplete, not empty. That closes issue #2006's own symptom in its * sharpest form: the walk puts a resolvable curator alone in wave 1, so when the * curator transport-fails the walk moves on to strangers, one answers empty, and * 40 Knowledge Assets get reported as zero. * * Scoped to the AUTHORITY rather than to `failedPeers`, and the difference is * load-bearing. `failedPeers` counts any unreachable peer, so voiding on it would * also kill the verdict when NO curator is resolvable at all — the state where * the hosted-empty backstop structurally cannot fire — leaving a legitimately * empty public graph pinned at `unreachable` by a single unreachable stranger. * That is the liveness failure this rule was originally written to avoid, and it * is still worth avoiding; it is only the curator's silence that is decisive. * * Two counters are deliberately NOT consulted: * * - `failedPeers`. A transport failure to a peer we never heard from, which on a * live testnet can be most of the connected set. An unreachable STRANGER is * evidence of nothing; an unreachable CURATOR is, and has its own signal above. * - `fetchedMetaTriples`. A raw triple count, not a per-peer verdict: a delta * sync legitimately carries the whole metadata phase with nothing newer than * the watermark, and the requester deliberately does NOT flag that as * metadata-only. Voiding on the raw count would make a legitimately empty * public graph permanently unreadable rather than merely unproven. * - `failedPeers`. That is a transport failure to a peer we never heard from — * on a live testnet a majority of connected peers can be unreachable — and an * unreachable stranger is evidence of nothing. A peer that DID engage and * then failed shows up in `failedPhases` / `timedOutPhases` / `deniedPhases` * / `deferredBackpressure`, all of which do void the verdict. * * Residual, unchanged from before this rule existed: if the only host is * unreachable while another peer answers cleanly empty, the round still reads * as empty. Readiness is re-derived on the next catch-up. */ export declare function catchupPlaneProvenByUnanimousEmpty(completion: CatchupPlaneCompletionEvidence | undefined, diagnostics: CatchupPlaneRoundDiagnostics | undefined, options: { isPrivate: boolean; }): boolean; /** * Canonical readiness proof for one catch-up plane: verified content, the * curator's hosted-empty word, or a whole round in which nobody had anything — * in that order of strength. * * The peer walk stops early only on {@link catchupPlaneProvenByData} or the * curator's own round, so whenever this falls through to the unanimous-empty * branch the full peer set really was walked and the "nobody saw anything" * denominator is meaningful. */ export declare function catchupPlaneReady(completion: CatchupPlaneCompletionEvidence | undefined, diagnostics: CatchupPlaneRoundDiagnostics | undefined, options: { isPrivate: boolean; }): boolean; export declare function catchupPeerSucceeded(durable: CatchupPhaseProgress | null | undefined, shared: CatchupPhaseProgress | null | undefined, peerDenied: boolean, durableComplete?: boolean): boolean; export declare function catchupPeerResponded(durable: CatchupPhaseProgress | null | undefined, shared: CatchupPhaseProgress | null | undefined): boolean; export interface CatchupRunner { run(request: CatchupRunRequest): Promise; close(): Promise; } export declare function createCatchupRunner(agent: DKGAgent): CatchupRunner; export declare function createInlineCatchupRunner(agent: DKGAgent): CatchupRunner; //# sourceMappingURL=catchup-runner.d.ts.map