/**
 * Core webhook processing pipeline.
 *
 * Connects all orchestrator modules into a complete processing flow:
 * dedup -> normalize event -> fetch lock file -> get changed files ->
 * match triggers -> cache check -> dispatch jobs
 *
 * Uses the ProviderRegistry for all provider-specific operations.
 * No direct GitHub/Octokit references -- fully provider-agnostic.
 *
 * Decision traces are forwarded to Platform via platformClient.send() (which buffers
 * internally when disconnected -- the caller does NOT check connection state).
 */
import { type Kysely } from 'kysely';
import type { Database } from '../db/types.js';
import type { WebhookInfo } from '../webhook/handler.js';
import type { DedupCache } from '../webhook/dedup.js';
import type { ProviderRegistry, ProviderBundle } from '../provider-registry.js';
import type { LockFileCache } from '../lockfile-cache.js';
import type { Dispatcher } from '../agent/dispatcher.js';
import type { PlatformClient } from '../ws/platform-client.js';
import type { QueuedJobInput } from '../queue/job-queue.js';
import type { SourceCache } from '../cache/index.js';
import type { BuildCoordinator } from '../cache/index.js';
import type { DepCache } from '../cache/index.js';
import type { PendingBuildTracker } from '../cache/index.js';
import type { PendingInitTracker } from '../cache/pending-inits.js';
import type { PendingDynamicTracker } from '../cache/pending-dynamics.js';
import type { CheckRunReporter } from '../reporting/check-run-reporter.js';
import type { ExecutionTracker } from '../reporting/execution-tracker.js';
import type { AgentRegistry } from '../agent/registry.js';
import type { RunCoordinator } from '../cluster/coordinator.js';
import type { LogStorage } from '../reporting/log-storage.js';
import type { SecretResolver } from '../secrets/secret-resolver.js';
import type { ContributorCache } from '../security/contributor-cache.js';
import type { LockFile as FullLockFile, LockWorkflow, SimulatedEvent, WebhookNormalizer } from '@kici-dev/engine';
import { LockFileParseError } from '@kici-dev/engine';
import type { EventRouter } from '../events/event-router.js';
import type { RegistrationStore } from '../registration/registration-store.js';
import type { RegistrationIndex } from '../registration/registration-index.js';
import type { CronScheduler } from '../cron/cron-scheduler.js';
import type { GlobalWorkflowPolicy } from '../security/global-workflow-policy.js';
import type { EventLogWriter } from '../webhook/event-log.js';
import { EventLogSource } from '@kici-dev/engine';
import type { LockJob } from '@kici-dev/engine';
import type { EnvironmentStore } from '../environments/environment-store.js';
import type { VariableStore } from '../environments/variable-store.js';
import type { TrustResolver, IdentityLink, PermissionLevel } from '../security/trust-resolver.js';
import type { HeldRunStore } from '../environments/held-runs.js';
import type { WorkflowDecision } from '@kici-dev/engine';
/**
 * Pending dispatch context for jobs gated by the needs scheduler.
 * Keyed by `${runId}:${jobName}`. Populated at processWebhook time for all
 * non-root static jobs. Consumed by the onJobReady callback when the scheduler
 * determines a job's upstreams are all satisfied.
 *
 * Entries are cleaned up after dispatch or when the run completes.
 */
interface PendingJobContext {
    jobInput: QueuedJobInput;
    runsOnLabels: string[];
}
/**
 * Store a pending dispatch context for a job that will be dispatched later
 * by the needs scheduler. The key is `${runId}:${jobName}`.
 * Writes to both in-memory Map and DB table for crash recovery.
 * @internal Exported for testing
 */
export declare function storePendingJobContext(db: Kysely<Database> | undefined, runId: string, jobName: string, ctx: PendingJobContext): Promise<void>;
/**
 * Consume and remove a pending dispatch context for a job.
 * Returns undefined if no context is stored (e.g. job was already dispatched).
 * Deletes from both in-memory Map and DB table.
 *
 * Cluster correctness: `storePendingJobContext` writes to both the local
 * in-memory Map and the shared DB. In an HA cluster the peer that ingested
 * the webhook stores the context, but `onJobReady` fires on whichever peer
 * tracked the upstream job's completion — which, for a rerouted upstream,
 * is a different peer whose Map is empty. Falling through to an atomic
 * `DELETE ... RETURNING` ensures exactly one peer claims the context.
 * @internal Exported for testing
 */
export declare function consumePendingJobContext(db: Kysely<Database> | undefined, runId: string, jobName: string): Promise<PendingJobContext | undefined>;
/**
 * Clean up all pending dispatch contexts for a run (called on run completion).
 * Deletes from both in-memory Map and DB table.
 */
export declare function cleanupPendingJobContexts(db: Kysely<Database> | undefined, runId: string): Promise<void>;
/**
 * Restore all pending job contexts from DB into the in-memory Map.
 * Called on startup before the needs scheduler recovery loop so that
 * dispatchReadyJob has context available for recovered jobs.
 */
export declare function restorePendingJobContexts(db: Kysely<Database>): Promise<number>;
/**
 * Clear all entries from the in-memory pending job contexts Map.
 * @internal Exported for testing only.
 */
export declare function clearPendingJobContextsMap(): void;
/**
 * Determine if a lock job is a "root" job (no concrete needs and no dependsOnGroups).
 * Root jobs can be dispatched immediately; non-root jobs wait for the scheduler.
 */
export declare function isRootJob(lockJob: LockJob): boolean;
/**
 * Attempt to resolve a lock file for (repoIdentifier, ref) using the inbound
 * webhook's provider bundle first, then falling back to other provider bundles
 * registered against the SAME customer's registrations for the SAME repo.
 *
 * Why this exists
 * ----------------
 * The webhook pipeline binds `lockFileFetcher` to the inbound webhook's
 * provider bundle. When an internal-sourced webhook (e.g., the staging
 * stg-ha-smoke failover-dispatch test) arrives for a repo whose lock file
 * is only accessible via a different provider (e.g., github), the inbound
 * fetcher returns null and trigger matching silently drops the webhook.
 * This resolver lets the pipeline consult OTHER bundles whose registrations
 * prove they own the repo for the same tenant.
 *
 * Tenant boundary (security-critical)
 * -----------------------------------
 * The fallback iterates ONLY registrations returned by
 * `registrationIndex.getByOrgAndRepo(customerId, repoIdentifier)`. That
 * index is keyed by `${customerId}|${repoIdentifier}`, so cross-customer
 * leakage is structurally impossible — a customer-B registration for
 * `owner/repo` will never be returned when we pass `customerId = 'custA'`.
 * The resolver also skips any registration whose `routingKey` matches the
 * inbound routing key (no self-recursion) and dedupes by routing key so a
 * repo with many workflows registered through the same source only
 * triggers one fallback fetch.
 *
 * Ordering
 * --------
 * Registrations are consulted in the order returned by the index (which
 * preserves insertion order = createdAt ascending). The first non-null
 * lock file wins. If all fallbacks return null the function returns null
 * and the caller falls through to the existing `Lock file not found`
 * global-workflow-matching path.
 *
 * Credentials
 * -----------
 * Each fallback fetcher is invoked with the REGISTRATION'S
 * `providerContext`, NOT the inbound normalizer's credentials. This is
 * load-bearing: the InternalWebhookNormalizer returns `{}` as
 * credentials, which would never satisfy a github fetcher that requires
 * `installationId`. The registration carries the correct credentials
 * because it was created via the owning provider's source.
 */
export declare function resolveLockFileWithFallback(args: {
    inboundBundle: ProviderBundle;
    inboundRoutingKey: string;
    repoIdentifier: string;
    ref: string;
    inboundCredentials: unknown;
    customerId: string;
    providerRegistry: ProviderRegistry;
    registrationIndex: RegistrationIndex | undefined;
    lockFileCache: LockFileCache;
    deliveryId: string;
}): Promise<{
    lockFile: FullLockFile | null;
    resolvedVia: 'inbound' | 'fallback' | 'miss' | 'corrupt';
    fallbackRoutingKey?: string;
    /** The winning provider bundle when resolvedVia='fallback'. Used by the dispatch
     *  site to swap repoUrlBuilder and cloneTokenProvider (Layer 4 cross-provider fix). */
    fallbackBundle?: ProviderBundle;
    /** The winning registration's providerContext when resolvedVia='fallback'.
     *  Carries installationId etc. for clone token issuance. */
    fallbackCredentials?: Record<string, unknown>;
    /** Set when resolvedVia='corrupt': the parse error seen while attempting to
     *  resolve a lock file. A valid fallback always wins over a corrupt inbound,
     *  so this is only surfaced when NOTHING resolved. */
    corruptError?: LockFileParseError;
}>;
/**
 * Resolve the customer/org ID for a routing key.
 *
 * Checks the `sources` table first (GitHub App sources), then
 * `generic_webhook_sources` (generic webhook sources). Falls back to
 * '__default__' if neither table has the routing key.
 */
export declare function resolveOrgId(db: Kysely<Database>, routingKey: string): Promise<string>;
/**
 * Map GitHub webhook event types to lock file trigger type strings.
 * Used for global workflow matching via RegistrationIndex.getGlobalByTriggerType().
 */
export declare function eventTypeToTriggerType(eventType: string): string;
/**
 * Extract a human-readable trigger event string from webhook info.
 * Combines event type with action (e.g. "push", "pull_request:opened").
 */
export declare function buildTriggerEvent(event: string, action: string | null | undefined): string;
/**
 * Extract the first line of the commit message from a webhook payload.
 * Handles push (head_commit.message) and PR (pull_request.title) events.
 */
/**
 * Best-effort extraction of a repository identifier from a generic webhook
 * payload for cross-source dispatch (phase 28.5). Generic webhooks have no
 * canonical repo shape, so we probe the conventions a sender is most likely
 * to use:
 *
 *   1. GitHub-style `repository.full_name` (`owner/repo`)
 *   2. GitHub-style `repository.owner.login` + `repository.name`
 *   3. Flat `repository` string (e.g. `{"repository": "owner/repo"}`)
 *
 * Returns null when no recognisable repo field is present, in which case the
 * cross-source repo lookup is skipped (event-name fan-out still runs).
 */
export declare function extractInboundRepoIdentifier(payload: unknown): string | null;
export declare function extractCommitMessage(event: string, payload: unknown): string | undefined;
/**
 * Build a human-readable summary for a security hold check.
 */
export declare function buildSecurityHoldSummary(reason: string, tier: string, contributorUsername?: string): string;
/**
 * Dependencies for the processing pipeline.
 * All injected for testability. Fully provider-agnostic.
 *
 * sourceCache and buildCoordinator are optional for backward compatibility --
 * existing tests and deployments without cache configured still work.
 */
export interface ProcessingDeps {
    dedup: DedupCache;
    providerRegistry: ProviderRegistry;
    lockFileCache: LockFileCache;
    dispatcher: Dispatcher;
    /** Null/undefined in Independent mode. send() buffers when disconnected. */
    platformClient?: PlatformClient;
    /** Directory for writing raw webhook payloads. If set, writes {dir}/{repo}/{deliveryId}/payload.json. */
    webhookPayloadDir?: string;
    /** Bundle cache for compiled workflow bundles. Optional -- if not set, cache is bypassed. */
    sourceCache?: SourceCache;
    /** Build coordinator for deduplicating concurrent builds. Optional -- if not set, cache is bypassed. */
    buildCoordinator?: BuildCoordinator;
    /** Dep cache for dependency tarballs. Optional -- if not set, dep caching is bypassed. */
    depCache?: DepCache;
    /** Pending build tracker -- waits for build agents to finish before dispatching execution jobs. */
    pendingBuilds?: PendingBuildTracker;
    /** Pending init tracker -- waits for init agents to resolve dynamic fields before static dispatch. */
    pendingInits?: PendingInitTracker;
    /** Pending dynamic tracker -- waits for agents to evaluate DynamicJobFn and return generated LockJob[]. */
    pendingDynamics?: PendingDynamicTracker;
    /** Commit status reporter for setting pending/success/failure/error on commits. Optional. */
    checkRunReporter?: CheckRunReporter;
    /** Execution tracker for DB persistence. Optional -- if not set, execution tracking is skipped. */
    executionTracker?: ExecutionTracker;
    /** Agent registry for determining execution target platform/arch. Optional -- if not set, defaults to linux/x64. */
    agentRegistry?: AgentRegistry;
    /** Run coordinator for multi-orchestrator job routing. Optional -- if not set, all jobs dispatch locally (single-orchestrator mode). */
    coordinator?: RunCoordinator;
    /** Secret resolver for dispatch-time secret resolution. Optional -- if not set, secrets are not resolved. */
    secretResolver?: SecretResolver;
    /** Optional callback when source locations are extracted from a lock file workflow. */
    onSourceLocationsExtracted?: (workflowName: string, jobName: string, sourceLocations: Array<{
        file: string;
        line: number;
        column: number;
    } | undefined>) => void;
    /** Event router for registering lock file event subscriptions. Optional -- if not set, event routing is inactive. */
    eventRouter?: EventRouter;
    /** Registration store for persisting workflow registrations. Optional -- if not set, registration is skipped. */
    registrationStore?: RegistrationStore;
    /** Registration index for in-memory lookup. Optional -- if not set, registration is skipped. */
    registrationIndex?: RegistrationIndex;
    /** Cron scheduler for cache refresh after registration changes. Optional -- if not set, cron cache refresh is skipped. */
    cronScheduler?: CronScheduler;
    /** Database connection for ephemeral key storage. Optional -- if not set, cross-job secret output support is inactive. */
    db?: Kysely<Database>;
    /** Secret key (KICI_SECRET_KEY) for encrypting ephemeral private keys. Required when db is set. */
    secretKey?: string;
    /** Log storage backend for persisting webhook payloads. Optional -- if not set, payload storage is skipped. */
    logStorage?: LogStorage;
    /** Environment store for looking up deployment environments. Optional -- if not set, environment features are inactive. */
    environmentStore?: EnvironmentStore;
    /** Variable store for resolving environment variables. Optional -- if not set, environment vars are not merged. */
    variableStore?: VariableStore;
    /** Held run store for persisting protection rule holds. Optional -- if not set, holds are not persisted. */
    heldRunStore?: HeldRunStore;
    /** Trust resolver for determining contributor trust tiers. Optional -- if not set, trust resolution is skipped. */
    trustResolver?: TrustResolver;
    /** Identity links pushed from Platform for trust resolution. Optional -- defaults to empty. */
    identityLinks?: IdentityLink[];
    /** ci_trust permission levels per user ID from Platform push. Optional -- defaults to empty. */
    orgMemberPermissions?: Map<string, PermissionLevel>;
    /** Global workflow policy for org-level permission enforcement. Optional -- if not set, global workflows are unrestricted. */
    globalWorkflowPolicy?: GlobalWorkflowPolicy;
    /** Inbound webhook delivery log writer. Optional -- if not set, deliveries are not persisted to event_log. */
    eventLog?: EventLogWriter;
    /** Where this delivery arrived: 'relay' (Platform WS) or 'direct' (HTTP).
     *  Used by the eventLog writer to populate the source column. Defaults to
     *  'direct' when omitted (independent / direct paths). */
    eventLogSource?: EventLogSource;
    /** Contributor permission cache. Optional -- if not set, membership-webhook
     *  invalidations silently no-op. In platform/hybrid mode the singleton is
     *  created in server.ts and threaded through both the Platform-relay WS
     *  path and the generic webhook HTTP path. */
    contributorCache?: ContributorCache;
}
/**
 * Check if any trigger in the lock file workflows uses path filters.
 * Used to skip the changedFilesFetcher call when no path patterns are configured,
 * saving one provider API call per webhook.
 */
export declare function anyTriggerHasPathPatterns(workflows: LockWorkflow[]): boolean;
export { processWebhook } from './process-webhook.js';
/**
 * Dispatch a job that has become ready via the needs scheduler.
 *
 * Called by the onJobReady callback registered on the execution tracker.
 * Consumes the pending dispatch context stored at processWebhook time,
 * dispatches the job through the normal dispatcher path, and updates the
 * execution tracker with the real job ID.
 */
export declare function dispatchReadyJob(runId: string, jobName: string, dispatcher: Dispatcher, executionTracker?: ExecutionTracker, coordinator?: RunCoordinator, db?: Kysely<Database>): Promise<void>;
/**
 * Check whether a webhook event is a push to the repository's default branch.
 * Used to trigger registration extraction for workflow event subscriptions.
 *
 * Resolution order for the default branch:
 *   1. `normalizer.extractDefaultBranch?(payload)` — provider-specific hook
 *      (universal-git reads a JSONPath from the source's `payloadPaths.defaultBranch`).
 *   2. Fallback to `payload.repository.default_branch` — the GitHub-shaped
 *      default that most forges mirror.
 */
export declare function isDefaultBranchPush(info: WebhookInfo, event: SimulatedEvent, payload: Record<string, unknown>, normalizer: WebhookNormalizer): boolean;
/**
 * Create a serializable summary of a workflow decision for Platform forwarding.
 */
export declare function summarizeDecision(decision: WorkflowDecision): Record<string, unknown>;
//# sourceMappingURL=processor.d.ts.map